aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-08-24 16:57:24 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-08-26 11:16:17 +0200
commit2a09bbc8f813d28d69768ba9375b637e2ba8bd8d (patch)
tree867d6ec61d5d7ff30d03a90d124948c4d9bcccc6
parent98fff0dc733050d8bbdfba5235de918ba15fee0e (diff)
downloadkernel-2a09bbc8f813d28d69768ba9375b637e2ba8bd8d.tar.xz
kernel-2a09bbc8f813d28d69768ba9375b637e2ba8bd8d.zip
kernel/fs: ext2: cosmetic cleanup
-rw-r--r--kernel/kernel/filesystem/ext2/error.cpp3
-rw-r--r--kernel/kernel/filesystem/ext2/error.hpp3
-rw-r--r--kernel/kernel/filesystem/ext2/filesystem.cpp402
-rw-r--r--kernel/kernel/filesystem/ext2/filesystem.hpp93
-rw-r--r--kernel/kernel/filesystem/ext2/filesystem.tests.cpp6
-rw-r--r--kernel/kernel/filesystem/ext2/inode.cpp4
-rw-r--r--kernel/kernel/filesystem/ext2/inode.tests.cpp9
-rw-r--r--kernel/kernel/filesystem/ext2/linked_directory_entry.hpp3
-rw-r--r--libs/kstd/kstd/span.hpp57
9 files changed, 389 insertions, 191 deletions
diff --git a/kernel/kernel/filesystem/ext2/error.cpp b/kernel/kernel/filesystem/ext2/error.cpp
index ffe49460..73921379 100644
--- a/kernel/kernel/filesystem/ext2/error.cpp
+++ b/kernel/kernel/filesystem/ext2/error.cpp
@@ -42,6 +42,8 @@ namespace kernel::filesystem::ext2
return "not enough inodes";
case ext2_errc::unsupported_features_present:
return "unsupported features present";
+ case ext2_errc::name_too_long:
+ return "name too long";
default:
return "unknown ext2 error";
};
@@ -54,6 +56,7 @@ namespace kernel::filesystem::ext2
{
case ext2_errc::invalid_magic_number:
case ext2_errc::invalid_root_inode:
+ case ext2_errc::name_too_long:
if (condition.category() == kernel::filesystem::vfs_category())
{
return condition.value() == static_cast<int>(kernel::filesystem::vfs_errc::invalid_filesystem);
diff --git a/kernel/kernel/filesystem/ext2/error.hpp b/kernel/kernel/filesystem/ext2/error.hpp
index 59df1669..edd0d017 100644
--- a/kernel/kernel/filesystem/ext2/error.hpp
+++ b/kernel/kernel/filesystem/ext2/error.hpp
@@ -21,7 +21,8 @@ namespace kernel::filesystem::ext2
failed_to_read_block_group_descriptors,
not_enough_free_blocks,
not_enough_inodes,
- unsupported_features_present
+ unsupported_features_present,
+ name_too_long,
};
[[nodiscard]] auto ext2_category() noexcept -> kstd::error_category const &;
diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp
index 92945eb2..45d4efeb 100644
--- a/kernel/kernel/filesystem/ext2/filesystem.cpp
+++ b/kernel/kernel/filesystem/ext2/filesystem.cpp
@@ -16,6 +16,7 @@
#include <kstd/cstring.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
+#include <kstd/span.hpp>
#include <kstd/system_error.hpp>
#include <kstd/units.hpp>
#include <kstd/vector.hpp>
@@ -34,43 +35,53 @@ using namespace kstd::units_literals;
namespace kernel::filesystem::ext2
{
+ //! Check that this driver class is stateless.
+ static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(kernel::filesystem::ext2::filesystem));
+
namespace
{
- [[nodiscard]] auto bitmap_is_set(kstd::vector<uint8_t> const & bitmap, size_t index) -> bool
+ //! Check if the given bit has the bit at the given index set.
+ //!
+ //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap.
+ //!
+ //! @param bitmap The bitmap to check.
+ //! @param index The index of the bit to check.
+ [[nodiscard]] auto bitmap_is_set(kstd::vector<std::byte> const & bitmap, size_t index) -> bool
{
auto const byte_index = index / 8;
auto const bit_index = index % 8;
- return (bitmap.at(byte_index) & static_cast<uint8_t>(1u << bit_index)) != 0;
+ return (bitmap.at(byte_index) & static_cast<std::byte>(1u << bit_index)) != std::byte{};
}
- auto bitmap_set(kstd::vector<uint8_t> & bitmap, size_t index) -> void
+ //! Set the bit at the given index in the given bitmap.
+ //!
+ //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap.
+ //!
+ //! @param bitmap The bitmap to manipulate.
+ //! @param index The index of the bit to set.
+ auto bitmap_set(kstd::vector<std::byte> & bitmap, size_t index) -> void
{
auto const byte_index = index / 8;
auto const bit_index = index % 8;
- bitmap.at(byte_index) |= static_cast<uint8_t>(1u << bit_index);
+ bitmap.at(byte_index) |= static_cast<std::byte>(1u << bit_index);
}
- auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries;
- auto supported_ro_compatible_features = read_only_compatible_features::sparse_superblock_copies | //
- read_only_compatible_features::large_file_support;
- } // namespace
+ //! The set of "incompatible" Extended Filesystem features supported by this driver.
+ constexpr auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries;
- static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(kernel::filesystem::ext2::filesystem));
+ //! The set of "read-only" Extended Filesystem features supported by this driver.
+ constexpr auto supported_ro_compatible_features = read_only_compatible_features::sparse_superblock_copies | //
+ read_only_compatible_features::large_file_support;
- auto filesystem::indirect_levels(mount_state const & state) const -> std::array<indirect_level, 3>
- {
- return {
- {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block(state)},
- {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block(state)},
- {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block(state)}}
- };
- }
+ //! A set of permissions allowing all access to everyone.
+ constexpr auto no_access_restrictions = std::uint16_t{0x01FF};
+ } // namespace
auto filesystem::probe(inode_ptr const & backing_inode) const -> kstd::result<std::uint32_t>
{
auto superblock = ext2::superblock{};
- auto bytes = std::as_writable_bytes(std::span{&superblock, 1});
- auto read_result = backing_inode->read(bytes, constants::superblock_offset);
+ auto const bytes = kstd::raw_bytes(superblock);
+ auto const read_result = backing_inode->read(bytes, constants::superblock_offset);
if (!read_result)
{
@@ -93,12 +104,12 @@ namespace kernel::filesystem::ext2
auto filesystem::mount(inode_ptr const & backing_inode) -> kstd::result<mount_result>
{
- auto mount_state = kstd::make_shared<struct mount_state>();
+ auto const mount_state = kstd::make_shared<struct mount_state>();
auto & [superblock, block_group_descriptors, backing] = *mount_state;
backing = backing_inode;
- auto superblock_bytes = std::as_writable_bytes(std::span{&superblock, 1});
+ auto const superblock_bytes = kstd::raw_bytes(superblock);
if (auto read_result = backing->read(superblock_bytes, constants::superblock_offset); !read_result)
{
return kstd::failure(read_result.error());
@@ -113,7 +124,7 @@ namespace kernel::filesystem::ext2
auto const num_block_groups = (superblock.blocks_count + blocks_per_group - 1) / blocks_per_group;
block_group_descriptors = kstd::vector<block_group_descriptor>(num_block_groups);
- auto block_group_descriptors_bytes = std::as_writable_bytes(std::span{block_group_descriptors});
+ auto const block_group_descriptors_bytes = kstd::raw_bytes(block_group_descriptors);
if (auto read_result =
backing->read(block_group_descriptors_bytes, block_group_descriptor_table_offset(*mount_state));
!read_result)
@@ -121,7 +132,7 @@ namespace kernel::filesystem::ext2
return kstd::failure(read_result.error());
}
- auto root = read_inode(constants::root_inode_number, *mount_state);
+ auto const root = read_inode(constants::root_inode_number, *mount_state);
if (!root)
{
@@ -139,27 +150,27 @@ namespace kernel::filesystem::ext2
auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const
-> kstd::result<inode_ptr>
{
- if (!parent)
+ auto const ext2_parent = static_pointer_cast<inode>(parent);
+ if (!ext2_parent)
{
return kstd::failure(vfs_errc::invalid_inode);
}
- if (!parent->is_directory())
+ if (!ext2_parent->is_directory())
{
return kstd::failure(vfs_errc::not_a_directory);
}
- auto mount_state = static_pointer_cast<struct mount_state>(driver_data);
+ auto const mount_state = static_pointer_cast<struct mount_state>(driver_data);
if (!mount_state)
{
- return kstd::failure(vfs_errc::invalid_driver_data);
+ return kstd::failure(vfs_errc::not_mounted);
}
- auto * ext2_parent = static_cast<inode *>(parent.get());
auto const & inode_data = ext2_parent->data();
- kstd::vector<uint8_t> buffer(block_size(*mount_state).value);
+ auto buffer = kstd::vector<std::byte>{block_size(*mount_state).value};
- for (uint32_t i = 0; i < inode_block_count(inode_data, *mount_state); ++i)
+ for (auto i = 0u; i < block_count(inode_data, *mount_state); ++i)
{
auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data, *mount_state);
if (!global_block_number)
@@ -167,24 +178,24 @@ namespace kernel::filesystem::ext2
return kstd::failure(global_block_number.error());
}
- if (auto read_result = read_block(*global_block_number, buffer.data(), *mount_state); !read_result)
+ if (auto result = read_block(*global_block_number, buffer.data(), *mount_state); !result)
{
- return kstd::failure(read_result.error());
+ return kstd::failure(result.error());
}
- auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data());
- auto bytes_read = 0_B;
+ auto entry = reinterpret_cast<linked_directory_entry const *>(buffer.data());
+ auto read = 0_B;
- while (bytes_read < block_size(*mount_state) && entry->inode != 0)
+ while (read < block_size(*mount_state) && entry->inode != 0)
{
- auto const entry_name = std::string_view{entry->name.data(), entry->name_len};
+ auto const entry_name = std::string_view{&entry->name_start, entry->name_len};
if (entry_name == name)
{
return read_inode(entry->inode, *mount_state);
}
- bytes_read += kstd::bytes{entry->rec_len};
- entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + bytes_read);
+ read += kstd::bytes{entry->rec_len};
+ entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + read);
}
}
@@ -205,19 +216,19 @@ namespace kernel::filesystem::ext2
return kstd::failure(vfs_errc::not_a_directory);
}
- auto is_device = kapi::filesystem::is_device(type);
+ auto const is_device = kapi::filesystem::is_device(type);
if (is_device && !raw_device)
{
return kstd::failure(vfs_errc::invalid_argument);
}
- auto * ext2_parent = static_cast<inode *>(parent.get());
+ auto const ext2_parent = static_pointer_cast<inode>(parent);
if (!ext2_parent)
{
return kstd::failure(vfs_errc::invalid_argument);
}
- auto mount_state = static_pointer_cast<struct mount_state>(driver_data);
+ auto const mount_state = static_pointer_cast<struct mount_state>(driver_data);
if (!mount_state)
{
return kstd::failure(vfs_errc::invalid_driver_data);
@@ -231,65 +242,92 @@ namespace kernel::filesystem::ext2
auto inode_number = maybe_inode_number.value();
- inode_data new_inode_data{};
+ auto data = inode_data{};
- uint16_t const full_access_mode = 0x01FF; // TODO use correct access rights
- new_inode_data.mode = map_vfs_inode_type_into_inode_mode(type, full_access_mode);
- new_inode_data.size = 0;
- new_inode_data.dir_acl = 0;
- new_inode_data.links_count = 1;
- new_inode_data.blocks = 0;
- new_inode_data.block.fill(0);
+ // TODO: use correct access rights
+ data.mode = map_vfs_inode_type_into_inode_mode(type, no_access_restrictions);
+ data.size = 0;
+ data.dir_acl = 0;
+ data.links_count = 1;
+ data.blocks = 0;
+ data.block.fill(0);
if (is_device)
{
- new_inode_data.block[0] = static_cast<std::uint32_t>(raw_device->minor) | //
- (static_cast<std::uint32_t>(raw_device->major) << 8);
+ data.block[0] = static_cast<std::uint32_t>(raw_device->minor) | //
+ (static_cast<std::uint32_t>(raw_device->major) << 8);
}
- // TODO preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks;
+ auto created = kstd::make_shared<inode>(inode_number, data);
+ created->set_owning_mount(ext2_parent->owning_mount().lock());
- if (auto result = add_directory_entry(ext2_parent, name, inode_number, new_inode_data.mode, *mount_state); !result)
+ // TODO: preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks;
+ if (auto result = add_directory_entry(*ext2_parent, name, *created, data.mode, *mount_state); !result)
{
return kstd::failure(result.error());
}
- auto new_inode = kstd::make_shared<inode>(inode_number, new_inode_data);
- new_inode->set_owning_mount(ext2_parent->owning_mount().lock());
-
- if (new_inode->is_directory())
+ if (created->is_directory())
{
// TODO increment used_dirs_count in block group descriptor
- if (auto result = init_directory(new_inode, ext2_parent, *mount_state); !result)
+ if (auto result = init_directory(*created, *ext2_parent, *mount_state); !result)
{
return kstd::failure(result.error());
}
}
- if (auto write_result = write_inode(inode_number, new_inode->data(), *mount_state); !write_result)
+ if (auto result = write_inode(inode_number, created->data(), *mount_state); !result)
{
- return kstd::failure(write_result.error());
+ return kstd::failure(result.error());
}
- return new_inode;
+ return created;
+ }
+
+ auto filesystem::block_size(mount_state const & state) const -> kstd::bytes
+ {
+ return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size};
+ }
+
+ auto filesystem::revision_level(mount_state const & state) const -> uint32_t
+ {
+ return state.superblock.rev_level;
+ }
+
+ auto filesystem::inode_size(mount_state const & state) const -> kstd::bytes
+ {
+ return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size);
+ }
+
+ auto filesystem::block_count(inode_data const & data, mount_state const & state) const -> uint32_t
+ {
+ return data.blocks / (2 << state.superblock.log_block_size);
}
- auto filesystem::add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number,
- [[maybe_unused]] uint16_t mode, mount_state & state) -> kstd::result<void>
+ auto filesystem::indirect_levels(mount_state const & state) const -> std::array<indirect_level, 3>
{
- auto const last_block_index = inode_block_count(ext2_parent->data(), state) - 1;
- auto const global_block =
- map_inode_block_index_to_global_block_number(last_block_index, ext2_parent->data(), state);
+ return {
+ {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block(state)},
+ {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block(state)},
+ {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block(state)}}
+ };
+ }
+
+ auto filesystem::add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode,
+ mount_state & state) -> kstd::result<void>
+ {
+ auto const last_block_index = block_count(directory.data(), state) - 1;
+ auto const global_block = map_inode_block_index_to_global_block_number(last_block_index, directory.data(), state);
if (!global_block)
{
return kstd::failure(global_block.error());
}
- kstd::vector<uint8_t> buffer(block_size(state).value);
- if (auto read_result = read_block(*global_block, buffer.data(), state); !read_result)
+ auto buffer = kstd::vector<std::byte>{block_size(state).value};
+ if (auto result = read_block(*global_block, buffer.data(), state); !result)
{
- return kstd::failure(read_result.error());
+ return kstd::failure(result.error());
}
// TODO handle "gaps" in directory entries (e.g., after deletions) in the current implementation, we only
@@ -298,6 +336,11 @@ namespace kernel::filesystem::ext2
while (true)
{
auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + offset);
+ auto f = std::string_view{&entry->name_start, entry->name_len};
+ if (f.length() == 255)
+ {
+ break;
+ }
if (offset + kstd::bytes{entry->rec_len} >= block_size(state))
{
break;
@@ -315,84 +358,137 @@ namespace kernel::filesystem::ext2
if (kstd::bytes{last_entry->rec_len} - last_entry_actual_len >= needed_rec_len)
{
last_entry->rec_len = last_entry_actual_len.value;
- write_directory_entry_to_buffer(buffer.data(), offset + last_entry_actual_len, child_inode_number, name,
- file_type, state);
+ auto entry_offset = offset + last_entry_actual_len;
+ auto entry = write_directory_entry_to_buffer(buffer, entry_offset, child, name, file_type);
+ if (!entry)
+ {
+ return kstd::failure(entry.error());
+ }
+
+ auto remainder = block_size(state) - entry_offset;
+ (*entry)->rec_len = remainder.value;
+
return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer.data(), state); })
.transform([](auto) {});
}
else
{
- if (!ext2_parent->append_blocks(1))
+ if (!directory.append_blocks(1))
{
return kstd::failure(ext2_errc::not_enough_free_blocks);
}
- auto const new_block_index = inode_block_count(ext2_parent->data(), state) - 1;
+ auto const new_block_index = block_count(directory.data(), state) - 1;
auto const new_global_block =
- map_inode_block_index_to_global_block_number(new_block_index, ext2_parent->data(), state);
+ map_inode_block_index_to_global_block_number(new_block_index, directory.data(), state);
if (!new_global_block)
{
return kstd::failure(new_global_block.error());
}
kstd::libc::memset(buffer.data(), 0, block_size(state).value);
- write_directory_entry_to_buffer(buffer.data(), 0_B, child_inode_number, name, file_type, state);
- if (auto update_result = new_global_block.and_then(
+ auto entry = write_directory_entry_to_buffer(buffer, 0_B, child, name, file_type);
+ if (!entry)
+ {
+ return kstd::failure(entry.error());
+ }
+
+ auto remainder = block_size(state);
+ (*entry)->rec_len = remainder.value;
+
+ if (auto result = new_global_block.and_then(
[&](auto block_number) { return write_block(block_number, buffer.data(), state); });
- !update_result)
+ !result)
{
- return kstd::failure(update_result.error());
+ return kstd::failure(result.error());
}
}
- ext2_parent->data_mutable().size = (inode_block_count(ext2_parent->data(), state) * block_size(state)).value;
- return write_inode(ext2_parent->number(), ext2_parent->data(), state);
+ auto size = block_count(directory.data(), state) * block_size(state);
+ directory.data_mutable().size = size.value;
+ return write_inode(directory.number(), directory.data(), state);
}
- auto filesystem::init_directory(kstd::shared_ptr<kernel::filesystem::ext2::inode> const & new_inode,
- kernel::filesystem::ext2::inode * parent_inode, mount_state & state)
- -> kstd::result<void>
+ auto filesystem::init_directory(inode & directory, inode & parent, mount_state & state) -> kstd::result<void>
{
- if (!new_inode->append_blocks(1))
+ if (!directory.append_blocks(1))
{
return kstd::failure(ext2_errc::not_enough_free_blocks);
}
- auto const global_block = map_inode_block_index_to_global_block_number(0, new_inode->data(), state);
+ auto const global_block = map_inode_block_index_to_global_block_number(0, directory.data(), state);
if (!global_block)
{
return kstd::failure(global_block.error());
}
- kstd::vector<uint8_t> buffer(block_size(state).value, 0);
- write_directory_entry_to_buffer(buffer.data(), 0_B, new_inode->number(), ".", 2, state);
- auto const dot_actual_len = static_cast<kstd::bytes>((8u + 1 + 3u) & ~3u);
- auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data());
- entry->rec_len = dot_actual_len.value;
+ auto buffer = kstd::vector<std::byte>{block_size(state).value};
+ auto dot = write_directory_entry_to_buffer(buffer, 0_B, directory, ".", kapi::filesystem::file_type::directory);
- write_directory_entry_to_buffer(buffer.data(), dot_actual_len, parent_inode->number(), "..", 2, state);
+ if (!dot)
+ {
+ return kstd::failure(dot.error());
+ }
+
+ auto dot_dot_offset = kstd::bytes{(*dot)->rec_len};
+ auto dot_dot =
+ write_directory_entry_to_buffer(buffer, dot_dot_offset, parent, "..", kapi::filesystem::file_type::directory);
+ if (!dot_dot)
+ {
+ return kstd::failure(dot_dot.error());
+ }
+ auto remainder = block_size(state) - dot_dot_offset;
+ (*dot_dot)->rec_len = remainder.value;
// '..' inside the new dir counts as an extra hard link to the parent
- auto & parent_inode_data = parent_inode->data_mutable();
+ auto & parent_inode_data = parent.data_mutable();
parent_inode_data.links_count++;
- return write_inode(parent_inode->number(), parent_inode_data, state)
+ return write_inode(parent.number(), parent_inode_data, state)
.and_then([&]() { return global_block; })
.and_then([&](auto block_number) { return write_block(block_number, buffer.data(), state); })
.transform([](auto) {});
}
- auto filesystem::write_directory_entry_to_buffer(uint8_t * block_buffer, kstd::bytes offset,
- uint32_t child_inode_number, std::string_view name,
- uint8_t file_type, mount_state & state) -> void
+ auto filesystem::write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target,
+ std::string_view name, kapi::filesystem::file_type type)
+ -> kstd::result<linked_directory_entry *>
{
- auto const name_len = static_cast<uint8_t>(name.size());
- auto * entry = reinterpret_cast<linked_directory_entry *>(block_buffer + offset);
- entry->inode = child_inode_number;
- entry->rec_len = static_cast<uint16_t>(block_size(state) - offset);
- entry->name_len = name_len;
- entry->file_type = file_type;
- kstd::libc::memcpy(entry->name.data(), name.data(), name_len);
+ auto const name_length = static_cast<uint8_t>(name.size());
+ auto const record_size = (sizeof(linked_directory_entry) - 1 + name_length + 3u) & ~3u;
+ if (buffer.size() - offset.value < record_size)
+ {
+ // TODO: improve error code.
+ return kstd::failure(vfs_errc::invalid_argument);
+ }
+
+ auto * entry = reinterpret_cast<linked_directory_entry *>(buffer.data() + offset);
+ entry->inode = target.number();
+ entry->rec_len = record_size;
+ entry->name_len = name_length;
+ entry->file_type = [=] {
+ switch (type)
+ {
+ case kapi::filesystem::file_type::regular:
+ return 1;
+ case kapi::filesystem::file_type::directory:
+ return 2;
+ case kapi::filesystem::file_type::character_device:
+ return 3;
+ case kapi::filesystem::file_type::block_device:
+ return 4;
+ case kapi::filesystem::file_type::fifo:
+ return 5;
+ case kapi::filesystem::file_type::socket:
+ return 6;
+ case kapi::filesystem::file_type::symbolic_link:
+ return 7;
+ default:
+ return 0;
+ }
+ }();
+ kstd::libc::memcpy(&entry->name_start, name.data(), name_length);
+ return kstd::success(entry);
}
auto filesystem::read_inode(uint32_t inode_number, mount_state const & state) const
@@ -402,8 +498,7 @@ namespace kernel::filesystem::ext2
{
auto new_inode_data = inode_data{};
- if (auto read_result = state.backing_inode->read(as_writable_bytes(std::span{&new_inode_data, 1}), *inode_offset);
- !read_result)
+ if (auto read_result = state.backing_inode->read(kstd::raw_bytes(new_inode_data), *inode_offset); !read_result)
{
return kstd::failure(read_result.error());
}
@@ -423,7 +518,7 @@ namespace kernel::filesystem::ext2
{
if (auto inode_offset = calculate_inode_offset(inode_number, state))
{
- return state.backing_inode->write(as_bytes(std::span{&data, 1}), *inode_offset).transform([](auto) {});
+ return state.backing_inode->write(kstd::raw_bytes(data), *inode_offset).transform([](auto) {});
}
else
{
@@ -471,7 +566,7 @@ namespace kernel::filesystem::ext2
continue;
}
- kstd::vector<uint8_t> block_bitmap(block_size(state).value, 0);
+ auto block_bitmap = kstd::vector<std::byte>{block_size(state).value};
if (auto read_result = read_block(block_group_descriptor.block_bitmap, block_bitmap.data(), state); !read_result)
{
return kstd::failure(read_result.error());
@@ -502,7 +597,7 @@ namespace kernel::filesystem::ext2
// TODO update all block group descriptors
if (auto write_result =
- backing_inode->write(as_bytes(std::span{&block_group_descriptor, 1}),
+ backing_inode->write(kstd::raw_bytes(block_group_descriptor),
block_group_descriptor_table_offset(state) +
block_group_descriptor_index * kstd::size_of(block_group_descriptor));
!write_result)
@@ -513,7 +608,7 @@ namespace kernel::filesystem::ext2
}
// TODO update all superblock copies
- if (auto write_result = backing_inode->write(as_bytes(std::span{&superblock, 1}), constants::superblock_offset);
+ if (auto write_result = backing_inode->write(kstd::raw_bytes(superblock), constants::superblock_offset);
!write_result)
{
return kstd::failure(write_result.error());
@@ -545,7 +640,7 @@ namespace kernel::filesystem::ext2
continue;
}
- kstd::vector<uint8_t> inode_bitmap(block_size(state).value, 0);
+ auto inode_bitmap = kstd::vector<std::byte>{block_size(state).value};
if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap.data(), state); !read_result)
{
return kstd::failure(read_result.error());
@@ -649,8 +744,7 @@ namespace kernel::filesystem::ext2
auto const block_start_offset = block_number * block_size(state);
auto const number_start_address = block_start_offset + index * kstd::size_of<uint32_t>();
- if (auto read_result =
- state.backing_inode->read(as_writable_bytes(std::span{&block_number_buffer, 1}), number_start_address);
+ if (auto read_result = state.backing_inode->read(kstd::raw_bytes(block_number_buffer), number_start_address);
!read_result)
{
return kstd::failure(read_result.error());
@@ -704,7 +798,7 @@ namespace kernel::filesystem::ext2
}
else
{
- if (auto write_result = backing_inode->write(as_bytes(std::span{&block_number, 1}), parent_byte_offset);
+ if (auto write_result = backing_inode->write(kstd::raw_bytes(block_number), parent_byte_offset);
!write_result)
{
return kstd::failure(write_result.error());
@@ -716,7 +810,7 @@ namespace kernel::filesystem::ext2
if (stride == 1)
{
- if (auto write_result = backing_inode->write(as_bytes(std::span{&global_block_number, 1}), byte_offset);
+ if (auto write_result = backing_inode->write(kstd::raw_bytes(global_block_number), byte_offset);
!write_result)
{
return kstd::failure(write_result.error());
@@ -740,7 +834,7 @@ namespace kernel::filesystem::ext2
return kstd::success();
}
- auto filesystem::read_block(uint32_t block_number, void * buffer, mount_state & state) const
+ auto filesystem::read_block(uint32_t block_number, void * buffer, mount_state const & state) const
-> kstd::result<kstd::bytes>
{
auto const block_offset = static_cast<size_t>(block_number) * block_size(state);
@@ -762,7 +856,7 @@ namespace kernel::filesystem::ext2
{
// TODO update all block group descriptors
return state.backing_inode
- ->write(as_bytes(std::span{&block_group_descriptor, 1}),
+ ->write(kstd::raw_bytes(block_group_descriptor),
block_group_descriptor_table_offset(state) +
block_group_descriptor_index * kstd::size_of(block_group_descriptor))
.transform([](auto) {});
@@ -771,42 +865,56 @@ namespace kernel::filesystem::ext2
auto filesystem::write_superblock(mount_state & state) const -> kstd::result<void>
{
// TODO update all superblock copies
- return state.backing_inode->write(as_bytes(std::span{&state.superblock, 1}), constants::superblock_offset)
+ return state.backing_inode->write(kstd::raw_bytes(state.superblock), constants::superblock_offset)
.transform([](auto) {});
}
- auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> uint8_t
+ auto filesystem::map_inode_mode_to_file_type(uint16_t mode) -> kapi::filesystem::file_type
{
switch (mode & constants::mode_mask)
{
case constants::mode_regular:
- return 1;
+ return kapi::filesystem::file_type::regular;
case constants::mode_directory:
- return 2;
+ return kapi::filesystem::file_type::directory;
+ case constants::mode_block_device:
+ return kapi::filesystem::file_type::block_device;
+ case constants::mode_character_device:
+ return kapi::filesystem::file_type::character_device;
case constants::mode_symbolic_link:
- return 7;
+ return kapi::filesystem::file_type::symbolic_link;
+ case constants::mode_fifo:
+ return kapi::filesystem::file_type::fifo;
+ case constants::mode_socket:
+ return kapi::filesystem::file_type::socket;
default:
- return 0;
+ return kapi::filesystem::file_type{0};
}
}
auto filesystem::map_vfs_inode_type_into_inode_mode(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t
{
- switch (type)
- {
- case kapi::filesystem::file_type::directory:
- return mode | constants::mode_directory;
- case kapi::filesystem::file_type::regular:
- return mode | constants::mode_regular;
- case kapi::filesystem::file_type::block_device:
- return mode | constants::mode_block_device;
- case kapi::filesystem::file_type::character_device:
- return mode | constants::mode_character_device;
- case kapi::filesystem::file_type::symbolic_link:
- // return mode | constants::mode_symbolic_link; // TODO implement
- default:
- kapi::system::panic("[EXT2] Not implemented.");
- }
+ return mode | [=] {
+ switch (type)
+ {
+ case kapi::filesystem::file_type::directory:
+ return constants::mode_directory;
+ case kapi::filesystem::file_type::regular:
+ return constants::mode_regular;
+ case kapi::filesystem::file_type::block_device:
+ return constants::mode_block_device;
+ case kapi::filesystem::file_type::character_device:
+ return constants::mode_character_device;
+ case kapi::filesystem::file_type::symbolic_link:
+ return constants::mode_symbolic_link; // TODO implement
+ case kapi::filesystem::file_type::fifo:
+ return constants::mode_fifo;
+ case kapi::filesystem::file_type::socket:
+ return constants::mode_socket;
+ default:
+ kapi::system::panic("[EXT2] Not implemented.");
+ }
+ }();
}
auto filesystem::block_numbers_per_block(mount_state const & state) const -> size_t
@@ -829,26 +937,6 @@ namespace kernel::filesystem::ext2
return block_numbers_per_doubly_indirect_block(state) * block_numbers_per_block(state);
}
- auto filesystem::block_size(mount_state const & state) const -> kstd::bytes
- {
- return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size};
- }
-
- auto filesystem::revision_level(mount_state const & state) const -> uint32_t
- {
- return state.superblock.rev_level;
- }
-
- auto filesystem::inode_size(mount_state const & state) const -> kstd::bytes
- {
- return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size);
- }
-
- auto filesystem::inode_block_count(inode_data const & data, mount_state const & state) const -> uint32_t
- {
- return data.blocks / (2 << state.superblock.log_block_size);
- }
-
auto filesystem::update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void
{
data.blocks += delta * (2 << state.superblock.log_block_size);
diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp
index 4b5604e5..fec4a283 100644
--- a/kernel/kernel/filesystem/ext2/filesystem.hpp
+++ b/kernel/kernel/filesystem/ext2/filesystem.hpp
@@ -3,6 +3,7 @@
#include <kernel/filesystem/ext2/block_group_descriptor.hpp>
#include <kernel/filesystem/ext2/inode.hpp>
+#include <kernel/filesystem/ext2/linked_directory_entry.hpp>
#include <kernel/filesystem/ext2/mount_state.hpp>
#include <kernel/filesystem/ext2/superblock.hpp>
#include <kernel/filesystem/filesystem.hpp>
@@ -21,6 +22,7 @@
#include <cstddef>
#include <cstdint>
#include <optional>
+#include <span>
#include <string_view>
#include <type_traits>
@@ -49,6 +51,9 @@ namespace kernel::filesystem::ext2
constexpr uint16_t inline mode_symbolic_link = 0xA000;
constexpr uint16_t inline mode_block_device = 0x6000;
constexpr uint16_t inline mode_character_device = 0x2000;
+ constexpr uint16_t inline mode_fifo = 0x1000;
+ constexpr uint16_t inline mode_socket = 0xC000;
+
} // namespace constants
//! Ext2 incompatibility features
@@ -76,6 +81,9 @@ namespace kernel::filesystem::ext2
//! The Second Extended Filesystem (ext2)
struct filesystem final : kernel::filesystem::filesystem
{
+ //! @name Driver Interface Implementation
+ //! @{
+
[[nodiscard]] auto probe(inode_ptr const & backing_inode) const -> kstd::result<std::uint32_t> override;
auto mount(inode_ptr const & backing_inode) -> kstd::result<mount_result> override;
@@ -88,23 +96,48 @@ namespace kernel::filesystem::ext2
std::optional<kapi::filesystem::device_number> raw_device = std::nullopt)
-> kstd::result<inode_ptr> override;
+ //! @}
+
+ //! @name Filesystem Information Queries
+ //! @{
+
//! Get the size of a block in the filesystem.
//!
+ //! @param state The driver state to operate on.
//! @return The size of a block in bytes.
[[nodiscard]] auto block_size(mount_state const & state) const -> kstd::bytes;
//! Get the revision level of the filesystem.
//!
+ //! @param state The driver state to operate on.
//! @return The revision level.
[[nodiscard]] auto revision_level(mount_state const & state) const -> uint32_t;
+ //! Get the size of an inode in the filesystem.
+ //!
+ //! @param state The driver state to operate on.
+ //! @return The size of an inode in bytes.
+ [[nodiscard]] auto inode_size(mount_state const & state) const -> kstd::bytes;
+
+ //! Get the number of blocks allocated to an inode.
+ //!
+ //! @param data The inode data.
+ //! @param state The driver state to operate on.
+ //! @return The number of blocks allocated to the inode.
+ [[nodiscard]] auto block_count(inode_data const & data, mount_state const & state) const -> uint32_t;
+
+ //! @}
+
+ //! @name Block I/O
+ //! @{
+
//! Read a block from the backing inode into the provided buffer.
//!
//! @param block_number The number of the block to read.
//! @param buffer The buffer to read the block data into.
//! @param state The state to operate on.
//! @return The number of bytes read.
- auto read_block(uint32_t block_number, void * buffer, mount_state & state) const -> kstd::result<kstd::bytes>;
+ auto read_block(uint32_t block_number, void * buffer, mount_state const & state) const -> kstd::result<kstd::bytes>;
//! Write a block of data from the provided buffer to the backing inode.
//!
@@ -114,6 +147,8 @@ namespace kernel::filesystem::ext2
//! @return The number of bytes written.
auto write_block(uint32_t block_number, void const * buffer, mount_state & state) -> kstd::result<kstd::bytes>;
+ //! @}
+
//! Allocate a specified number of blocks.
//!
//! @param count The number of blocks to allocate.
@@ -121,18 +156,6 @@ namespace kernel::filesystem::ext2
//! @return A vector of the allocated block numbers.
auto allocate_blocks(size_t count, mount_state & state) -> kstd::result<kstd::vector<uint32_t>>;
- //! Get the size of an inode in the filesystem.
- //!
- //! @return The size of an inode in bytes.
- [[nodiscard]] auto inode_size(mount_state const & state) const -> kstd::bytes;
-
- //! Get the number of blocks allocated to an inode.
- //!
- //! @param data The inode data.
- //! @param state The state to operate on.
- //! @return The number of blocks allocated to the inode.
- [[nodiscard]] auto inode_block_count(inode_data const & data, mount_state const & state) const -> uint32_t;
-
//! Update the number of blocks allocated to an inode.
//!
//! @param data The inode data.
@@ -176,13 +199,45 @@ namespace kernel::filesystem::ext2
[[nodiscard]] auto indirect_levels(mount_state const & state) const -> std::array<indirect_level, 3>;
+ //! Allocate a single inode in the file system.
+ //!
+ //! @param state The driver state to operate on.
+ //! @return An inode number on success, an error otherwise.
auto allocate_inode(mount_state & state) -> kstd::result<uint32_t>;
- auto add_directory_entry(inode * ext2_parent, std::string_view name, uint32_t child_inode_number, uint16_t mode,
+
+ //! Add a new directory entry to a given directory.
+ //!
+ //! @param directory The directory to add the entry to.
+ //! @param name The name for the entry.
+ //! @param child The inode number of the child.
+ //! @param mode The file mode of the entry.
+ //! @param state The driver state to operate on.
+ //! @return Nothing on success, an error code otherwise.
+ auto add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode,
mount_state & state) -> kstd::result<void>;
- auto init_directory(kstd::shared_ptr<inode> const & new_inode, inode * parent_inode, mount_state & state)
- -> kstd::result<void>;
- auto write_directory_entry_to_buffer(uint8_t * block_buffer, kstd::bytes offset, uint32_t child_inode_number,
- std::string_view name, uint8_t file_type, mount_state & state) -> void;
+
+ //! Initialize a new directory inode.
+ //!
+ //! This function initializes an inode to be an empty directory. On success, the inode will be a directory with only
+ //! two children, "." and "..".
+ //!
+ //! @param directory The inode to initialize.
+ //! @param parent The parent of this newly initialized directory.
+ //! @param state The driver state to operate on.
+ //! @return Nothing on success, an error otherwise.
+ auto init_directory(inode & directory, inode & parent, mount_state & state) -> kstd::result<void>;
+
+ //! Write a new directory entry into the given buffer.
+ //!
+ //! @param buffer The buffer to write to.
+ //! @param offset The offset inside to the buffer.
+ //! @param target The inode the entry targets.
+ //! @param name The name for the entry.
+ //! @param type The filet type of the entry.
+ //! @return A pointer to the written entry on success, an error otherwise.
+ auto write_directory_entry_to_buffer(std::span<std::byte> buffer, kstd::bytes offset, inode & target,
+ std::string_view name, kapi::filesystem::file_type type)
+ -> kstd::result<linked_directory_entry *>;
[[nodiscard]] auto calculate_inode_offset(uint32_t inode_number, mount_state const & state) const
-> kstd::result<kstd::bytes>;
@@ -203,7 +258,7 @@ namespace kernel::filesystem::ext2
mount_state const & state) const -> kstd::result<void>;
[[nodiscard]] auto write_superblock(mount_state & state) const -> kstd::result<void>;
- auto map_inode_mode_to_file_type(uint16_t mode) -> uint8_t;
+ auto map_inode_mode_to_file_type(uint16_t mode) -> kapi::filesystem::file_type;
auto map_vfs_inode_type_into_inode_mode(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t;
};
} // namespace kernel::filesystem::ext2
diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp
index 752dd010..c0df4647 100644
--- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp
+++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp
@@ -166,12 +166,6 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture,
std::string_view result{reinterpret_cast<char *>(read_buffer.data()), static_cast<size_t>(*bytes_read)};
REQUIRE(result == "Hello");
}
-
- THEN("a symbolic link is not implemented yet")
- {
- REQUIRE_THROWS_AS(fs->create_inode(root, "blub", kapi::filesystem::file_type::symbolic_link, driver_data),
- kernel::tests::cpu::halt);
- }
}
}
diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp
index 8c7d0ec0..a12c65d8 100644
--- a/kernel/kernel/filesystem/ext2/inode.cpp
+++ b/kernel/kernel/filesystem/ext2/inode.cpp
@@ -136,7 +136,7 @@ namespace kernel::filesystem::ext2
auto block_size = (*filesystem)->block_size(*state);
- auto const current_block_count = (*filesystem)->inode_block_count(m_data, *state);
+ auto const current_block_count = (*filesystem)->block_count(m_data, *state);
auto const max_new_inode_size_without_new_blocks = current_block_count * block_size;
if (new_inode_size > max_new_inode_size_without_new_blocks)
@@ -217,7 +217,7 @@ namespace kernel::filesystem::ext2
for (auto i = 0uz; i < new_blocks->size(); ++i)
{
- auto const block_index = (*filesystem)->inode_block_count(m_data, *state) + i;
+ auto const block_index = (*filesystem)->block_count(m_data, *state) + i;
auto const global_block_number = new_blocks->at(i);
if (auto write_result =
diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp
index a6a88fc8..0152fd8b 100644
--- a/kernel/kernel/filesystem/ext2/inode.tests.cpp
+++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp
@@ -16,6 +16,7 @@
#include <kapi/filesystem.hpp>
#include <kstd/memory.hpp>
+#include <kstd/span.hpp>
#include <kstd/units.hpp>
#include <kstd/vector.hpp>
@@ -355,7 +356,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in
auto inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data();
auto const block_size = fs->block_size(*mount_state);
- auto const expected_allocated_blocks = 32 * fs->inode_block_count(inode_data, *mount_state);
+ auto const expected_allocated_blocks = 32 * fs->block_count(inode_data, *mount_state);
auto write_buffer = kstd::vector<std::byte>(block_size.value * expected_allocated_blocks, std::byte{'A'});
@@ -369,7 +370,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 in
auto new_inode_data = static_cast<kernel::filesystem::ext2::inode *>(file.value().get())->data();
REQUIRE(new_inode_data.size == write_buffer.size());
- REQUIRE(fs->inode_block_count(new_inode_data, *mount_state) == expected_allocated_blocks);
+ REQUIRE(fs->block_count(new_inode_data, *mount_state) == expected_allocated_blocks);
}
THEN("writing to a an inode of type directory panics")
@@ -433,7 +434,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]"
auto inode = kernel::filesystem::ext2::inode{inode_number, inode_data};
inode.set_owning_mount(*mount);
- REQUIRE(dev_inode->write(as_bytes(std::span{&inode, 1}), inode_data_offset));
+ REQUIRE(dev_inode->write(kstd::raw_bytes(inode), inode_data_offset));
auto const buffer =
kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '},
@@ -443,7 +444,7 @@ SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]"
{
auto do_read_inode = [&]() {
auto inode_data = kernel::filesystem::ext2::inode_data{};
- REQUIRE(dev_inode->read(as_writable_bytes(std::span{&inode_data, 1}), inode_data_offset));
+ REQUIRE(dev_inode->read(kstd::raw_bytes(inode_data), inode_data_offset));
auto read_inode = kernel::filesystem::ext2::inode{inode_number, inode_data};
read_inode.set_owning_mount(*mount);
return read_inode;
diff --git a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp
index 9c9d162d..08999cb8 100644
--- a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp
+++ b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp
@@ -1,7 +1,6 @@
#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP
#define TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP
-#include <array>
#include <cstdint>
namespace kernel::filesystem::ext2
@@ -13,7 +12,7 @@ namespace kernel::filesystem::ext2
uint16_t rec_len;
uint8_t name_len;
uint8_t file_type;
- std::array<char, 255> name; // NOLINT(readability-magic-numbers)
+ char name_start; // NOLINT(readability-magic-numbers)
};
} // namespace kernel::filesystem::ext2
diff --git a/libs/kstd/kstd/span.hpp b/libs/kstd/kstd/span.hpp
new file mode 100644
index 00000000..6be2eea2
--- /dev/null
+++ b/libs/kstd/kstd/span.hpp
@@ -0,0 +1,57 @@
+#ifndef KSTD_SPAN_HPP
+#define KSTD_SPAN_HPP
+
+#include <cstddef>
+#include <ranges>
+#include <span> // IWYU pragma: export
+
+namespace kstd
+{
+
+ //! Get a read-only span of bytes, that maps to a given object
+ //!
+ //! @param object The object to obtain the byte span for.
+ //! @return A span over the raw bytes of the object representation of the given object.
+ template<typename ObjectType>
+ requires(!std::ranges::range<ObjectType>)
+ constexpr auto raw_bytes(ObjectType const & object) -> std::span<std::byte const, sizeof(ObjectType)>
+ {
+ return std::as_bytes(std::span<ObjectType const, 1>{std::addressof(object), 1});
+ }
+
+ //! Get a read-write span of bytes, that maps to a given object
+ //!
+ //! @param object The object to obtain the byte span for.
+ //! @return A span over the raw bytes of the object representation of the given object.
+ template<typename ObjectType>
+ requires(!std::ranges::range<ObjectType>)
+ constexpr auto raw_bytes(ObjectType & object) -> std::span<std::byte, sizeof(ObjectType)>
+ {
+ return std::as_writable_bytes(std::span<ObjectType, 1>{std::addressof(object), 1});
+ }
+
+ //! Get a read-only span of bytes, that maps to a given range object
+ //!
+ //! @param range The range of objects to obtain the byte span for.
+ //! @return A span over the raw bytes of the object representation of the given object.
+ template<typename ObjectType>
+ requires(std::ranges::range<ObjectType>)
+ constexpr auto raw_bytes(ObjectType const & range) -> std::span<std::byte const>
+ {
+ return std::as_bytes(std::span{range});
+ }
+
+ //! Get a read-write span of bytes, that maps to a given range object
+ //!
+ //! @param range The range of objects to obtain the byte span for.
+ //! @return A span over the raw bytes of the object representation of the given object.
+ template<typename ObjectType>
+ requires(std::ranges::range<ObjectType>)
+ constexpr auto raw_bytes(ObjectType & range) -> std::span<std::byte>
+ {
+ return std::as_writable_bytes(std::span{range});
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file