aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Oesch <lukas.oesch@ost.ch>2026-06-10 10:40:46 +0200
committerLukas Oesch <lukas.oesch@ost.ch>2026-06-10 10:40:46 +0200
commit33abd5cf264cb9e34121082105b0bc17b3cf7a36 (patch)
tree36b15d53fea04f4f9d9af817100f7ad013bd9b5c
parentd01caf1c4aef3c89c68b9d1cc9fe56445f0860b5 (diff)
parent7e27130c342b7299a1d2188a7192a7f17b5ac2ad (diff)
downloadkernel-33abd5cf264cb9e34121082105b0bc17b3cf7a36.tar.xz
kernel-33abd5cf264cb9e34121082105b0bc17b3cf7a36.zip
Merge branch 'develop-BA-FS26' into 'develop'HEADdevelop
Merge of BA-FS26 branch into develop See merge request teachos/kernel!49
-rw-r--r--.aiignore1
-rw-r--r--.clang-format34
-rw-r--r--.clang-tidy18
-rw-r--r--.clangd7
-rw-r--r--.devcontainer/x86-64/Containerfile38
-rw-r--r--.devcontainer/x86-64/devcontainer.json26
-rw-r--r--.gitattributes2
-rw-r--r--.gitignore15
-rw-r--r--.gitlab-ci.yml64
-rw-r--r--.lazy.lua13
-rw-r--r--.lcovrc7
-rw-r--r--.nvim.lua169
-rw-r--r--.vscode/extensions.json1
-rw-r--r--.vscode/launch.json8
-rw-r--r--.vscode/settings.json55
-rw-r--r--.vscode/tasks.json32
-rw-r--r--CMakeLists.txt54
-rw-r--r--CMakePresets.json25
-rw-r--r--README.rst14
-rw-r--r--arch/x86_64/CMakeLists.txt77
-rw-r--r--arch/x86_64/arch/boot/boot.hpp (renamed from arch/x86_64/include/arch/boot/boot.hpp)2
-rw-r--r--arch/x86_64/arch/boot/boot32.S (renamed from arch/x86_64/src/boot/boot32.S)2
-rw-r--r--arch/x86_64/arch/boot/entry64.s (renamed from arch/x86_64/src/boot/entry64.s)0
-rw-r--r--arch/x86_64/arch/boot/initialize_runtime.cpp (renamed from arch/x86_64/src/boot/initialize_runtime.cpp)0
-rw-r--r--arch/x86_64/arch/boot/ld.hpp (renamed from arch/x86_64/include/arch/boot/ld.hpp)0
-rw-r--r--arch/x86_64/arch/boot/multiboot.s (renamed from arch/x86_64/src/boot/multiboot.s)7
-rw-r--r--arch/x86_64/arch/bus/isa.cpp14
-rw-r--r--arch/x86_64/arch/bus/isa.hpp18
-rw-r--r--arch/x86_64/arch/cpu/control_register.hpp (renamed from arch/x86_64/include/arch/cpu/control_register.hpp)4
-rw-r--r--arch/x86_64/arch/cpu/global_descriptor_table.hpp (renamed from arch/x86_64/include/arch/cpu/global_descriptor_table.hpp)4
-rw-r--r--arch/x86_64/arch/cpu/initialization.cpp164
-rw-r--r--arch/x86_64/arch/cpu/initialization.hpp12
-rw-r--r--arch/x86_64/arch/cpu/interrupts.S112
-rw-r--r--arch/x86_64/arch/cpu/interrupts.cpp203
-rw-r--r--arch/x86_64/arch/cpu/interrupts.hpp (renamed from arch/x86_64/include/arch/cpu/interrupts.hpp)61
-rw-r--r--arch/x86_64/arch/cpu/legacy_pic.hpp19
-rw-r--r--arch/x86_64/arch/cpu/model_specific_register.hpp (renamed from arch/x86_64/include/arch/cpu/model_specific_register.hpp)2
-rw-r--r--arch/x86_64/arch/cpu/registers.hpp (renamed from arch/x86_64/include/arch/cpu/registers.hpp)6
-rw-r--r--arch/x86_64/arch/cpu/segment_descriptor.hpp (renamed from arch/x86_64/include/arch/cpu/segment_descriptor.hpp)0
-rw-r--r--arch/x86_64/arch/cpu/segment_selector.hpp (renamed from arch/x86_64/include/arch/cpu/segment_selector.hpp)0
-rw-r--r--arch/x86_64/arch/cpu/task_state_segment.hpp (renamed from arch/x86_64/include/arch/cpu/task_state_segment.hpp)0
-rw-r--r--arch/x86_64/arch/debug/qemu_output.cpp (renamed from arch/x86_64/src/debug/qemu_output.cpp)4
-rw-r--r--arch/x86_64/arch/debug/qemu_output.hpp (renamed from arch/x86_64/include/arch/debug/qemu_output.hpp)4
-rw-r--r--arch/x86_64/arch/device_io/port_io.hpp (renamed from arch/x86_64/include/arch/device_io/port_io.hpp)11
-rw-r--r--arch/x86_64/arch/devices/init.cpp76
-rw-r--r--arch/x86_64/arch/devices/init.hpp12
-rw-r--r--arch/x86_64/arch/devices/legacy_pit.cpp60
-rw-r--r--arch/x86_64/arch/devices/legacy_pit.hpp29
-rw-r--r--arch/x86_64/arch/devices/local_apic.cpp134
-rw-r--r--arch/x86_64/arch/devices/local_apic.hpp37
-rw-r--r--arch/x86_64/arch/memory/higher_half_mapper.cpp (renamed from arch/x86_64/src/memory/higher_half_mapper.cpp)10
-rw-r--r--arch/x86_64/arch/memory/higher_half_mapper.hpp (renamed from arch/x86_64/include/arch/memory/higher_half_mapper.hpp)4
-rw-r--r--arch/x86_64/arch/memory/kernel_mapper.cpp (renamed from arch/x86_64/src/memory/kernel_mapper.cpp)22
-rw-r--r--arch/x86_64/arch/memory/kernel_mapper.hpp (renamed from arch/x86_64/include/arch/memory/kernel_mapper.hpp)3
-rw-r--r--arch/x86_64/arch/memory/mmu.cpp (renamed from arch/x86_64/src/memory/mmu.cpp)6
-rw-r--r--arch/x86_64/arch/memory/mmu.hpp (renamed from arch/x86_64/include/arch/memory/mmu.hpp)2
-rw-r--r--arch/x86_64/arch/memory/page_table.cpp (renamed from arch/x86_64/src/memory/page_table.cpp)4
-rw-r--r--arch/x86_64/arch/memory/page_table.hpp (renamed from arch/x86_64/include/arch/memory/page_table.hpp)11
-rw-r--r--arch/x86_64/arch/memory/page_utilities.hpp (renamed from arch/x86_64/include/arch/memory/page_utilities.hpp)2
-rw-r--r--arch/x86_64/arch/memory/region_allocator.cpp (renamed from arch/x86_64/src/memory/region_allocator.cpp)49
-rw-r--r--arch/x86_64/arch/memory/region_allocator.hpp (renamed from arch/x86_64/include/arch/memory/region_allocator.hpp)12
-rw-r--r--arch/x86_64/arch/vga/crtc.hpp (renamed from arch/x86_64/include/arch/vga/crtc.hpp)2
-rw-r--r--arch/x86_64/arch/vga/text.hpp10
-rw-r--r--arch/x86_64/arch/vga/text/attribute.hpp (renamed from arch/x86_64/include/arch/vga/text/attribute.hpp)6
-rw-r--r--arch/x86_64/arch/vga/text/buffer.cpp (renamed from arch/x86_64/src/vga/text/buffer.cpp)4
-rw-r--r--arch/x86_64/arch/vga/text/buffer.hpp (renamed from arch/x86_64/include/arch/vga/text/buffer.hpp)4
-rw-r--r--arch/x86_64/arch/vga/text/color.hpp (renamed from arch/x86_64/include/arch/vga/text/color.hpp)2
-rw-r--r--arch/x86_64/arch/vga/text/common_attributes.hpp (renamed from arch/x86_64/include/arch/vga/text/common_attributes.hpp)8
-rw-r--r--arch/x86_64/arch/vga/text/device.cpp (renamed from arch/x86_64/src/vga/text/device.cpp)10
-rw-r--r--arch/x86_64/arch/vga/text/device.hpp (renamed from arch/x86_64/include/arch/vga/text/device.hpp)6
-rw-r--r--arch/x86_64/arch/vga/text/flags.hpp (renamed from arch/x86_64/include/arch/vga/text/flags.hpp)2
-rw-r--r--arch/x86_64/include/arch/vga/text.hpp10
-rw-r--r--arch/x86_64/kapi/boot_modules.cpp53
-rw-r--r--arch/x86_64/kapi/cio.cpp6
-rw-r--r--arch/x86_64/kapi/cpu.cpp80
-rw-r--r--arch/x86_64/kapi/devices.cpp14
-rw-r--r--arch/x86_64/kapi/interrupts.cpp16
-rw-r--r--arch/x86_64/kapi/memory.cpp67
-rw-r--r--arch/x86_64/kapi/system.cpp118
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp69
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/idt_flags.hpp81
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp24
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp40
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/ist_offset.hpp45
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/segment_selector.hpp105
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/main.hpp51
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp91
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp18
-rw-r--r--arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp18
-rw-r--r--arch/x86_64/pre/include/arch/interrupt_handling/generic_interrupt_handler.hpp34
-rw-r--r--arch/x86_64/pre/include/arch/kernel/cpu/call.hpp30
-rw-r--r--arch/x86_64/pre/include/arch/kernel/cpu/idtr.hpp27
-rw-r--r--arch/x86_64/pre/include/arch/kernel/cpu/if.hpp21
-rw-r--r--arch/x86_64/pre/include/arch/kernel/cpu/msr.hpp64
-rw-r--r--arch/x86_64/pre/include/arch/kernel/cpu/tr.hpp24
-rw-r--r--arch/x86_64/pre/include/arch/kernel/halt.hpp13
-rw-r--r--arch/x86_64/pre/include/arch/kernel/main.hpp13
-rw-r--r--arch/x86_64/pre/include/arch/user/main.hpp16
-rw-r--r--arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp24
-rw-r--r--arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp20
-rw-r--r--arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp53
-rw-r--r--arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp13
-rw-r--r--arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp10
-rw-r--r--arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp24
-rw-r--r--arch/x86_64/pre/src/context_switching/main.cpp66
-rw-r--r--arch/x86_64/pre/src/context_switching/syscall/main.cpp35
-rw-r--r--arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp32
-rw-r--r--arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp121
-rw-r--r--arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp13
-rw-r--r--arch/x86_64/pre/src/kernel/cpu/call.cpp9
-rw-r--r--arch/x86_64/pre/src/kernel/cpu/idtr.cpp18
-rw-r--r--arch/x86_64/pre/src/kernel/cpu/if.cpp13
-rw-r--r--arch/x86_64/pre/src/kernel/cpu/tr.cpp16
-rw-r--r--arch/x86_64/pre/src/kernel/main.cpp71
-rw-r--r--arch/x86_64/pre/src/user/main.cpp35
-rw-r--r--arch/x86_64/scripts/kernel.ld6
-rw-r--r--arch/x86_64/support/grub.cfg.in3
-rw-r--r--arch/x86_64/support/modules/README.md130
-rw-r--r--arch/x86_64/support/modules/ext2_1KB_fs.img3
-rw-r--r--arch/x86_64/support/modules/ext2_2KB_fs.img3
-rw-r--r--arch/x86_64/support/modules/ext2_4KB_fs.img3
-rw-r--r--cmake/Modules/EnableCoverage.cmake9
-rw-r--r--cmake/Modules/GenerateBootableIso.cmake11
-rw-r--r--docs/conf.py7
-rw-r--r--docs/index.rst2
-rw-r--r--docs/kapi.rst10
-rw-r--r--docs/kapi/cio.rst17
-rw-r--r--docs/kapi/cpu.rst17
-rw-r--r--docs/kapi/devices.rst17
-rw-r--r--docs/kapi/interrupts.rst17
-rw-r--r--docs/kapi/memory.rst14
-rw-r--r--docs/kapi/system.rst14
-rw-r--r--docs/pre/arch/x86_64.rst9
-rw-r--r--docs/pre/arch/x86_64/boot.rst9
-rw-r--r--docs/pre/arch/x86_64/boot/pointers.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching.rst9
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table.rst9
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/gate_descriptor.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/idt_flags.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table copy.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer copy.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/ist_offset.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/segment_selector.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/main.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table.rst9
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/access_byte.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/gdt_flags.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table_pointer.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_base.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_extension.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_type.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/segment_descriptor_table/task_state_segment.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/syscall.rst9
-rw-r--r--docs/pre/arch/x86_64/context_switching/syscall/main.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/syscall/syscall_enable.rst5
-rw-r--r--docs/pre/arch/x86_64/context_switching/syscall/syscall_handler.rst5
-rw-r--r--docs/pre/arch/x86_64/exception_handling.rst9
-rw-r--r--docs/pre/arch/x86_64/exception_handling/assert.rst5
-rw-r--r--docs/pre/arch/x86_64/exception_handling/panic.rst5
-rw-r--r--docs/pre/arch/x86_64/interrupt_handling.rst9
-rw-r--r--docs/pre/arch/x86_64/interrupt_handling/generic_interrupt_handler.rst5
-rw-r--r--docs/pre/arch/x86_64/io.rst9
-rw-r--r--docs/pre/arch/x86_64/io/port_io.rst6
-rw-r--r--docs/pre/arch/x86_64/kernel.rst9
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu.rst9
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/call.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/control_register.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/gdtr.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/idtr.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/if.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/msr.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/segment_register.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/tlb.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/cpu/tr.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/halt.rst5
-rw-r--r--docs/pre/arch/x86_64/kernel/main.rst5
-rw-r--r--docs/pre/arch/x86_64/memory.rst9
-rw-r--r--docs/pre/arch/x86_64/memory/allocator.rst9
-rw-r--r--docs/pre/arch/x86_64/memory/allocator/area_frame_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/allocator/concept.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/allocator/physical_frame.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/allocator/tiny_frame_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/cpu.rst9
-rw-r--r--docs/pre/arch/x86_64/memory/heap.rst9
-rw-r--r--docs/pre/arch/x86_64/memory/heap/bump_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/heap/global_heap_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/heap/heap_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/heap/linked_list_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/heap/memory_block.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/heap/user_heap_allocator.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/main.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/multiboot.rst9
-rw-r--r--docs/pre/arch/x86_64/memory/multiboot/elf_symbols_section.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/multiboot/info.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/multiboot/memory_map.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/multiboot/reader.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging.rst9
-rw-r--r--docs/pre/arch/x86_64/memory/paging/active_page_table.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging/inactive_page_table.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging/kernel_mapper.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging/page_entry.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging/page_table.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging/temporary_page.rst5
-rw-r--r--docs/pre/arch/x86_64/memory/paging/virtual_page.rst5
-rw-r--r--docs/pre/arch/x86_64/stl.rst9
-rw-r--r--docs/pre/arch/x86_64/stl/container.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/contiguous_pointer_iterator.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/forward_value_iterator.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/mutex.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/shared_pointer.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/stack.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/unique_pointer.rst5
-rw-r--r--docs/pre/arch/x86_64/stl/vector.rst5
-rw-r--r--docs/pre/arch/x86_64/user.rst9
-rw-r--r--docs/pre/arch/x86_64/user/main.rst5
-rw-r--r--docs/pre/arch/x86_64/video.rst9
-rw-r--r--docs/pre/arch/x86_64/video/vga.rst9
-rw-r--r--docs/pre/arch/x86_64/video/vga/io.rst4
-rw-r--r--docs/pre/arch/x86_64/video/vga/text.rst5
-rw-r--r--docs/pre/cross/memory.rst11
-rw-r--r--docs/pre/cross/memory/asm_pointer.rst10
-rw-r--r--docs/requirements.txt5
-rw-r--r--kapi/CMakeLists.txt29
-rw-r--r--kapi/gdb/__init__.py35
-rw-r--r--kapi/gdb/boot_modules/__init__.py2
-rw-r--r--kapi/gdb/boot_modules/boot_module.py44
-rw-r--r--kapi/gdb/boot_modules/boot_module_registry.py20
-rw-r--r--kapi/gdb/devices/__init__.py1
-rw-r--r--kapi/gdb/devices/device.py20
-rw-r--r--kapi/gdb/memory/__init__.py4
-rw-r--r--kapi/gdb/memory/address.py31
-rw-r--r--kapi/gdb/memory/chunk.py41
-rw-r--r--kapi/include/kapi/cpu.hpp13
-rw-r--r--kapi/include/kapi/memory/layout.hpp26
-rw-r--r--kapi/kapi.dox8
-rw-r--r--kapi/kapi/acpi.hpp40
-rw-r--r--kapi/kapi/boot.hpp (renamed from kapi/include/kapi/boot.hpp)0
-rw-r--r--kapi/kapi/boot_module/boot_module.hpp23
-rw-r--r--kapi/kapi/boot_module/boot_module_registry.hpp107
-rw-r--r--kapi/kapi/boot_modules.hpp31
-rw-r--r--kapi/kapi/cio.hpp (renamed from kapi/include/kapi/cio.hpp)32
-rw-r--r--kapi/kapi/cio/output_device.hpp (renamed from kapi/include/kapi/cio/output_device.hpp)2
-rw-r--r--kapi/kapi/cpu.hpp135
-rw-r--r--kapi/kapi/devices.hpp37
-rw-r--r--kapi/kapi/devices/bus.hpp63
-rw-r--r--kapi/kapi/devices/cpu.hpp37
-rw-r--r--kapi/kapi/devices/device.hpp80
-rw-r--r--kapi/kapi/devices/manager.hpp53
-rw-r--r--kapi/kapi/filesystem.hpp73
-rw-r--r--kapi/kapi/interrupts.hpp74
-rw-r--r--kapi/kapi/memory.hpp (renamed from kapi/include/kapi/memory.hpp)79
-rw-r--r--kapi/kapi/memory/address.hpp (renamed from kapi/include/kapi/memory/address.hpp)23
-rw-r--r--kapi/kapi/memory/chunk.hpp (renamed from kapi/include/kapi/memory/chunk.hpp)10
-rw-r--r--kapi/kapi/memory/frame.hpp (renamed from kapi/include/kapi/memory/frame.hpp)8
-rw-r--r--kapi/kapi/memory/frame_allocator.hpp (renamed from kapi/include/kapi/memory/frame_allocator.hpp)4
-rw-r--r--kapi/kapi/memory/layout.hpp54
-rw-r--r--kapi/kapi/memory/page.hpp (renamed from kapi/include/kapi/memory/page.hpp)8
-rw-r--r--kapi/kapi/memory/page_mapper.hpp (renamed from kapi/include/kapi/memory/page_mapper.hpp)6
-rw-r--r--kapi/kapi/system.hpp (renamed from kapi/include/kapi/system.hpp)12
-rw-r--r--kernel/CMakeLists.txt231
-rw-r--r--kernel/include/kernel/acpi/manager.hpp32
-rw-r--r--kernel/include/kernel/devices/block_device.hpp97
-rw-r--r--kernel/include/kernel/devices/block_device_utils.hpp44
-rw-r--r--kernel/include/kernel/devices/root_bus.hpp16
-rw-r--r--kernel/include/kernel/devices/storage/controller.hpp71
-rw-r--r--kernel/include/kernel/devices/storage/management.hpp78
-rw-r--r--kernel/include/kernel/devices/storage/ram_disk/controller.hpp31
-rw-r--r--kernel/include/kernel/devices/storage/ram_disk/device.hpp58
-rw-r--r--kernel/include/kernel/filesystem/constants.hpp14
-rw-r--r--kernel/include/kernel/filesystem/dentry.hpp104
-rw-r--r--kernel/include/kernel/filesystem/devfs/filesystem.hpp46
-rw-r--r--kernel/include/kernel/filesystem/devfs/inode.hpp42
-rw-r--r--kernel/include/kernel/filesystem/device_inode.hpp64
-rw-r--r--kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp24
-rw-r--r--kernel/include/kernel/filesystem/ext2/filesystem.hpp115
-rw-r--r--kernel/include/kernel/filesystem/ext2/inode.hpp105
-rw-r--r--kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp22
-rw-r--r--kernel/include/kernel/filesystem/ext2/superblock.hpp74
-rw-r--r--kernel/include/kernel/filesystem/filesystem.hpp79
-rw-r--r--kernel/include/kernel/filesystem/inode.hpp69
-rw-r--r--kernel/include/kernel/filesystem/mount.hpp97
-rw-r--r--kernel/include/kernel/filesystem/mount_table.hpp58
-rw-r--r--kernel/include/kernel/filesystem/open_file_descriptor.hpp68
-rw-r--r--kernel/include/kernel/filesystem/open_file_table.hpp66
-rw-r--r--kernel/include/kernel/filesystem/path.hpp71
-rw-r--r--kernel/include/kernel/filesystem/rootfs/filesystem.hpp41
-rw-r--r--kernel/include/kernel/filesystem/rootfs/inode.hpp45
-rw-r--r--kernel/include/kernel/filesystem/type.hpp42
-rw-r--r--kernel/include/kernel/filesystem/type_registry.hpp53
-rw-r--r--kernel/include/kernel/filesystem/vfs.hpp113
-rw-r--r--kernel/include/kernel/memory.hpp4
-rw-r--r--kernel/include/kernel/memory/bitmap_allocator.hpp2
-rw-r--r--kernel/include/kernel/memory/block_list_allocator.hpp25
-rw-r--r--kernel/include/kernel/memory/heap_allocator.hpp7
-rw-r--r--kernel/include/kernel/memory/mmio_allocator.hpp41
-rw-r--r--kernel/include/kernel/test_support/boot_modules.hpp10
-rw-r--r--kernel/include/kernel/test_support/bump_frame_allocator.hpp54
-rw-r--r--kernel/include/kernel/test_support/cio.hpp39
-rw-r--r--kernel/include/kernel/test_support/cpu.hpp23
-rw-r--r--kernel/include/kernel/test_support/devices/block_device.hpp31
-rw-r--r--kernel/include/kernel/test_support/devices/character_device.hpp23
-rw-r--r--kernel/include/kernel/test_support/devices/storage/management.hpp10
-rw-r--r--kernel/include/kernel/test_support/filesystem/ext2.hpp21
-rw-r--r--kernel/include/kernel/test_support/filesystem/filesystem.hpp22
-rw-r--r--kernel/include/kernel/test_support/filesystem/inode.hpp19
-rw-r--r--kernel/include/kernel/test_support/filesystem/open_file_table.hpp10
-rw-r--r--kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp48
-rw-r--r--kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp23
-rw-r--r--kernel/include/kernel/test_support/filesystem/vfs.hpp10
-rw-r--r--kernel/include/kernel/test_support/log_buffer.hpp37
-rw-r--r--kernel/include/kernel/test_support/memory.hpp22
-rw-r--r--kernel/include/kernel/test_support/page_mapper.hpp49
-rw-r--r--kernel/include/kernel/test_support/simulated_memory.hpp67
-rw-r--r--kernel/kapi/acpi.cpp40
-rw-r--r--kernel/kapi/boot_modules.cpp41
-rw-r--r--kernel/kapi/cio.cpp2
-rw-r--r--kernel/kapi/cpu.cpp35
-rw-r--r--kernel/kapi/cpu.tests.cpp19
-rw-r--r--kernel/kapi/devices.cpp86
-rw-r--r--kernel/kapi/devices/bus.cpp72
-rw-r--r--kernel/kapi/devices/cpu.cpp31
-rw-r--r--kernel/kapi/devices/device.cpp43
-rw-r--r--kernel/kapi/filesystem.cpp76
-rw-r--r--kernel/kapi/filesystem.tests.cpp199
-rw-r--r--kernel/kapi/interrupts.cpp65
-rw-r--r--kernel/kapi/memory.cpp60
-rw-r--r--kernel/kapi/system.cpp4
-rw-r--r--kernel/kapi/system.tests.cpp30
-rw-r--r--kernel/kstd/os.cpp2
-rw-r--r--kernel/kstd/print.cpp113
-rw-r--r--kernel/kstd/print.tests.cpp23
-rw-r--r--kernel/src/acpi/manager.cpp98
-rw-r--r--kernel/src/devices/block_device.cpp42
-rw-r--r--kernel/src/devices/block_device.tests.cpp46
-rw-r--r--kernel/src/devices/block_device_utils.cpp106
-rw-r--r--kernel/src/devices/block_device_utils.tests.cpp219
-rw-r--r--kernel/src/devices/root_bus.cpp12
-rw-r--r--kernel/src/devices/storage/controller.cpp44
-rw-r--r--kernel/src/devices/storage/management.cpp95
-rw-r--r--kernel/src/devices/storage/ram_disk/controller.cpp27
-rw-r--r--kernel/src/devices/storage/ram_disk/device.cpp71
-rw-r--r--kernel/src/devices/storage/ram_disk/device.tests.cpp116
-rw-r--r--kernel/src/filesystem/dentry.cpp95
-rw-r--r--kernel/src/filesystem/dentry.tests.cpp150
-rw-r--r--kernel/src/filesystem/devfs/filesystem.cpp81
-rw-r--r--kernel/src/filesystem/devfs/filesystem.tests.cpp72
-rw-r--r--kernel/src/filesystem/devfs/inode.cpp21
-rw-r--r--kernel/src/filesystem/devfs/inode.tests.cpp55
-rw-r--r--kernel/src/filesystem/device_inode.cpp57
-rw-r--r--kernel/src/filesystem/device_inode.tests.cpp109
-rw-r--r--kernel/src/filesystem/ext2/filesystem.cpp247
-rw-r--r--kernel/src/filesystem/ext2/filesystem.tests.cpp139
-rw-r--r--kernel/src/filesystem/ext2/inode.cpp111
-rw-r--r--kernel/src/filesystem/ext2/inode.tests.cpp376
-rw-r--r--kernel/src/filesystem/filesystem.cpp60
-rw-r--r--kernel/src/filesystem/inode.cpp24
-rw-r--r--kernel/src/filesystem/mount.cpp90
-rw-r--r--kernel/src/filesystem/mount.tests.cpp94
-rw-r--r--kernel/src/filesystem/mount_table.cpp79
-rw-r--r--kernel/src/filesystem/mount_table.tests.cpp184
-rw-r--r--kernel/src/filesystem/open_file_descriptor.cpp46
-rw-r--r--kernel/src/filesystem/open_file_descriptor.tests.cpp113
-rw-r--r--kernel/src/filesystem/open_file_table.cpp87
-rw-r--r--kernel/src/filesystem/open_file_table.tests.cpp106
-rw-r--r--kernel/src/filesystem/path.tests.cpp69
-rw-r--r--kernel/src/filesystem/rootfs/filesystem.cpp47
-rw-r--r--kernel/src/filesystem/rootfs/filesystem.tests.cpp38
-rw-r--r--kernel/src/filesystem/rootfs/inode.cpp23
-rw-r--r--kernel/src/filesystem/rootfs/inode.tests.cpp37
-rw-r--r--kernel/src/filesystem/type_registry.cpp72
-rw-r--r--kernel/src/filesystem/type_registry.tests.cpp77
-rw-r--r--kernel/src/filesystem/vfs.cpp299
-rw-r--r--kernel/src/filesystem/vfs.tests.cpp568
-rw-r--r--kernel/src/main.cpp142
-rw-r--r--kernel/src/memory.cpp15
-rw-r--r--kernel/src/memory/bitmap_allocator.cpp24
-rw-r--r--kernel/src/memory/bitmap_allocator.tests.cpp288
-rw-r--r--kernel/src/memory/block_list_allocator.cpp36
-rw-r--r--kernel/src/memory/block_list_allocator.tests.cpp85
-rw-r--r--kernel/src/memory/mmio_allocator.cpp111
-rw-r--r--kernel/src/memory/operators.cpp21
-rw-r--r--kernel/src/test_support/devices/block_device.cpp61
-rw-r--r--kernel/src/test_support/devices/character_device.cpp19
-rw-r--r--kernel/src/test_support/filesystem/ext2.cpp68
-rw-r--r--kernel/src/test_support/filesystem/filesystem.cpp17
-rw-r--r--kernel/src/test_support/filesystem/inode.cpp23
-rw-r--r--kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp139
-rw-r--r--kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp31
l---------kernel/src/test_support/filesystem/test_assets/README.md1
-rw-r--r--kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img3
-rw-r--r--kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img3
-rw-r--r--kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img3
-rw-r--r--kernel/src/test_support/kapi/cio.cpp54
-rw-r--r--kernel/src/test_support/kapi/cpu.cpp52
-rw-r--r--kernel/src/test_support/kapi/interrupts.cpp11
-rw-r--r--kernel/src/test_support/kapi/memory.cpp74
-rw-r--r--kernel/src/test_support/log_buffer.cpp33
-rw-r--r--kernel/src/test_support/output_device.cpp28
-rw-r--r--kernel/src/test_support/page_mapper.cpp70
-rw-r--r--kernel/src/test_support/simulated_memory.cpp106
-rw-r--r--kernel/src/test_support/state_reset_listener.cpp48
-rw-r--r--libs/CMakeLists.txt3
-rw-r--r--libs/acpi/CMakeLists.txt118
-rw-r--r--libs/acpi/acpi/acpi.hpp12
-rw-r--r--libs/acpi/acpi/common/basic_table.hpp40
-rw-r--r--libs/acpi/acpi/common/checksum.cpp19
-rw-r--r--libs/acpi/acpi/common/checksum.hpp20
-rw-r--r--libs/acpi/acpi/common/table_header.cpp130
-rw-r--r--libs/acpi/acpi/common/table_header.hpp56
-rw-r--r--libs/acpi/acpi/common/table_header.test.cpp60
-rw-r--r--libs/acpi/acpi/common/table_signature.hpp17
-rw-r--r--libs/acpi/acpi/common/table_type.hpp17
-rw-r--r--libs/acpi/acpi/common/vla_table.hpp120
-rw-r--r--libs/acpi/acpi/data/madt.cpp112
-rw-r--r--libs/acpi/acpi/data/madt.hpp117
-rw-r--r--libs/acpi/acpi/data/madt.test.cpp57
-rw-r--r--libs/acpi/acpi/data/rsdt.cpp39
-rw-r--r--libs/acpi/acpi/data/rsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/rsdt.test.cpp48
-rw-r--r--libs/acpi/acpi/data/xsdt.cpp39
-rw-r--r--libs/acpi/acpi/data/xsdt.hpp42
-rw-r--r--libs/acpi/acpi/data/xsdt.test.cpp48
-rw-r--r--libs/acpi/acpi/pointers.cpp56
-rw-r--r--libs/acpi/acpi/pointers.hpp72
-rw-r--r--libs/acpi/acpi/pointers.test.cpp30
-rw-r--r--libs/acpi/acpi/test_data/tables.S17
-rw-r--r--libs/acpi/acpi/test_data/tables.hpp28
-rw-r--r--libs/acpi/cmake/Scripts/IaslCompile.cmake16
-rw-r--r--libs/acpi/test_data/basic_madt.asl29
-rw-r--r--libs/acpi/test_data/basic_rsdp.asl16
-rw-r--r--libs/acpi/test_data/basic_rsdt.asl26
-rw-r--r--libs/acpi/test_data/basic_xsdt.asl26
-rw-r--r--libs/acpi/test_data/table_header.asl9
-rw-r--r--libs/elf/CMakeLists.txt30
-rw-r--r--libs/elf/elf/format.hpp (renamed from libs/elf/include/elf/format.hpp)0
-rw-r--r--libs/elf/elf/section_header.hpp (renamed from libs/elf/include/elf/section_header.hpp)2
-rw-r--r--libs/kstd/CMakeLists.txt117
-rw-r--r--libs/kstd/gdb/__init__.py23
-rw-r--r--libs/kstd/gdb/smart_pointers.py68
-rw-r--r--libs/kstd/gdb/string.py27
-rw-r--r--libs/kstd/gdb/units.py0
-rw-r--r--libs/kstd/gdb/vector.py41
-rw-r--r--libs/kstd/include/kstd/bits/format_context.hpp31
-rw-r--r--libs/kstd/include/kstd/bits/format_specs.hpp104
-rw-r--r--libs/kstd/include/kstd/bits/format_string.hpp131
-rw-r--r--libs/kstd/include/kstd/bits/formatter.hpp300
-rw-r--r--libs/kstd/include/kstd/bits/shared_ptr.hpp258
-rw-r--r--libs/kstd/include/kstd/format8
-rw-r--r--libs/kstd/include/kstd/memory7
-rw-r--r--libs/kstd/include/kstd/vector569
-rw-r--r--libs/kstd/kstd/allocator64
-rw-r--r--libs/kstd/kstd/asm_ptr (renamed from libs/kstd/include/kstd/asm_ptr)0
-rw-r--r--libs/kstd/kstd/bits/concepts.hpp15
-rw-r--r--libs/kstd/kstd/bits/flat_map.hpp186
-rw-r--r--libs/kstd/kstd/bits/format/arg.hpp75
-rw-r--r--libs/kstd/kstd/bits/format/args.hpp160
-rw-r--r--libs/kstd/kstd/bits/format/context.hpp65
-rw-r--r--libs/kstd/kstd/bits/format/error.hpp24
-rw-r--r--libs/kstd/kstd/bits/format/formatter.hpp92
-rw-r--r--libs/kstd/kstd/bits/format/formatter/bool.hpp83
-rw-r--r--libs/kstd/kstd/bits/format/formatter/byte.hpp23
-rw-r--r--libs/kstd/kstd/bits/format/formatter/char.hpp94
-rw-r--r--libs/kstd/kstd/bits/format/formatter/cstring.hpp29
-rw-r--r--libs/kstd/kstd/bits/format/formatter/integral.hpp204
-rw-r--r--libs/kstd/kstd/bits/format/formatter/ordering.hpp111
-rw-r--r--libs/kstd/kstd/bits/format/formatter/pointer.hpp42
-rw-r--r--libs/kstd/kstd/bits/format/formatter/range.hpp32
-rw-r--r--libs/kstd/kstd/bits/format/formatter/string_view.hpp41
-rw-r--r--libs/kstd/kstd/bits/format/fwd.hpp23
-rw-r--r--libs/kstd/kstd/bits/format/output_buffer.hpp32
-rw-r--r--libs/kstd/kstd/bits/format/parse_context.hpp109
-rw-r--r--libs/kstd/kstd/bits/format/specifiers.hpp205
-rw-r--r--libs/kstd/kstd/bits/format/string.hpp148
-rw-r--r--libs/kstd/kstd/bits/format/vformat.hpp99
-rw-r--r--libs/kstd/kstd/bits/observer_ptr.hpp163
-rw-r--r--libs/kstd/kstd/bits/observer_ptr.test.cpp360
-rw-r--r--libs/kstd/kstd/bits/print_sink.hpp (renamed from libs/kstd/include/kstd/bits/print_sink.hpp)2
-rw-r--r--libs/kstd/kstd/bits/shared_ptr.hpp648
-rw-r--r--libs/kstd/kstd/bits/unique_ptr.hpp (renamed from libs/kstd/include/kstd/bits/unique_ptr.hpp)25
-rw-r--r--libs/kstd/kstd/cstring21
-rw-r--r--libs/kstd/kstd/ext/bitfield_enum (renamed from libs/kstd/include/kstd/ext/bitfield_enum)0
-rw-r--r--libs/kstd/kstd/flat_map406
-rw-r--r--libs/kstd/kstd/flat_map.test.cpp351
-rw-r--r--libs/kstd/kstd/format22
-rw-r--r--libs/kstd/kstd/format.test.cpp114
-rw-r--r--libs/kstd/kstd/libc/stdlib.cpp (renamed from libs/kstd/src/libc/stdlib.cpp)2
-rw-r--r--libs/kstd/kstd/libc/string.cpp78
-rw-r--r--libs/kstd/kstd/memory8
-rw-r--r--libs/kstd/kstd/mutex (renamed from libs/kstd/include/kstd/mutex)0
-rw-r--r--libs/kstd/kstd/mutex.cpp (renamed from libs/kstd/src/mutex.cpp)4
-rw-r--r--libs/kstd/kstd/os/error.cpp (renamed from libs/kstd/src/os/error.cpp)2
-rw-r--r--libs/kstd/kstd/os/error.hpp (renamed from libs/kstd/include/kstd/os/error.hpp)0
-rw-r--r--libs/kstd/kstd/os/print.hpp (renamed from libs/kstd/include/kstd/os/print.hpp)4
-rw-r--r--libs/kstd/kstd/print (renamed from libs/kstd/include/kstd/print)24
-rw-r--r--libs/kstd/kstd/ranges21
-rw-r--r--libs/kstd/kstd/stack (renamed from libs/kstd/include/kstd/stack)3
-rw-r--r--libs/kstd/kstd/string369
-rw-r--r--libs/kstd/kstd/string.test.cpp445
-rw-r--r--libs/kstd/kstd/test_support/os_panic.hpp23
-rw-r--r--libs/kstd/kstd/test_support/os_panic.test.cpp15
-rw-r--r--libs/kstd/kstd/test_support/test_types.hpp313
-rw-r--r--libs/kstd/kstd/unikstd.h12
-rw-r--r--libs/kstd/kstd/units149
-rw-r--r--libs/kstd/kstd/vector1079
-rw-r--r--libs/kstd/kstd/vector.test.cpp2003
-rw-r--r--libs/kstd/kstd/vformat.cpp209
-rw-r--r--libs/kstd/src/libc/string.cpp53
-rw-r--r--libs/multiboot2/CMakeLists.txt37
-rw-r--r--libs/multiboot2/multiboot2/constants.hpp (renamed from libs/multiboot2/include/multiboot2/constants.hpp)2
-rw-r--r--libs/multiboot2/multiboot2/constants/architecture_id.hpp (renamed from libs/multiboot2/include/multiboot2/constants/architecture_id.hpp)0
-rw-r--r--libs/multiboot2/multiboot2/constants/information_id.hpp (renamed from libs/multiboot2/include/multiboot2/constants/information_id.hpp)8
-rw-r--r--libs/multiboot2/multiboot2/constants/memory_type.hpp (renamed from libs/multiboot2/include/multiboot2/constants/memory_type.hpp)0
-rw-r--r--libs/multiboot2/multiboot2/constants/tag_id.hpp (renamed from libs/multiboot2/include/multiboot2/constants/tag_id.hpp)0
-rw-r--r--libs/multiboot2/multiboot2/information.hpp (renamed from libs/multiboot2/include/multiboot2/information.hpp)92
-rw-r--r--libs/multiboot2/multiboot2/information/data.hpp (renamed from libs/multiboot2/include/multiboot2/information/data.hpp)60
-rw-r--r--libs/multiboot2/multiboot2/information/iterator.hpp (renamed from libs/multiboot2/include/multiboot2/information/iterator.hpp)4
-rw-r--r--libs/multiboot2/multiboot2/information/tag.hpp (renamed from libs/multiboot2/include/multiboot2/information/tag.hpp)6
-rw-r--r--libs/multiboot2/test_data/mbi.bin3
-rw-r--r--poetry.lock1296
-rw-r--r--pyproject.toml22
-rw-r--r--scripts/ci/parse_clang_tidy.py54
-rw-r--r--scripts/gdb/teachos.py47
-rw-r--r--scripts/gdb/teachos/__init__.py8
-rw-r--r--scripts/gdb/teachos/dump_mb2i.py241
-rw-r--r--scripts/gdb/toolchain.py35
-rwxr-xr-xscripts/qemu-wrapper.sh30
527 files changed, 25229 insertions, 4128 deletions
diff --git a/.aiignore b/.aiignore
new file mode 100644
index 0000000..60193e2
--- /dev/null
+++ b/.aiignore
@@ -0,0 +1 @@
+*.img \ No newline at end of file
diff --git a/.clang-format b/.clang-format
index e54cb03..57204cc 100644
--- a/.clang-format
+++ b/.clang-format
@@ -55,23 +55,39 @@ DerivePointerAlignment: "false"
FixNamespaceComments: "true"
IncludeBlocks: Regroup
IncludeCategories:
- - Regex: 'kapi/[[:alnum:]._\/]+\.hpp'
+ # Platform Headers
+ - Regex: 'arch/[[:alnum:]._\/]+\.hpp'
Priority: 100
- - Regex: 'x86_64/[[:alnum:]._\/]+\.hpp'
- Priority: 110
- - Regex: '"[[:alnum:]._\/]+\.hpp"'
- Priority: 300
- - Regex: '<kstd/[[:alnum:]._\/]+>'
- Priority: 400
- - Regex: '<[[:alnum:]._\/]+\.hpp>'
+ # Kernel Headers
+ - Regex: 'kernel/[[:alnum:]._\/]+\.hpp'
+ Priority: 125
+ # KAPI Headers
+ - Regex: 'kapi/[[:alnum:]._\/]+\.hpp'
+ Priority: 150
+ # Library Headers
+ - Regex: 'acpi/[[:alnum:]._\/]+\.hpp'
+ Priority: 200
+ - Regex: 'elf/[[:alnum:]._\/]+\.hpp'
+ Priority: 210
+ - Regex: 'kstd/[[:alnum:]._\/]+(\.hpp)?'
+ Priority: 220
+ - Regex: 'multiboot2/[[:alnum:]._\/]+\.hpp'
+ Priority: 230
+ # Catch2 Headers
+ - Regex: 'catch2/[[:alnum:]._\/]+\.hpp'
Priority: 600
- - Regex: '<[[:alnum:]._]+(?!\.(h|hpp))>'
+ # Standard Headers
+ - Regex: '<[[:alnum:]._]+>'
Priority: 900
+ # Local Headers
+ - Regex: '".*"'
+ Priority: 10
IndentCaseLabels: "true"
IndentPPDirectives: None
IndentWidth: "2"
KeepEmptyLinesAtTheStartOfBlocks: "false"
Language: Cpp
+MainIncludeChar: "Any"
MaxEmptyLinesToKeep: "1"
NamespaceIndentation: All
PointerAlignment: Middle
diff --git a/.clang-tidy b/.clang-tidy
index e802dbd..dc76491 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -34,7 +34,7 @@ Checks:
- cppcoreguidelines-slicing
- cppcoreguidelines-use-enum-class
- cppcoreguidelines-virtual-class-destructor
-
+
- misc-definitions-in-headers
- misc-include-cleaner
- misc-no-recursion
@@ -55,16 +55,20 @@ Checks:
- readability-magic-numbers
CheckOptions:
+ cppcoreguidelines-avoid-do-while.IgnoreMacros: true
cppcoreguidelines-avoid-non-const-global-variables.AllowInternalLinkage: true
- modernize-use-std-print.ReplacementPrintFunction: 'kstd::print'
- modernize-use-std-print.ReplacementPrintlnFunction: 'kstd::println'
- modernize-use-std-print.PrintHeader: 'kstd/print'
+ modernize-use-std-print.ReplacementPrintFunction: "kstd::print"
+ modernize-use-std-print.ReplacementPrintlnFunction: "kstd::println"
+ modernize-use-std-print.PrintHeader: "kstd/print"
modernize-use-trailing-return-type.TransformLambdas: none
- readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10'
+ readability-magic-numbers.IgnoredIntegerValues: "1;2;3;4;5;6;7;10;15;20;25;30;3\
+ 5;40;45;50;60;70;80;90;100;200;300;400;255"
readability-magic-numbers.IgnorePowersOf2IntegerValues: true
readability-magic-numbers.IgnoreBitFieldsWidths: true
readability-magic-numbers.IgnoreTypeAliases: true
FormatStyle: file
-HeaderFilterRegex: '(.*/kstd/.*)|.*\.hpp'
-SystemHeaders: true \ No newline at end of file
+HeaderFilterRegex: "(.*/kstd/kstd/.*)|(arch|kernel|kapi)/.*\\.hpp"
+SystemHeaders: true
+RemovedArgs:
+ - -fcondition-coverage
diff --git a/.clangd b/.clangd
index e3c98ed..55f84ae 100644
--- a/.clangd
+++ b/.clangd
@@ -1,3 +1,8 @@
Diagnostics:
UnusedIncludes: Strict
- MissingIncludes: Strict \ No newline at end of file
+ MissingIncludes: Strict
+CompileFlags:
+ Remove:
+ - -fcondition-coverage
+Documentation:
+ CommentFormat: Doxygen
diff --git a/.devcontainer/x86-64/Containerfile b/.devcontainer/x86-64/Containerfile
new file mode 100644
index 0000000..eb03057
--- /dev/null
+++ b/.devcontainer/x86-64/Containerfile
@@ -0,0 +1,38 @@
+FROM registry.gitlab.ost.ch:45023/teachos/devcontainers/x86-64:16.1.0-3-py3.14
+
+RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install --yes --no-install-recommends \
+ acpica-tools \
+ clangd-22 \
+ clang-tidy-22 \
+ cmake \
+ g++-16 \
+ gcc-16 \
+ gdb \
+ git \
+ git-lfs \
+ grub2-common \
+ grub-pc \
+ libcatch2-dev \
+ locales \
+ mtools \
+ ninja-build \
+ python3-poetry \
+ qemu-system-x86 \
+ ssh \
+ wget \
+ xorriso \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
+ dpkg-reconfigure --frontend=noninteractive locales && \
+ update-locale LANG=en_US.UTF-8
+
+RUN update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-16 100 && \
+ update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-16 100 && \
+ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-16 100 && \
+ update-alternatives --install /usr/bin/gcc-ar gcc-ar /usr/bin/gcc-ar-16 100 && \
+ update-alternatives --install /usr/bin/gcc-nm gcc-nm /usr/bin/gcc-nm-16 100 && \
+ update-alternatives --install /usr/bin/gcc-ranlib gcc-ranlib /usr/bin/gcc-ranlib-16 100 && \
+ update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-16 100 && \
+ update-alternatives --install /usr/bin/clangd clangd /usr/bin/clangd-22 100 && \
+ update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-22 100
diff --git a/.devcontainer/x86-64/devcontainer.json b/.devcontainer/x86-64/devcontainer.json
index ec50ecb..dc70596 100644
--- a/.devcontainer/x86-64/devcontainer.json
+++ b/.devcontainer/x86-64/devcontainer.json
@@ -1,25 +1,29 @@
{
"name": "TeachOS on x86-64",
- "image": "registry.gitlab.ost.ch:45023/teachos/devcontainers/x86-64:15.2.0-2",
- "features": {
- "ghcr.io/devcontainers/features/git:1": {},
- "ghcr.io/devcontainers/features/git-lfs:1": {},
- "ghcr.io/devcontainers-extra/features/apt-packages:1": {
- "packages": "cmake,grub2-common,grub-pc,mtools,ninja-build,qemu-system-x86,ssh,xorriso"
- }
+ "build": {
+ "dockerfile": "Containerfile"
},
+ "remoteUser": "ubuntu",
"customizations": {
"vscode": {
"extensions": [
"basdp.language-gas-x86",
"gruntfuggly.todo-tree",
"llvm-vs-code-extensions.vscode-clangd",
+ "matepek.vscode-catch2-test-adapter",
"ms-vscode.cmake-tools",
"KylinIdeTeam.cppdebug",
- "zixuanwang.linkerscript"
+ "zixuanwang.linkerscript",
+ "ms-vscode.hexeditor"
]
}
},
- "remoteUser": "ubuntu",
- "updateRemoteUserUID": true
-}
+ "containerEnv": {
+ "DEBUGINFOD_URLS": "NOSUCHURL"
+ },
+ "remoteEnv": {
+ "LANG": "en_US.UTF-8",
+ "LC_ALL": "en_US.UTF-8",
+ "TERM": "xterm-256color"
+ }
+} \ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..219d96f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.img filter=lfs diff=lfs merge=lfs -text
+*.bin filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
index d575651..36f5e77 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,16 @@
-.venv/
+# Directories
.cache/
-
-.gdb_history
-
.devpod-internal/
-
+.venv/
/build
/docs/_build
+# Generated files
+.gdb_history
+*.pyc
+coverage.info
+desktop.ini
+devcontainer-lock.json
+
+# QEMU log files
qemu-*-*.log
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1c1548b..7551708 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,26 +1,64 @@
-.build_matrix: &build_matrix
- parallel:
- matrix:
- - PLATFORM: ["x86_64"]
- TYPE: ["dbg", "rel"]
+build:bht:
+ stage: build
+ image: registry.gitlab.ost.ch:45023/teachos/devcontainers/bht.ci:latest
+ script:
+ - cmake --preset bht
+ - cmake --build --preset bht-dbg 2>&1 | tee build_output.txt
+ - set -o pipefail
+ - python3 scripts/ci/parse_clang_tidy.py build_output.txt > code-quality-bht.json
+ artifacts:
+ paths:
+ - build/bht/
+ reports:
+ codequality: code-quality-bht.json
+ expire_in: 5 min
-build:
+build:bootable:
stage: build
- image: registry.gitlab.ost.ch:45023/teachos/devcontainers/x86-64:15.2.0-1
- before_script:
- - apt update
- - apt install -y cmake grub2-common grub-pc mtools ninja-build xorriso
+ image: registry.gitlab.ost.ch:45023/teachos/devcontainers/x86-64.ci:latest
script:
- cmake --preset $PLATFORM
- - cmake --build --preset $PLATFORM-$TYPE
- - cp build/bin/**/kernel.{dis,elf,sym,iso} .
+ - cmake --build --preset $PLATFORM-$TYPE 2>&1 | tee build_output.txt
+ - set -o pipefail
+ - python3 scripts/ci/parse_clang_tidy.py build_output.txt > code-quality-$PLATFORM-$TYPE.json
+ - cp build/${PLATFORM}/bin/**/kernel.{dis,elf,sym,iso} .
artifacts:
paths:
- kernel.dis
- kernel.elf
- kernel.sym
- kernel.iso
- <<: *build_matrix
+ reports:
+ codequality: code-quality-$PLATFORM-$TYPE.json
+ expire_in: 1 week
+
+ parallel:
+ matrix:
+ - PLATFORM: ["x86_64"]
+ TYPE: ["dbg", "rel"]
+
+test:bht:
+ stage: test
+ image: registry.gitlab.ost.ch:45023/teachos/devcontainers/bht.ci:latest
+ needs: ["build:bht"]
+ script:
+ - ctest --preset bht-dbg
+ - lcov --quiet --config-file .lcovrc --capture --directory $(pwd) --output-file coverage.info
+ - lcov --quiet --list coverage.info
+ - genhtml --quiet --prefix $(pwd) --output-directory coverage coverage.info
+ - gcovr --root . --cobertura-pretty --output coverage/cobertura-coverage.xml
+ after_script:
+ - echo "CoverageReport public URL - https://teachos.pages.ost.ch/-/kernel/-/jobs/$CI_JOB_ID/artifacts/coverage/index.html"
+ coverage: '/Total:\|\s*(\d+(?:\.\d+)?)\%/'
+ artifacts:
+ paths:
+ - coverage/
+ expire_in: 24 hours
+ reports:
+ coverage_report:
+ coverage_format: cobertura
+ path: coverage/cobertura-coverage.xml
+ junit: build/bht/**/junit.xml
license_check:
stage: .pre
diff --git a/.lazy.lua b/.lazy.lua
new file mode 100644
index 0000000..1c2df6e
--- /dev/null
+++ b/.lazy.lua
@@ -0,0 +1,13 @@
+return {
+ {
+ "nvim-neo-tree/neo-tree.nvim",
+ opts = {
+ filesystem = {
+ filtered_items = {
+ visible = false,
+ hide_gitignored = true,
+ },
+ },
+ }
+ }
+}
diff --git a/.lcovrc b/.lcovrc
new file mode 100644
index 0000000..0d0fdce
--- /dev/null
+++ b/.lcovrc
@@ -0,0 +1,7 @@
+exclude = /usr/include/*
+exclude = build/bht/_deps/*
+exclude = tests/*
+exclude = **.tests.cpp
+exclude = kapi/kapi/*
+
+ignore_errors = unused,empty,inconsistent \ No newline at end of file
diff --git a/.nvim.lua b/.nvim.lua
new file mode 100644
index 0000000..0748af9
--- /dev/null
+++ b/.nvim.lua
@@ -0,0 +1,169 @@
+local workspace_folder = vim.fn.getcwd()
+
+-- Formatting
+vim.g.autoformat = true
+vim.opt.fixeol = false
+
+-- Enable Doxygen
+vim.g.load_doxygen_syntax = true
+
+local function safe_require(module)
+ local ok, mod = pcall(require, module)
+ if not ok then return nil end
+ return mod
+end
+
+-- C++
+local default_clangd_config = vim.deepcopy(vim.lsp.config["clangd"]) or {}
+default_clangd_config.cmd = {
+ "clangd",
+ "--background-index",
+ "--clang-tidy",
+ "--header-insertion=iwyu",
+ "--completion-style=detailed",
+ string.format("--compile-commands-dir=%s/build", workspace_folder),
+ "--query-driver=**/x86_64-pc-elf-g++"
+}
+
+vim.lsp.config("clangd", default_clangd_config)
+
+vim.filetype.add({
+ pattern = {
+ [".*/include/kstd/.*"] = function(path, _)
+ if not path:match("%.[^/]+$") then
+ return "cpp"
+ end
+ end
+ }
+})
+
+-- File Browser
+local neo_tree = safe_require("neo-tree")
+if neo_tree then
+ local current_config = neo_tree.config or {}
+ local project_config = vim.tbl_deep_extend("force", current_config, {
+ nesting_rules = {
+ ['*.hpp'] = {
+ pattern = "(.*).hpp",
+ files = { "%1.cpp", "%1.test.cpp", "%1.tests.cpp" }
+ }
+ }
+ })
+ neo_tree.setup(project_config)
+end
+
+-- Debugging
+local dap = require("dap")
+local qemu_job_id = nil
+local qemu_bufnr = nil
+
+local function resolve_build_paths(artifact_path)
+ local base_path = string.gsub(artifact_path, "%.[%w]+$", "")
+ local build_type = artifact_path:match("bin/([^/]+)/") or "Debug"
+ return {
+ iso = base_path .. ".iso",
+ elf = base_path .. ".elf",
+ sym = base_path .. ".sym",
+ build_type = build_type,
+ }
+end
+
+local function launch_qemu(artifact_path, is_debug)
+ if qemu_job_id then
+ vim.fn.jobstop(qemu_job_id)
+ qemu_job_id = nil
+ end
+
+ local paths = resolve_build_paths(artifact_path)
+ local debug_flag = is_debug and "1" or "0"
+ local shell_cmd = string.format("%s/scripts/qemu-wrapper.sh '%s' '%s' '%s' '%s'",
+ workspace_folder, workspace_folder, paths.build_type, paths.iso, debug_flag)
+
+ vim.cmd("botright vsplit | enew")
+ qemu_bufnr = vim.api.nvim_get_current_buf()
+ vim.api.nvim_buf_set_name(qemu_bufnr, "TeachOS QEMU (" .. paths.build_type .. ")")
+ vim.opt_local.number = false
+ vim.opt_local.relativenumber = false
+ vim.opt_local.signcolumn = "no"
+
+ qemu_job_id = vim.fn.termopen(shell_cmd, {
+ on_exit = function() qemu_job_id = nil end
+ })
+
+ vim.cmd("wincmd p")
+end
+
+dap.adapters.teachos_qemu_mi = function(callback, config)
+ local artifact_path = config.program
+ if not artifact_path then
+ vim.notify("Fatal: No artifact path resolved by CMake", vim.log.levels.ERROR)
+ return
+ end
+
+ launch_qemu(artifact_path, true)
+
+ local paths = resolve_build_paths(artifact_path)
+ config.setupCommands = {
+ { description = "Enable pretty-printing for gdb", text = "-enable-pretty-printing" },
+ { description = "Load code", text = string.format("-file-exec-file %s", paths.elf) },
+ { description = "Load symbols", text = string.format("-file-exec-and-symbols %s", paths.sym) },
+ }
+ config.program = paths.sym
+
+ vim.defer_fn(function()
+ callback({
+ type = "executable",
+ command = vim.fn.stdpath("data") .. "/mason/bin/OpenDebugAD7",
+ id = "cppdbg",
+ })
+ end, 500)
+end
+
+local function teardown_qemu()
+ if qemu_job_id then
+ vim.fn.jobstop(qemu_job_id)
+ qemu_job_id = nil
+ end
+
+ if qemu_bufnr and vim.api.nvim_buf_is_valid(qemu_bufnr) then
+ vim.api.nvim_buf_delete(qemu_bufnr, { force = true })
+ qemu_bufnr = nil
+ end
+end
+
+dap.listeners.after.event_terminated["teachos_qemu_teardown"] = teardown_qemu
+dap.listeners.after.event_exited["teachos_qemu_teardown"] = teardown_qemu
+dap.listeners.after.disconnect["teachos_qemu_teardown"] = teardown_qemu
+
+local cmake_tools = safe_require("cmake-tools")
+if cmake_tools then
+ require("cmake-tools").setup({
+ cmake_compile_commands_options = {
+ action = "copy",
+ target = string.format("%s/build", workspace_folder),
+ },
+ cmake_dap_configuration = {
+ name = "(gdb) QEMU MI Attach",
+ type = "teachos_qemu_mi",
+ request = "launch",
+ MIMode = "gdb",
+ cwd = workspace_folder,
+ miDebuggerServerAddress = "localhost:1234",
+ miDebuggerPath = "x86_64-pc-elf-gdb",
+ stopAtEntry = true,
+ }
+ })
+
+ vim.api.nvim_create_user_command("TeachOSRun", function()
+ local cmake = safe_require("cmake-tools")
+ if not cmake then return end
+ local is_ready, target = pcall(cmake.get_launch_target_path)
+ if is_ready and target then
+ launch_qemu(target, false)
+ else
+ vim.notify("Fatal: Cannot determine CMake target path.", vim.log.levels.ERROR)
+ end
+ end, {})
+
+ vim.keymap.set('n', '<S-F5>', '<cmd>TeachOSRun<CR>', { noremap = true, desc = "Run TeachOS QEMU (No Debug)" })
+end
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index ea07f1b..fd40a4c 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -3,6 +3,7 @@
"basdp.language-gas-x86",
"gruntfuggly.todo-tree",
"llvm-vs-code-extensions.vscode-clangd",
+ "matepek.vscode-catch2-test-adapter",
"ms-vscode.cmake-tools",
"KylinIdeTeam.cppdebug",
"zixuanwang.linkerscript"
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 6478c96..90c7520 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -25,6 +25,14 @@
{
"description": "Load symbols",
"text": "-file-exec-and-symbols ${command:cmake.buildDirectory}/bin/${command:cmake.buildType}/kernel.sym"
+ },
+ {
+ "description": "Load teachos python helpers",
+ "text": "source ${workspaceFolder}/scripts/gdb/teachos.py"
+ },
+ {
+ "description": "Load toolchain python helpers",
+ "text": "source ${workspaceFolder}/scripts/gdb/toolchain.py"
}
]
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 08f9457..d62742c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,21 +1,29 @@
{
"cmake.useCMakePresets": "always",
"cmake.options.statusBarVisibility": "visible",
-
+ "cmake.ctest.testExplorerIntegrationEnabled": false,
+ "cmake.copyCompileCommands": "${workspaceFolder}/build/compile_commands.json",
"clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}/build",
- "--query-driver=**/x86_64-pc-elf-g++"
+ "--query-driver=**/x86_64-pc-elf-g++",
+ "--completion-style=detailed",
+ "--background-index",
+ "--clang-tidy",
+ "--header-insertion=iwyu"
],
-
+ "explorer.fileNesting.enabled": true,
+ "explorer.fileNesting.expand": false,
+ "explorer.fileNesting.patterns": {
+ "*.hpp": "${capture}.cpp, ${capture}.test.cpp, ${capture}.S",
+ "*": "${capture}.cpp, ${capture}.test.cpp, ${capture}.S"
+ },
"files.associations": {
- "**/kstd/include/kstd/**": "cpp",
+ "**/kstd/kstd/**": "cpp",
},
-
"[cpp]": {
"editor.formatOnSave": true,
"editor.tabSize": 2,
},
-
"[gas]": {
"editor.rulers": [
80
@@ -23,12 +31,16 @@
},
"cSpell.words": [
"acpi",
+ "APIC",
"bugprone",
"cppcoreguidelines",
"crtc",
"crtp",
"efer",
+ "FACS",
"functors",
+ "hhdm",
+ "idtr",
"initializable",
"interprocedural",
"invlpg",
@@ -36,9 +48,12 @@
"iwyu",
"kapi",
"kstd",
+ "lapic",
+ "madt",
"malloc",
"memcmp",
"memset",
+ "mmio",
"multiboot",
"nodiscard",
"nolintnextline",
@@ -47,11 +62,35 @@
"raii",
"rdmsr",
"RSDP",
+ "rsdt",
"rvalues",
"stringview",
"sysret",
"teachos",
"undelegated",
- "wrmsr"
- ]
+ "wrmsr",
+ "xsdp",
+ "xsdt"
+ ],
+ "testMate.cpp.debug.configTemplate": {
+ "type": "cppdbg",
+ "MIMode": "gdb",
+ "program": "${exec}",
+ "args": "${argsArray}",
+ "cwd": "${cwd}",
+ "env": "${envObject}",
+ "environment": "${envObjArray}",
+ "sourceFileMap": "${sourceFileMapObj}",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ },
+ {
+ "description": "Load teachos python helpers",
+ "text": "source ${workspaceFolder}/scripts/gdb/teachos.py"
+ }
+ ],
+ },
} \ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index e07afd2..1aa53d9 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -6,21 +6,10 @@
"command": "${workspaceFolder}/scripts/qemu-wrapper.sh",
"type": "shell",
"args": [
- "-s",
- "-m",
- "32M",
- "-machine",
- "q35",
- "-display",
- "curses",
- "-debugcon",
- "file:${workspaceFolder}/qemu-debugcon-${command:cmake.buildType}.log",
- "-no-reboot",
- "-d",
- "int,cpu_reset",
- "-cdrom",
+ "${workspaceFolder}",
+ "${command:cmake.buildType}",
"${command:cmake.buildDirectory}/bin/${command:cmake.buildType}/kernel.iso",
- "2>${workspaceFolder}/qemu-stderr-${command:cmake.buildType}.log"
+ "1"
],
"isBackground": true,
"presentation": {
@@ -45,20 +34,13 @@
},
{
"label": "QEMU",
- "command": "qemu-system-x86_64",
+ "command": "${workspaceFolder}/scripts/qemu-wrapper.sh",
"type": "shell",
"args": [
- "-m",
- "32M",
- "-machine",
- "q35",
- "-display",
- "curses",
- "-debugcon",
- "file:${workspaceFolder}/qemu-debugcon-${command:cmake.buildType}.log",
- "-cdrom",
+ "${workspaceFolder}",
+ "${command:cmake.buildType}",
"${command:cmake.buildDirectory}/bin/${command:cmake.buildType}/kernel.iso",
- "2>${workspaceFolder}/qemu-stderr-${command:cmake.buildType}.log"
+ "0"
],
"isBackground": true,
"presentation": {
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2343c77..f7c7fe8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,29 @@ set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/Modules")
include("ElfTransformations")
include("GenerateBootableIso")
+include("CTest")
+include("EnableCoverage")
+
+#[============================================================================[
+# External Dependencies
+#]============================================================================]
+
+include("FetchContent")
+
+if (BUILD_TESTING)
+ FetchContent_Declare(
+ "Catch2"
+ URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.7.1.tar.gz"
+ URL_HASH "SHA256=c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c"
+ EXCLUDE_FROM_ALL
+ FIND_PACKAGE_ARGS
+ )
+
+ FetchContent_MakeAvailable("Catch2")
+
+ add_compile_definitions("CATCH_CONFIG_NO_COUNTER")
+ set(CATCH_TEST_ARGS "EXTRA_ARGS" "--reporter" "junit::out=junit.xml" "--reporter" "console::out=-::colour-mode=ansi")
+endif()
#[============================================================================[
# Global Build System Options
@@ -23,6 +46,10 @@ option(TEACHOS_GENERATE_DOCS "Generate documentation during build" ON)
# Global Build System Configuration
#]============================================================================]
+if(POLICY CMP0209)
+ cmake_policy(SET CMP0209 NEW)
+endif()
+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION YES)
@@ -42,7 +69,12 @@ add_compile_options(
# Global Linting Configuration
#]============================================================================]
-find_program(CLANG_TIDY_EXE "clang-tidy")
+find_program(CLANG_TIDY_EXE NAMES
+ "clang-tidy-23"
+ "clang-tidy-22"
+ "clang-tidy-21"
+ "clang-tidy"
+)
if(CLANG_TIDY_EXE AND TEACHOS_ENABLE_LINTING)
set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY_EXE}")
@@ -64,10 +96,26 @@ if(Doxygen_FOUND AND TEACHOS_GENERATE_DOCS)
endif()
#[============================================================================[
+# Build Host Testing
+#]============================================================================]
+
+if(BUILD_TESTING)
+ if(TARGET "Catch2" AND TARGET "Catch2WithMain")
+ set_target_properties("Catch2" "Catch2WithMain" PROPERTIES
+ C_CLANG_TIDY ""
+ CXX_CLANG_TIDY ""
+ )
+ endif()
+endif()
+
+#[============================================================================[
# Global Targets
#]============================================================================]
-add_subdirectory("arch/${CMAKE_SYSTEM_PROCESSOR}")
+if(CMAKE_CROSSCOMPILING)
+ add_subdirectory("arch/${CMAKE_SYSTEM_PROCESSOR}")
+endif()
+
add_subdirectory("kapi")
+add_subdirectory("kernel")
add_subdirectory("libs")
-add_subdirectory("kernel") \ No newline at end of file
diff --git a/CMakePresets.json b/CMakePresets.json
index fd88d3c..0e5dd88 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -4,7 +4,7 @@
{
"name": "base",
"hidden": true,
- "binaryDir": "${sourceDir}/build",
+ "binaryDir": "${sourceDir}/build/${presetName}",
"generator": "Ninja Multi-Config",
"cacheVariables": {
"CMAKE_CONFIGURATION_TYPES": "Debug;MinSizeRel",
@@ -14,13 +14,17 @@
},
{
"name": "x86_64",
+ "description": "Target x86-64",
"inherits": "base",
"toolchainFile": "cmake/Platforms/x86_64.cmake",
"cacheVariables": {
- "TEACHOS_PLATFORM_FRAME_SIZE": "4096",
- "TEACHOS_PLATFORM_PAGE_SIZE": "4096",
- "TEACHOS_PLATFORM_PAGING_LEVELS": "4"
+ "BUILD_TESTING": false
}
+ },
+ {
+ "name": "bht",
+ "inherits": "base",
+ "description": "Build-host Testing"
}
],
"buildPresets": [
@@ -33,6 +37,17 @@
"name": "x86_64-rel",
"configurePreset": "x86_64",
"configuration": "MinSizeRel"
+ },
+ {
+ "name": "bht-dbg",
+ "configurePreset": "bht",
+ "configuration": "Debug"
+ }
+ ],
+ "testPresets": [
+ {
+ "name": "bht-dbg",
+ "configurePreset": "bht"
}
]
-}
+} \ No newline at end of file
diff --git a/README.rst b/README.rst
index 716013d..c850c7f 100644
--- a/README.rst
+++ b/README.rst
@@ -45,4 +45,16 @@ The default build target generates a bootable image.
On x86-64 for example, this image takes the form of a bootable, grub2 based ISO image.
These images are designed to be booted in QEMU, and should theoretically also be bootable on real hardware.
However, note that not warranty is provided, and the kernel code may irreparably destroy any physical hardware if booted on a real system.
-The VSCodium IDE configuration provides a launch task using QEMU, available for debugging (via F5) and direct launch as a task. \ No newline at end of file
+The VSCodium IDE configuration provides a launch task using QEMU, available for debugging (via F5) and direct launch as a task.
+
+Notes for Development under Windows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+While this repository and the devcontainer can be used from Windows, significant performance issues may occur.
+To reduce these issues, you can either set up a Linux VM for development, or, if that is not preferred, clone the repository inside WSL and open it from there with Visual Studio Code.
+
+| ``git clone <repo-url>``
+| ``cd <repo-folder>``
+| ``code .``
+
+If you use tools such as Git Extensions or GitHub Desktop, access the repository via the WSL network path, for example ``\\wsl.localhost\<distro>\<path-to-repo>``.
diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt
index 41932c9..20a48f9 100644
--- a/arch/x86_64/CMakeLists.txt
+++ b/arch/x86_64/CMakeLists.txt
@@ -1,44 +1,69 @@
+#[============================================================================[
+# Library
+#]============================================================================]
+
add_library("x86_64" OBJECT)
-add_library("os::arch" ALIAS "x86_64")
+add_library("arch::lib" ALIAS "x86_64")
target_include_directories("x86_64" PUBLIC
- "include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries("x86_64" PUBLIC
- "os::kapi"
- "libs::multiboot2"
+ "kapi::lib"
+ "multiboot2::lib"
)
target_sources("x86_64" PRIVATE
- # Platform-dependent KAPI implementation
+ "kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
+ "kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
+)
+
+target_sources("x86_64" PRIVATE
+ # CPU Initialization
+ "arch/cpu/initialization.cpp"
+ "arch/cpu/interrupts.cpp"
+ "arch/cpu/interrupts.S"
+
+ # Bus Initialization
+ "arch/bus/isa.cpp"
# Low-level bootstrap
- "src/boot/boot32.S"
- "src/boot/entry64.s"
- "src/boot/initialize_runtime.cpp"
- "src/boot/multiboot.s"
+ "arch/boot/boot32.S"
+ "arch/boot/entry64.s"
+ "arch/boot/initialize_runtime.cpp"
+ "arch/boot/multiboot.s"
# Debug interfaces
- "src/debug/qemu_output.cpp"
+ "arch/debug/qemu_output.cpp"
+
+ # Devices
+ "arch/devices/init.cpp"
+ "arch/devices/legacy_pit.cpp"
+ "arch/devices/local_apic.cpp"
# Memory management
- "src/memory/kernel_mapper.cpp"
- "src/memory/higher_half_mapper.cpp"
- "src/memory/mmu.cpp"
- "src/memory/page_table.cpp"
- "src/memory/region_allocator.cpp"
+ "arch/memory/kernel_mapper.cpp"
+ "arch/memory/higher_half_mapper.cpp"
+ "arch/memory/mmu.cpp"
+ "arch/memory/page_table.cpp"
+ "arch/memory/region_allocator.cpp"
# VGA text mode
- "src/vga/text/buffer.cpp"
- "src/vga/text/device.cpp"
+ "arch/vga/text/buffer.cpp"
+ "arch/vga/text/device.cpp"
)
-file(GLOB_RECURSE ARCH_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "include/**.hpp")
+file(GLOB_RECURSE ARCH_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "include/**.hpp"
+)
target_sources("x86_64" PUBLIC
FILE_SET HEADERS
@@ -46,7 +71,23 @@ target_sources("x86_64" PUBLIC
FILES ${ARCH_HEADERS}
)
+target_include_directories("x86_64" PUBLIC
+ "include"
+)
+
+target_link_libraries("x86_64" PUBLIC
+ "acpi::lib"
+ "elf::lib"
+ "kapi::lib"
+ "kstd::lib"
+ "multiboot2::lib"
+)
+
set(KERNEL_LINKER_SCRIPT
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/kernel.ld"
PARENT_SCOPE
)
+
+set_target_properties("x86_64" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
+)
diff --git a/arch/x86_64/include/arch/boot/boot.hpp b/arch/x86_64/arch/boot/boot.hpp
index 3a598f5..7df61c4 100644
--- a/arch/x86_64/include/arch/boot/boot.hpp
+++ b/arch/x86_64/arch/boot/boot.hpp
@@ -31,7 +31,7 @@
// clang-format on
#else
-#include "kapi/boot.hpp" // IWYU pragma: export
+#include <kapi/boot.hpp> // IWYU pragma: export
#include <multiboot2/information.hpp>
diff --git a/arch/x86_64/src/boot/boot32.S b/arch/x86_64/arch/boot/boot32.S
index 1c2fdaf..e6fcd85 100644
--- a/arch/x86_64/src/boot/boot32.S
+++ b/arch/x86_64/arch/boot/boot32.S
@@ -1,4 +1,4 @@
-#include "arch/boot/boot.hpp"
+#include <arch/boot/boot.hpp>
/**
* @brief Uninitialized data for the bootstrapping process.
diff --git a/arch/x86_64/src/boot/entry64.s b/arch/x86_64/arch/boot/entry64.s
index 29fb778..29fb778 100644
--- a/arch/x86_64/src/boot/entry64.s
+++ b/arch/x86_64/arch/boot/entry64.s
diff --git a/arch/x86_64/src/boot/initialize_runtime.cpp b/arch/x86_64/arch/boot/initialize_runtime.cpp
index b08c13c..b08c13c 100644
--- a/arch/x86_64/src/boot/initialize_runtime.cpp
+++ b/arch/x86_64/arch/boot/initialize_runtime.cpp
diff --git a/arch/x86_64/include/arch/boot/ld.hpp b/arch/x86_64/arch/boot/ld.hpp
index 988723d..988723d 100644
--- a/arch/x86_64/include/arch/boot/ld.hpp
+++ b/arch/x86_64/arch/boot/ld.hpp
diff --git a/arch/x86_64/src/boot/multiboot.s b/arch/x86_64/arch/boot/multiboot.s
index 7ccca56..37d8afe 100644
--- a/arch/x86_64/src/boot/multiboot.s
+++ b/arch/x86_64/arch/boot/multiboot.s
@@ -18,6 +18,13 @@ multiboot_header_start:
.long 3
.Lflags_end:
.align 8
+.Linformation_request_start:
+ .word 1
+ .word 0
+ .long .Linformation_request_end - .Linformation_request_start
+ .long 3
+.Linformation_request_end:
+.align 8
.Lend_start:
.word 0
.word 0
diff --git a/arch/x86_64/arch/bus/isa.cpp b/arch/x86_64/arch/bus/isa.cpp
new file mode 100644
index 0000000..f6cc72d
--- /dev/null
+++ b/arch/x86_64/arch/bus/isa.cpp
@@ -0,0 +1,14 @@
+#include <arch/bus/isa.hpp>
+
+#include <kapi/devices.hpp>
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ isa::isa(std::size_t major)
+ : kapi::devices::bus{major, 0, "isa"}
+ {}
+
+} // namespace arch::bus \ No newline at end of file
diff --git a/arch/x86_64/arch/bus/isa.hpp b/arch/x86_64/arch/bus/isa.hpp
new file mode 100644
index 0000000..e56f56a
--- /dev/null
+++ b/arch/x86_64/arch/bus/isa.hpp
@@ -0,0 +1,18 @@
+#ifndef TEACHOS_X86_64_BUS_ISA_HPP
+#define TEACHOS_X86_64_BUS_ISA_HPP
+
+#include <kapi/devices/bus.hpp>
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ struct isa final : public kapi::devices::bus
+ {
+ isa(std::size_t major);
+ };
+
+} // namespace arch::bus
+
+#endif // TEACHOS_X86_64_BUS_ISA_HPP
diff --git a/arch/x86_64/include/arch/cpu/control_register.hpp b/arch/x86_64/arch/cpu/control_register.hpp
index fafbfc7..9cedc35 100644
--- a/arch/x86_64/include/arch/cpu/control_register.hpp
+++ b/arch/x86_64/arch/cpu/control_register.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP
#define TEACHOS_X86_64_CPU_CONTROL_REGISTERS_HPP
-// IWYU pragma: private, include "arch/cpu/registers.hpp"
+// IWYU pragma: private, include <arch/cpu/registers.hpp>
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <kstd/ext/bitfield_enum>
diff --git a/arch/x86_64/include/arch/cpu/global_descriptor_table.hpp b/arch/x86_64/arch/cpu/global_descriptor_table.hpp
index 402c87d..b17c509 100644
--- a/arch/x86_64/include/arch/cpu/global_descriptor_table.hpp
+++ b/arch/x86_64/arch/cpu/global_descriptor_table.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_X86_64_GLOBAL_DESCRIPTOR_TABLE_HPP
#define TEACHOS_X86_64_GLOBAL_DESCRIPTOR_TABLE_HPP
-#include "kapi/memory.hpp"
+#include <arch/cpu/segment_descriptor.hpp>
-#include "arch/cpu/segment_descriptor.hpp"
+#include <kapi/memory.hpp>
#include <algorithm>
#include <array>
diff --git a/arch/x86_64/arch/cpu/initialization.cpp b/arch/x86_64/arch/cpu/initialization.cpp
new file mode 100644
index 0000000..1be9c82
--- /dev/null
+++ b/arch/x86_64/arch/cpu/initialization.cpp
@@ -0,0 +1,164 @@
+#include <arch/cpu/initialization.hpp>
+
+#include <arch/cpu/global_descriptor_table.hpp>
+#include <arch/cpu/interrupts.hpp>
+#include <arch/cpu/legacy_pic.hpp>
+#include <arch/cpu/segment_descriptor.hpp>
+#include <arch/cpu/task_state_segment.hpp>
+
+#include <kstd/print>
+
+#include <bit>
+#include <cstdint>
+
+namespace arch::cpu
+{
+
+ namespace
+ {
+ constexpr auto gdt_null_descriptor = segment_descriptor{};
+
+ constexpr auto gdt_kernel_code_descriptor = segment_descriptor{
+ .limit_low = 0xffff,
+ .base_low = 0,
+ .accessed = false,
+ .read_write = false,
+ .direction_or_conforming = false,
+ .executable = true,
+ .type = segment_type::code_or_data,
+ .privilege_level = 0,
+ .present = true,
+ .limit_high = 0xf,
+ .long_mode = true,
+ .protected_mode = false,
+ .granularity = segment_granularity::page,
+ .base_high = 0,
+ };
+
+ constexpr auto gdt_kernel_data_descriptor = segment_descriptor{
+ .limit_low = 0xffff,
+ .base_low = 0,
+ .accessed = false,
+ .read_write = true,
+ .direction_or_conforming = false,
+ .executable = false,
+ .type = segment_type::code_or_data,
+ .privilege_level = 0,
+ .present = true,
+ .limit_high = 0xf,
+ .long_mode = false,
+ .protected_mode = true,
+ .granularity = segment_granularity::page,
+ .base_high = 0,
+ };
+
+ constexpr auto gdt_user_code_descriptor = segment_descriptor{
+ .limit_low = 0xffff,
+ .base_low = 0,
+ .accessed = false,
+ .read_write = false,
+ .direction_or_conforming = false,
+ .executable = true,
+ .type = segment_type::code_or_data,
+ .privilege_level = 3,
+ .present = true,
+ .limit_high = 0xf,
+ .long_mode = true,
+ .protected_mode = false,
+ .granularity = segment_granularity::page,
+ .base_high = 0,
+ };
+
+ constexpr auto gdt_user_data_descriptor = segment_descriptor{
+ .limit_low = 0xffff,
+ .base_low = 0,
+ .accessed = false,
+ .read_write = true,
+ .direction_or_conforming = false,
+ .executable = false,
+ .type = segment_type::code_or_data,
+ .privilege_level = 3,
+ .present = true,
+ .limit_high = 0xf,
+ .long_mode = false,
+ .protected_mode = false,
+ .granularity = segment_granularity::page,
+ .base_high = 0,
+ };
+
+ constexpr auto make_tss_descriptor(task_state_segment const * tss_ptr) -> system_segment_descriptor
+ {
+ auto const address = std::bit_cast<std::uintptr_t>(tss_ptr);
+ auto const limit = sizeof(task_state_segment) - 1;
+
+ return system_segment_descriptor{
+ {
+ .limit_low = limit & 0xffff, // NOLINT(readability-magic-numbers)
+ .base_low = address & 0xffffff, // NOLINT(readability-magic-numbers)
+ .accessed = false,
+ .read_write = false,
+ .direction_or_conforming = false,
+ .executable = false,
+ .type = segment_type::system,
+ .privilege_level = 0,
+ .present = true,
+ .limit_high = (limit >> 16) & 0xf, // NOLINT(readability-magic-numbers)
+ .long_mode = false,
+ .protected_mode = false,
+ .granularity = segment_granularity::byte,
+ .base_high = (address >> 24) & 0xff, // NOLINT(readability-magic-numbers)
+ },
+ (address >> 32) & 0xffff'ffff, // NOLINT(readability-magic-numbers)
+ };
+ }
+ } // namespace
+
+ auto initialize_descriptors() -> void
+ {
+ auto static tss = task_state_segment{};
+ auto static tss_descriptor = make_tss_descriptor(&tss);
+
+ auto static gdt = global_descriptor_table{
+ gdt_null_descriptor, gdt_kernel_code_descriptor, gdt_kernel_data_descriptor,
+ gdt_user_code_descriptor, gdt_user_data_descriptor, tss_descriptor,
+ };
+
+ kstd::println("[x86_64:SYS] Reloading Global Descriptor Table.");
+ gdt.load(1, 2);
+
+ kstd::println("[x86_64:SYS] Initializing Interrupt Descriptor Table.");
+ auto static idt = interrupt_descriptor_table{};
+ idt.load();
+ }
+
+ auto initialize_legacy_interrupts() -> void
+ {
+ constexpr auto pic_init_command = std::uint8_t{0x11};
+ constexpr auto pic_master_offset = std::uint8_t{0x20};
+ constexpr auto pic_slave_offset = std::uint8_t{0x28};
+ constexpr auto pic_cascade_address = std::uint8_t{0x04};
+ constexpr auto pic_cascade_slave_identity = std::uint8_t{0x02};
+ constexpr auto pic_use_8086_mode = std::uint8_t{0x01};
+ constexpr auto pic_master_mask = std::uint8_t{0x00};
+ constexpr auto pic_slave_mask = std::uint8_t{0x00};
+
+ pic_master_control_port::write(pic_init_command);
+ pic_slave_control_port::write(pic_init_command);
+
+ pic_master_data_port::write(pic_master_offset);
+ pic_slave_data_port::write(pic_slave_offset);
+
+ pic_master_data_port::write(pic_cascade_address);
+ pic_slave_data_port::write(pic_cascade_slave_identity);
+
+ pic_master_data_port::write(pic_use_8086_mode);
+ pic_slave_data_port::write(pic_use_8086_mode);
+
+ pic_master_data_port::write(pic_master_mask);
+ pic_slave_data_port::write(pic_slave_mask);
+
+ pic_master_data_port::write(pic_master_mask);
+ pic_slave_data_port::write(pic_slave_mask);
+ }
+
+} // namespace arch::cpu
diff --git a/arch/x86_64/arch/cpu/initialization.hpp b/arch/x86_64/arch/cpu/initialization.hpp
new file mode 100644
index 0000000..564c544
--- /dev/null
+++ b/arch/x86_64/arch/cpu/initialization.hpp
@@ -0,0 +1,12 @@
+#ifndef TEACHOS_ARCH_X86_64_CPU_INITIALIZATION_HPP
+#define TEACHOS_ARCH_X86_64_CPU_INITIALIZATION_HPP
+
+namespace arch::cpu
+{
+ auto initialize_descriptors() -> void;
+
+ auto initialize_legacy_interrupts() -> void;
+
+} // namespace arch::cpu
+
+#endif
diff --git a/arch/x86_64/arch/cpu/interrupts.S b/arch/x86_64/arch/cpu/interrupts.S
new file mode 100644
index 0000000..e59bdd2
--- /dev/null
+++ b/arch/x86_64/arch/cpu/interrupts.S
@@ -0,0 +1,112 @@
+.altmacro
+
+.macro ISR_WITHOUT_ERROR_CODE vector
+ .global isr\vector
+ isr\vector:
+ pushq $0
+ pushq $\vector
+ jmp common_interrupt_handler
+.endm
+
+.macro ISR_WITH_ERROR_CODE vector
+ .global isr\vector
+ isr\vector:
+ pushq $\vector
+ jmp common_interrupt_handler
+.endm
+
+.macro ISR_TABLE_ENTRY vector
+ .quad isr\vector
+.endm
+
+.section .rodata
+.global isr_stub_table
+.align 16
+
+isr_stub_table:
+.set i, 0
+.rept 256
+ ISR_TABLE_ENTRY %i
+ .set i, i + 1
+.endr
+
+
+.section .text
+
+common_interrupt_handler:
+ push %rax
+ push %rbx
+ push %rcx
+ push %rdx
+ push %rbp
+ push %rsi
+ push %rdi
+ push %r8
+ push %r9
+ push %r10
+ push %r11
+ push %r12
+ push %r13
+ push %r14
+ push %r15
+
+ mov %rsp, %rdi
+ call interrupt_dispatch
+
+ pop %r15
+ pop %r14
+ pop %r13
+ pop %r12
+ pop %r11
+ pop %r10
+ pop %r9
+ pop %r8
+ pop %rdi
+ pop %rsi
+ pop %rbp
+ pop %rdx
+ pop %rcx
+ pop %rbx
+ pop %rax
+
+ add $16, %rsp
+ iretq
+
+ISR_WITHOUT_ERROR_CODE 0
+ISR_WITHOUT_ERROR_CODE 1
+ISR_WITHOUT_ERROR_CODE 2
+ISR_WITHOUT_ERROR_CODE 3
+ISR_WITHOUT_ERROR_CODE 4
+ISR_WITHOUT_ERROR_CODE 5
+ISR_WITHOUT_ERROR_CODE 6
+ISR_WITHOUT_ERROR_CODE 7
+ISR_WITH_ERROR_CODE 8
+ISR_WITHOUT_ERROR_CODE 9
+ISR_WITH_ERROR_CODE 10
+ISR_WITH_ERROR_CODE 11
+ISR_WITH_ERROR_CODE 12
+ISR_WITH_ERROR_CODE 13
+ISR_WITH_ERROR_CODE 14
+ISR_WITHOUT_ERROR_CODE 15
+ISR_WITHOUT_ERROR_CODE 16
+ISR_WITH_ERROR_CODE 17
+ISR_WITHOUT_ERROR_CODE 18
+ISR_WITHOUT_ERROR_CODE 19
+ISR_WITHOUT_ERROR_CODE 20
+ISR_WITH_ERROR_CODE 21
+ISR_WITHOUT_ERROR_CODE 22
+ISR_WITHOUT_ERROR_CODE 23
+ISR_WITHOUT_ERROR_CODE 24
+ISR_WITHOUT_ERROR_CODE 25
+ISR_WITHOUT_ERROR_CODE 26
+ISR_WITHOUT_ERROR_CODE 27
+ISR_WITHOUT_ERROR_CODE 28
+ISR_WITH_ERROR_CODE 29
+ISR_WITH_ERROR_CODE 30
+ISR_WITHOUT_ERROR_CODE 31
+
+.set i, 32
+.rept 256 - 32
+ ISR_WITHOUT_ERROR_CODE %i
+ .set i, i + 1
+.endr
diff --git a/arch/x86_64/arch/cpu/interrupts.cpp b/arch/x86_64/arch/cpu/interrupts.cpp
new file mode 100644
index 0000000..f40422f
--- /dev/null
+++ b/arch/x86_64/arch/cpu/interrupts.cpp
@@ -0,0 +1,203 @@
+#include <arch/cpu/interrupts.hpp>
+
+#include <arch/cpu/legacy_pic.hpp>
+#include <arch/cpu/segment_selector.hpp>
+
+#include <kapi/cpu.hpp>
+#include <kapi/interrupts.hpp>
+#include <kapi/memory.hpp>
+
+#include <kstd/print>
+
+#include <cstdint>
+
+namespace arch::cpu
+{
+
+ namespace
+ {
+ enum struct exception
+ {
+ divide_error,
+ debug_exception,
+ non_maskable_interrupt,
+ breakpoint,
+ overflow,
+ bound_range_exceeded,
+ invalid_opcode,
+ device_not_available,
+ double_fault,
+ coprocessor_segment_overrun,
+ invalid_tss,
+ segment_not_present,
+ stack_segment_fault,
+ general_protection_fault,
+ page_fault,
+ x87_fpu_floating_point_error = 16,
+ alignment_check,
+ machine_check,
+ simd_floating_point_error,
+ virtualization_exception,
+ control_protection_exception,
+ hypervisor_injection_exception = 28,
+ vmm_communication_exception,
+ security_exception,
+ };
+
+ constexpr auto number_of_exception_vectors = 32u;
+
+ constexpr auto pic_master_irq_start = 0x20;
+ constexpr auto pic_master_irq_end = pic_master_irq_start + 8;
+ constexpr auto pic_slave_irq_start = pic_master_irq_end;
+
+ constexpr auto to_exception_type(exception e)
+ {
+ switch (e)
+ {
+ case exception::divide_error:
+ case exception::x87_fpu_floating_point_error:
+ case exception::simd_floating_point_error:
+ return kapi::cpu::exception::type::arithmetic_error;
+ case exception::breakpoint:
+ return kapi::cpu::exception::type::breakpoint;
+ case exception::invalid_opcode:
+ return kapi::cpu::exception::type::illegal_instruction;
+ case exception::stack_segment_fault:
+ return kapi::cpu::exception::type::memory_access_fault;
+ case exception::general_protection_fault:
+ return kapi::cpu::exception::type::privilege_violation;
+ case exception::page_fault:
+ return kapi::cpu::exception::type::page_fault;
+ case exception::alignment_check:
+ return kapi::cpu::exception::type::alignment_fault;
+ default:
+ return kapi::cpu::exception::type::unknown;
+ }
+ }
+
+ constexpr auto has_error_code(exception e)
+ {
+ switch (e)
+ {
+ case exception::double_fault:
+ case exception::invalid_tss:
+ case exception::segment_not_present:
+ case exception::stack_segment_fault:
+ case exception::general_protection_fault:
+ case exception::page_fault:
+ case exception::alignment_check:
+ case exception::control_protection_exception:
+ case exception::security_exception:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ auto dispatch_exception(interrupt_frame * frame) -> bool
+ {
+ auto type = to_exception_type(static_cast<exception>(frame->interrupt.number));
+ auto fault_address = kapi::memory::linear_address{};
+ auto instruction_pointer = frame->cpu_saved.rip;
+
+ switch (type)
+ {
+ case kapi::cpu::exception::type::page_fault:
+ {
+ asm volatile("mov %%cr2, %0" : "=r"(fault_address));
+ auto present = (frame->interrupt.error_code & 0x1) != 0;
+ auto write = (frame->interrupt.error_code & 0x2) != 0;
+ auto user = (frame->interrupt.error_code & 0x4) != 0;
+
+ return kapi::cpu::dispatch({type, instruction_pointer, fault_address, present, write, user});
+ }
+ default:
+ return kapi::cpu::dispatch({type, instruction_pointer});
+ }
+ }
+
+ auto acknowledge_pic_interrupt(interrupt_frame * frame) -> void
+ {
+ if (frame->interrupt.number >= pic_slave_irq_start)
+ {
+ pic_slave_control_port::write(pic_end_of_interrupt);
+ }
+ pic_master_control_port::write(pic_end_of_interrupt);
+ }
+ } // namespace
+
+ extern "C"
+ {
+ extern std::uintptr_t const isr_stub_table[256];
+
+ auto interrupt_dispatch(interrupt_frame * frame) -> void
+ {
+ auto [number, code] = frame->interrupt;
+
+ if (number < number_of_exception_vectors)
+ {
+ if (!dispatch_exception(frame))
+ {
+ if (has_error_code(static_cast<exception>(number)))
+ {
+ kstd::println(kstd::print_sink::stderr,
+ "[x86_64:CPU] Unhandled exception number {:#04x} received with code {:#04x}", number, code);
+ }
+ else
+ {
+ kstd::println(kstd::print_sink::stderr, "[x86_64:CPU] Unhandled exception number {:#04x} received", number);
+ }
+ }
+ }
+ else
+ {
+ auto irq_number = number - number_of_exception_vectors;
+
+ if (kapi::interrupts::dispatch(irq_number) == kapi::interrupts::status::unhandled)
+ {
+ kstd::println(kstd::print_sink::stderr, "[x86_64:CPU] Unhandled interrupt {:#04x} (IRQ{})", number,
+ irq_number);
+ }
+
+ acknowledge_pic_interrupt(frame);
+ }
+ }
+ }
+
+ interrupt_descriptor_table::interrupt_descriptor_table() noexcept
+ {
+ for (auto i = 0uz; i < 256; ++i)
+ {
+ m_descriptors[i] = gate_descriptor{
+ .offset_low = static_cast<std::uint16_t>(isr_stub_table[i] & 0xffff), // NOLINT(readability-magic-numbers)
+ .m_code_segment = segment_selector{0, false, 1},
+ .interrupt_stack_table_selector = 0,
+ .gate_type = (i < 32 && i != 2) ? gate_type::trap_gate : gate_type::interrupt_gate,
+ .descriptor_privilege_level = 0,
+ .present = true,
+ .offset_middle =
+ static_cast<std::uint16_t>((isr_stub_table[i] >> 16) & 0xffff), // NOLINT(readability-magic-numbers)
+ .offset_high =
+ static_cast<std::uint32_t>((isr_stub_table[i] >> 32) & 0xffff'ffff), // NOLINT(readability-magic-numbers)
+ };
+ }
+ }
+
+ auto interrupt_descriptor_table::load() const -> void
+ {
+ interrupt_descriptor_table_register{.limit = sizeof(m_descriptors) - 1, .base = m_descriptors.data()}.load();
+ }
+
+ auto interrupt_descriptor_table_register::load() const -> void
+ {
+ asm volatile("lidt %0" : : "m"(*this));
+ }
+
+ auto interrupt_descriptor_table_register::read() -> interrupt_descriptor_table_register
+ {
+ interrupt_descriptor_table_register idtr{};
+ asm volatile("sidt %0" : : "m"(idtr));
+ return idtr;
+ }
+
+} // namespace arch::cpu \ No newline at end of file
diff --git a/arch/x86_64/include/arch/cpu/interrupts.hpp b/arch/x86_64/arch/cpu/interrupts.hpp
index 92c5824..6162f56 100644
--- a/arch/x86_64/include/arch/cpu/interrupts.hpp
+++ b/arch/x86_64/arch/cpu/interrupts.hpp
@@ -1,7 +1,9 @@
#ifndef TEACHOS_X86_64_CPU_INTERRUPTS_HPP
#define TEACHOS_X86_64_CPU_INTERRUPTS_HPP
-#include "arch/cpu/segment_selector.hpp"
+#include <arch/cpu/segment_selector.hpp>
+
+#include <kapi/memory.hpp>
#include <array>
#include <cstdint>
@@ -30,7 +32,7 @@ namespace arch::cpu
//! Reserved
std::uint8_t : 5;
//! The type of this gate.
- gate_type gate_type : 4;
+ enum gate_type gate_type : 4;
//! Reserved
std::uint8_t : 1;
//! The privilege level required to enter through this gate.
@@ -48,14 +50,67 @@ namespace arch::cpu
static_assert(sizeof(gate_descriptor) == 2 * sizeof(std::uint64_t));
static_assert(std::is_aggregate_v<gate_descriptor>);
+ //! The stack frame as established by the low-level assembly interrupt stubs.
+ //!
+ //! @note The layout of this struct is reverse than what would reasonably be expected. The reason for this reversal is
+ //! that fact that it represents a stack frame. Stack frames on x86_64 grow towards lower addresses, meaning the first
+ //! item on the stack is the last item in C++ memory layout order.
+ struct interrupt_frame
+ {
+ struct
+ {
+ std::uint64_t r15{};
+ std::uint64_t r14{};
+ std::uint64_t r13{};
+ std::uint64_t r12{};
+ std::uint64_t r11{};
+ std::uint64_t r10{};
+ std::uint64_t r9{};
+ std::uint64_t r8{};
+ std::uint64_t rdi{};
+ std::uint64_t rsi{};
+ std::uint64_t rbp{};
+ std::uint64_t rdx{};
+ std::uint64_t rcx{};
+ std::uint64_t rbx{};
+ std::uint64_t rax{};
+ } handler_saved;
+
+ struct
+ {
+ std::uint64_t number{};
+ std::uint64_t error_code{};
+ } interrupt;
+
+ struct
+ {
+ kapi::memory::linear_address rip{};
+ std::uint64_t cs{};
+ std::uint64_t rflags{};
+ kapi::memory::linear_address rsp{};
+ std::uint64_t ss{};
+ } cpu_saved;
+ };
+
struct interrupt_descriptor_table
{
- interrupt_descriptor_table();
+ interrupt_descriptor_table() noexcept;
+
+ auto load() const -> void;
private:
std::array<gate_descriptor, 256> m_descriptors{};
};
+ struct [[gnu::packed]] interrupt_descriptor_table_register
+ {
+ std::uint16_t limit;
+ gate_descriptor const * base;
+
+ auto load() const -> void;
+ auto static read() -> interrupt_descriptor_table_register;
+ };
+
} // namespace arch::cpu
#endif \ No newline at end of file
diff --git a/arch/x86_64/arch/cpu/legacy_pic.hpp b/arch/x86_64/arch/cpu/legacy_pic.hpp
new file mode 100644
index 0000000..56ca9c4
--- /dev/null
+++ b/arch/x86_64/arch/cpu/legacy_pic.hpp
@@ -0,0 +1,19 @@
+#ifndef TEACHOS_X86_64_CPU_LEGACY_PIC_HPP
+#define TEACHOS_X86_64_CPU_LEGACY_PIC_HPP
+
+#include <arch/device_io/port_io.hpp>
+
+#include <cstdint>
+
+namespace arch::cpu
+{
+ using pic_master_control_port = io::port<0x20, std::uint8_t, io::port_read, io::port_write>;
+ using pic_master_data_port = io::port<0x21, std::uint8_t, io::port_read, io::port_write>;
+ using pic_slave_control_port = io::port<0xa0, std::uint8_t, io::port_read, io::port_write>;
+ using pic_slave_data_port = io::port<0xa1, std::uint8_t, io::port_read, io::port_write>;
+
+ constexpr auto pic_end_of_interrupt = std::uint8_t{0x20};
+
+} // namespace arch::cpu
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/include/arch/cpu/model_specific_register.hpp b/arch/x86_64/arch/cpu/model_specific_register.hpp
index 8539a24..bd4aff9 100644
--- a/arch/x86_64/include/arch/cpu/model_specific_register.hpp
+++ b/arch/x86_64/arch/cpu/model_specific_register.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_X86_64_CPU_MODEL_SPECIFIC_REGISTER_HPP
#define TEACHOS_X86_64_CPU_MODEL_SPECIFIC_REGISTER_HPP
-// IWYU pragma: private, include "x86_64/cpu/registers.hpp"
+// IWYU pragma: private, include <x86_64/cpu/registers.hpp>
#include <kstd/ext/bitfield_enum>
diff --git a/arch/x86_64/include/arch/cpu/registers.hpp b/arch/x86_64/arch/cpu/registers.hpp
index d7def10..58633f6 100644
--- a/arch/x86_64/include/arch/cpu/registers.hpp
+++ b/arch/x86_64/arch/cpu/registers.hpp
@@ -1,10 +1,10 @@
#ifndef TEACHOS_X86_64_CPU_REGISTERS_HPP
#define TEACHOS_X86_64_CPU_REGISTERS_HPP
-#include "kapi/memory.hpp"
+#include <arch/cpu/control_register.hpp> // IWYU pragma: export
+#include <arch/cpu/model_specific_register.hpp> // IWYU pragma: export
-#include "arch/cpu/control_register.hpp" // IWYU pragma: export
-#include "arch/cpu/model_specific_register.hpp" // IWYU pragma: export
+#include <kapi/memory.hpp>
namespace arch::cpu
{
diff --git a/arch/x86_64/include/arch/cpu/segment_descriptor.hpp b/arch/x86_64/arch/cpu/segment_descriptor.hpp
index 9570670..9570670 100644
--- a/arch/x86_64/include/arch/cpu/segment_descriptor.hpp
+++ b/arch/x86_64/arch/cpu/segment_descriptor.hpp
diff --git a/arch/x86_64/include/arch/cpu/segment_selector.hpp b/arch/x86_64/arch/cpu/segment_selector.hpp
index 1a78c47..1a78c47 100644
--- a/arch/x86_64/include/arch/cpu/segment_selector.hpp
+++ b/arch/x86_64/arch/cpu/segment_selector.hpp
diff --git a/arch/x86_64/include/arch/cpu/task_state_segment.hpp b/arch/x86_64/arch/cpu/task_state_segment.hpp
index 57729dd..57729dd 100644
--- a/arch/x86_64/include/arch/cpu/task_state_segment.hpp
+++ b/arch/x86_64/arch/cpu/task_state_segment.hpp
diff --git a/arch/x86_64/src/debug/qemu_output.cpp b/arch/x86_64/arch/debug/qemu_output.cpp
index 535017d..71acede 100644
--- a/arch/x86_64/src/debug/qemu_output.cpp
+++ b/arch/x86_64/arch/debug/qemu_output.cpp
@@ -1,6 +1,6 @@
-#include "arch/debug/qemu_output.hpp"
+#include <arch/debug/qemu_output.hpp>
-#include "kapi/cio.hpp"
+#include <kapi/cio.hpp>
#include <algorithm>
#include <string_view>
diff --git a/arch/x86_64/include/arch/debug/qemu_output.hpp b/arch/x86_64/arch/debug/qemu_output.hpp
index e72eb39..5ddd4be 100644
--- a/arch/x86_64/include/arch/debug/qemu_output.hpp
+++ b/arch/x86_64/arch/debug/qemu_output.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_X86_64_DEBUG_QEMU_OUTPUT_HPP
#define TEACHOS_X86_64_DEBUG_QEMU_OUTPUT_HPP
-#include "kapi/cio.hpp"
+#include <arch/device_io/port_io.hpp>
-#include "arch/device_io/port_io.hpp"
+#include <kapi/cio.hpp>
#include <string_view>
diff --git a/arch/x86_64/include/arch/device_io/port_io.hpp b/arch/x86_64/arch/device_io/port_io.hpp
index 65e58e3..4c8d66a 100644
--- a/arch/x86_64/include/arch/device_io/port_io.hpp
+++ b/arch/x86_64/arch/device_io/port_io.hpp
@@ -69,9 +69,9 @@ namespace arch::io
//! The assembly templates used for writing to an I/O port.
constexpr auto static code = std::array{
- std::string_view{"mov %[port], %%dx\nmov %%dx, %[data]\nout %%al, %%dx"},
- std::string_view{"mov %[port], %%dx\nmov %%dx, %[data]\nout %%ax, %%dx"},
- std::string_view{"mov %[port], %%dx\nmov %%dx, %[data]\nout %%eax, %%dx"},
+ std::string_view{"mov %[port], %%dx\nmov %[data], %%al\nout %%al, %%dx"},
+ std::string_view{"mov %[port], %%dx\nmov %[data], %%ax\nout %%ax, %%dx"},
+ std::string_view{"mov %[port], %%dx\nmov %[data], %%eax\nout %%eax, %%dx"},
};
};
@@ -102,6 +102,11 @@ namespace arch::io
: std::string_view{"eax"};
};
+ auto inline wait() -> void
+ {
+ port<0x80, std::uint8_t, port_write>::write<std::uint8_t>(0);
+ }
+
} // namespace arch::io
#endif \ No newline at end of file
diff --git a/arch/x86_64/arch/devices/init.cpp b/arch/x86_64/arch/devices/init.cpp
new file mode 100644
index 0000000..c30e8cf
--- /dev/null
+++ b/arch/x86_64/arch/devices/init.cpp
@@ -0,0 +1,76 @@
+#include <arch/devices/init.hpp>
+
+#include <arch/boot/boot.hpp>
+#include <arch/bus/isa.hpp>
+#include <arch/devices/legacy_pit.hpp>
+
+#include <kapi/acpi.hpp>
+#include <kapi/cpu.hpp>
+#include <kapi/devices.hpp>
+
+#include <acpi/acpi.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <cstdint>
+#include <utility>
+
+namespace arch::devices
+{
+
+ namespace
+ {
+ constexpr auto pit_frequency_in_hz = std::uint32_t{100u};
+
+ auto get_acpi_root_pointer() -> kstd::observer_ptr<::acpi::rsdp const>
+ {
+ auto const & mbi = kapi::boot::bootstrap_information.mbi;
+ auto system_description_pointer = static_cast<::acpi::rsdp const *>(nullptr);
+
+ if (auto const & xsdp = mbi->maybe_acpi_xsdp())
+ {
+ auto data = xsdp->pointer().data();
+
+ system_description_pointer = reinterpret_cast<::acpi::xsdp const *>(data);
+ }
+ else if (auto const & rsdp = mbi->maybe_acpi_rsdp())
+ {
+ auto data = rsdp->pointer().data();
+ system_description_pointer = reinterpret_cast<::acpi::rsdp const *>(data);
+ }
+
+ return kstd::make_observer(system_description_pointer);
+ }
+ } // namespace
+
+ auto init_acpi_devices() -> void
+ {
+ auto acpi_root_pointer = get_acpi_root_pointer();
+ if (acpi_root_pointer && acpi_root_pointer->validate())
+ {
+ if (kapi::acpi::init(*acpi_root_pointer))
+ {
+ kstd::println("[x86_64:DEV] ACPI subsystem initialized.");
+ }
+ }
+
+ kapi::cpu::discover_topology();
+ }
+
+ auto init_legacy_devices() -> void
+ {
+ kstd::println("[x86_64:DEV] Initializing ISA bus...");
+
+ auto isa_major_number = kapi::devices::allocate_major_number();
+ auto isa_bus = kstd::make_unique<arch::bus::isa>(isa_major_number);
+
+ auto pit_major_number = kapi::devices::allocate_major_number();
+ auto pit = kstd::make_unique<arch::devices::legacy_pit>(pit_major_number, pit_frequency_in_hz);
+ isa_bus->add_child(std::move(pit));
+
+ auto & root_bus = kapi::devices::get_root_bus();
+ root_bus.add_child(std::move(isa_bus));
+ }
+
+} // namespace arch::devices
diff --git a/arch/x86_64/arch/devices/init.hpp b/arch/x86_64/arch/devices/init.hpp
new file mode 100644
index 0000000..c5fbf37
--- /dev/null
+++ b/arch/x86_64/arch/devices/init.hpp
@@ -0,0 +1,12 @@
+#ifndef TEACHOS_ARCH_X86_64_DEVICES_INIT_HPP
+#define TEACHOS_ARCH_X86_64_DEVICES_INIT_HPP
+
+namespace arch::devices
+{
+
+ auto init_acpi_devices() -> void;
+ auto init_legacy_devices() -> void;
+
+} // namespace arch::devices
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/arch/devices/legacy_pit.cpp b/arch/x86_64/arch/devices/legacy_pit.cpp
new file mode 100644
index 0000000..d542d47
--- /dev/null
+++ b/arch/x86_64/arch/devices/legacy_pit.cpp
@@ -0,0 +1,60 @@
+#include <arch/devices/legacy_pit.hpp>
+
+#include <arch/device_io/port_io.hpp>
+
+#include <kapi/devices.hpp>
+#include <kapi/devices/device.hpp>
+#include <kapi/interrupts.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ namespace
+ {
+ using command_port = io::port<0x43, std::uint8_t, io::port_write>;
+ using channel_0_port = io::port<0x40, std::uint8_t, io::port_write>;
+ using channel_1_port = io::port<0x41, std::uint8_t, io::port_write>;
+ using channel_2_port = io::port<0x42, std::uint8_t, io::port_write>;
+
+ constexpr auto base_frequency = 1'193'182u;
+ constexpr auto square_wave_mode = 0x36;
+ } // namespace
+
+ legacy_pit::legacy_pit(std::size_t major, std::uint32_t frequency_in_hz)
+ : kapi::devices::device{major, 0, "legacy_pit"}
+ , m_irq_number{0}
+ , m_frequency_in_hz{frequency_in_hz}
+ {}
+
+ auto legacy_pit::init() -> bool
+ {
+ auto divisor = static_cast<std::uint16_t>(base_frequency / m_frequency_in_hz);
+
+ kapi::interrupts::register_handler(m_irq_number, *this);
+
+ command_port::write<std::uint8_t>(square_wave_mode);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor & 0xff);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor >> 8 & 0xff);
+ io::wait();
+
+ return true;
+ }
+
+ auto legacy_pit::handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status
+ {
+ if (irq_number != m_irq_number)
+ {
+ return kapi::interrupts::status::unhandled;
+ }
+
+ ++m_ticks;
+
+ return kapi::interrupts::status::handled;
+ }
+
+} // namespace arch::devices \ No newline at end of file
diff --git a/arch/x86_64/arch/devices/legacy_pit.hpp b/arch/x86_64/arch/devices/legacy_pit.hpp
new file mode 100644
index 0000000..356895c
--- /dev/null
+++ b/arch/x86_64/arch/devices/legacy_pit.hpp
@@ -0,0 +1,29 @@
+#ifndef TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+#define TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+
+#include <kapi/devices/device.hpp>
+#include <kapi/interrupts.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ struct legacy_pit : kapi::devices::device, kapi::interrupts::handler
+ {
+ legacy_pit(std::size_t major, std::uint32_t frequency_in_hz);
+
+ auto init() -> bool override;
+
+ auto handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status override;
+
+ private:
+ std::uint32_t m_irq_number{};
+ std::uint32_t m_frequency_in_hz{};
+ std::uint64_t m_ticks{};
+ };
+
+} // namespace arch::devices
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/arch/devices/local_apic.cpp b/arch/x86_64/arch/devices/local_apic.cpp
new file mode 100644
index 0000000..660921b
--- /dev/null
+++ b/arch/x86_64/arch/devices/local_apic.cpp
@@ -0,0 +1,134 @@
+#include <arch/devices/local_apic.hpp>
+
+#include <kapi/devices.hpp>
+#include <kapi/memory.hpp>
+
+#include <kstd/print>
+
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+
+namespace arch::devices
+{
+
+ namespace
+ {
+ constexpr auto lapic_enable_bit = 0x100u;
+ constexpr auto spurious_interrupt_vector = 0xFFu;
+
+ constexpr auto offset_of_version = 0u;
+ constexpr auto offset_of_max_lvt_entry = 16u;
+ constexpr auto offset_of_eoi_suppression = 24u;
+
+ } // namespace
+
+ enum struct local_apic::registers : std::ptrdiff_t
+ {
+ id = 0x020,
+ version = 0x030,
+ task_priority = 0x080,
+ arbitration_priority = 0x090,
+ processor_priority = 0x0a0,
+ eoi = 0x0b0,
+ remote_read = 0x0c0,
+ logical_destination = 0x0d0,
+ destination_format = 0x0e0,
+ spurious_interrupt_vector = 0x0f0,
+ in_service_0 = 0x100,
+ in_service_1 = 0x110,
+ in_service_2 = 0x120,
+ in_service_3 = 0x130,
+ in_service_4 = 0x140,
+ in_service_5 = 0x150,
+ in_service_6 = 0x160,
+ in_service_7 = 0x170,
+ trigger_mode_0 = 0x180,
+ trigger_mode_1 = 0x190,
+ trigger_mode_2 = 0x1a0,
+ trigger_mode_3 = 0x1b0,
+ trigger_mode_4 = 0x1c0,
+ trigger_mode_5 = 0x1d0,
+ trigger_mode_6 = 0x1e0,
+ trigger_mode_7 = 0x1f0,
+ interrupt_request_0 = 0x200,
+ interrupt_request_1 = 0x210,
+ interrupt_request_2 = 0x220,
+ interrupt_request_3 = 0x230,
+ interrupt_request_4 = 0x240,
+ interrupt_request_5 = 0x250,
+ interrupt_request_6 = 0x260,
+ interrupt_request_7 = 0x270,
+ error_status = 0x280,
+ lvt_corrected_machine_check_interrupt = 0x2f0,
+ interrupt_command_0 = 0x300,
+ interrupt_command_1 = 0x310,
+ lvt_timer = 0x320,
+ lvt_thermal_sensors = 0x330,
+ lvt_performance_monitoring_counters = 0x340,
+ lvt_local_interrupt_0 = 0x350,
+ lvt_local_interrupt_1 = 0x360,
+ lvt_error = 0x370,
+ initial_count = 0x380,
+ current_count = 0x390,
+ divide_configuration = 0x3e0,
+ };
+
+ local_apic::local_apic(std::size_t major, std::size_t minor, std::uint64_t hardware_id,
+ kapi::memory::physical_address base, bool is_bsp)
+ : kapi::devices::device{major, minor, "lapic"}
+ , m_hardware_id{hardware_id}
+ , m_base{base}
+ , m_is_bsp{is_bsp}
+ {}
+
+ auto local_apic::init() -> bool
+ {
+ auto static shared_virtual_base = kapi::memory::allocate_mmio_region(1);
+ auto static is_mapped = false;
+
+ if (!is_mapped)
+ {
+ if (!kapi::memory::map_mmio_region(shared_virtual_base, m_base, kapi::memory::page_mapper::flags::writable))
+ {
+ kstd::println("[x86_64:DEV] LAPIC {} MMIO mapping failed!", m_hardware_id);
+ return false;
+ }
+ is_mapped = true;
+ }
+
+ m_mapped_region = shared_virtual_base;
+
+ if (m_is_bsp)
+ {
+ auto raw_version = read_register(registers::version);
+ m_version = (raw_version >> offset_of_version) & 0xff;
+ m_highest_lvt_entry_index = (raw_version >> offset_of_max_lvt_entry) & 0xff;
+ m_supports_eoi_broadcast_suppression = (raw_version >> offset_of_eoi_suppression) & 0x1;
+
+ write_register(registers::spurious_interrupt_vector, lapic_enable_bit | spurious_interrupt_vector);
+
+ kstd::println("[x86_64:DEV] LAPIC initialized. version: {#x} | max_lvt_entry: {} | eoi_suppression: {:s}",
+ m_version, m_highest_lvt_entry_index, m_supports_eoi_broadcast_suppression);
+ }
+ else
+ {
+ kstd::println("[x86_64:DEV] LAPIC {} is not on the BSP, deferring initialization.", m_hardware_id);
+ }
+
+ return true;
+ }
+
+ auto local_apic::read_register(registers id) const -> std::uint32_t
+ {
+ auto reg = static_cast<std::uint32_t volatile *>(m_mapped_region.first + std::to_underlying(id));
+ return *reg;
+ }
+
+ auto local_apic::write_register(registers id, std::uint32_t value) -> void
+ {
+ auto reg = static_cast<std::uint32_t volatile *>(m_mapped_region.first + std::to_underlying(id));
+ *reg = value;
+ }
+
+} // namespace arch::devices
diff --git a/arch/x86_64/arch/devices/local_apic.hpp b/arch/x86_64/arch/devices/local_apic.hpp
new file mode 100644
index 0000000..f8f080d
--- /dev/null
+++ b/arch/x86_64/arch/devices/local_apic.hpp
@@ -0,0 +1,37 @@
+#ifndef TEACHOS_ARCH_X86_64_DEVICES_LOCAL_APIC_HPP
+#define TEACHOS_ARCH_X86_64_DEVICES_LOCAL_APIC_HPP
+
+#include <kapi/devices/device.hpp>
+#include <kapi/memory.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ struct local_apic : kapi::devices::device
+ {
+ local_apic(std::size_t major, std::size_t minor, std::uint64_t hardware_id, kapi::memory::physical_address base,
+ bool is_bsp);
+
+ auto init() -> bool override;
+
+ private:
+ enum struct registers : std::ptrdiff_t;
+
+ [[nodiscard]] auto read_register(registers id) const -> std::uint32_t;
+ auto write_register(registers id, std::uint32_t value) -> void;
+
+ std::uint64_t m_hardware_id{};
+ kapi::memory::physical_address m_base{};
+ kapi::memory::mmio_region m_mapped_region{};
+ bool m_is_bsp{};
+ std::uint8_t m_version{};
+ std::uint8_t m_highest_lvt_entry_index{};
+ bool m_supports_eoi_broadcast_suppression{};
+ };
+
+} // namespace arch::devices
+
+#endif
diff --git a/arch/x86_64/src/memory/higher_half_mapper.cpp b/arch/x86_64/arch/memory/higher_half_mapper.cpp
index abb54a3..75adb3c 100644
--- a/arch/x86_64/src/memory/higher_half_mapper.cpp
+++ b/arch/x86_64/arch/memory/higher_half_mapper.cpp
@@ -1,10 +1,10 @@
-#include "arch/memory/higher_half_mapper.hpp"
+#include <arch/memory/higher_half_mapper.hpp>
-#include "kapi/memory.hpp"
-#include "kapi/system.hpp"
+#include <arch/memory/page_table.hpp>
+#include <arch/memory/page_utilities.hpp>
-#include "arch/memory/page_table.hpp"
-#include "arch/memory/page_utilities.hpp"
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
#include <algorithm>
#include <array>
diff --git a/arch/x86_64/include/arch/memory/higher_half_mapper.hpp b/arch/x86_64/arch/memory/higher_half_mapper.hpp
index c8df40c..9b02ee6 100644
--- a/arch/x86_64/include/arch/memory/higher_half_mapper.hpp
+++ b/arch/x86_64/arch/memory/higher_half_mapper.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_X86_64_HIGHER_HALF_MAPPER_HPP
#define TEACHOS_X86_64_HIGHER_HALF_MAPPER_HPP
-#include "kapi/memory.hpp"
+#include <arch/memory/page_table.hpp>
-#include "arch/memory/page_table.hpp"
+#include <kapi/memory.hpp>
#include <cstddef>
diff --git a/arch/x86_64/src/memory/kernel_mapper.cpp b/arch/x86_64/arch/memory/kernel_mapper.cpp
index 08c32c5..74272a0 100644
--- a/arch/x86_64/src/memory/kernel_mapper.cpp
+++ b/arch/x86_64/arch/memory/kernel_mapper.cpp
@@ -1,14 +1,16 @@
-#include "arch/memory/kernel_mapper.hpp"
+#include <arch/memory/kernel_mapper.hpp>
-#include "kapi/memory.hpp"
-#include "kapi/system.hpp"
+#include <arch/boot/ld.hpp>
-#include "arch/boot/ld.hpp"
-
-#include <kstd/print>
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
#include <elf/format.hpp>
#include <elf/section_header.hpp>
+
+#include <kstd/print>
+#include <kstd/units>
+
#include <multiboot2/information.hpp>
#include <algorithm>
@@ -19,13 +21,14 @@
#include <string_view>
#include <utility>
+using namespace std::string_view_literals;
+using namespace kstd::units_literals;
+
namespace arch::memory
{
namespace
{
- using namespace std::string_view_literals;
-
constexpr auto static ignored_section_prefixes = std::array{
".boot_"sv,
};
@@ -71,7 +74,8 @@ namespace arch::memory
auto kernel_mapper::map_section(section_header_type const & section, std::string_view name,
kapi::memory::page_mapper & mapper) -> void
{
- auto number_of_pages = (section.size + (PLATFORM_PAGE_SIZE - 1)) / PLATFORM_PAGE_SIZE;
+ auto number_of_pages =
+ (kstd::units::bytes{section.size} + (kapi::memory::page::size - 1_B)) / kapi::memory::page::size;
auto linear_start_address = kapi::memory::linear_address{section.virtual_load_address};
auto physical_start_address = kapi::memory::physical_address{section.virtual_load_address & ~m_kernel_load_base};
diff --git a/arch/x86_64/include/arch/memory/kernel_mapper.hpp b/arch/x86_64/arch/memory/kernel_mapper.hpp
index 4329f1b..adbf688 100644
--- a/arch/x86_64/include/arch/memory/kernel_mapper.hpp
+++ b/arch/x86_64/arch/memory/kernel_mapper.hpp
@@ -1,10 +1,11 @@
#ifndef TEACHOS_X86_64_KERNEL_MAPPER_HPP
#define TEACHOS_X86_64_KERNEL_MAPPER_HPP
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <elf/format.hpp>
#include <elf/section_header.hpp>
+
#include <multiboot2/information.hpp>
#include <cstdint>
diff --git a/arch/x86_64/src/memory/mmu.cpp b/arch/x86_64/arch/memory/mmu.cpp
index ea23278..2b53fa4 100644
--- a/arch/x86_64/src/memory/mmu.cpp
+++ b/arch/x86_64/arch/memory/mmu.cpp
@@ -1,8 +1,8 @@
-#include "arch/memory/mmu.hpp"
+#include <arch/memory/mmu.hpp>
-#include "kapi/memory.hpp"
+#include <arch/cpu/registers.hpp>
-#include "arch/cpu/registers.hpp"
+#include <kapi/memory.hpp>
namespace arch::memory
{
diff --git a/arch/x86_64/include/arch/memory/mmu.hpp b/arch/x86_64/arch/memory/mmu.hpp
index 2d64184..64373f4 100644
--- a/arch/x86_64/include/arch/memory/mmu.hpp
+++ b/arch/x86_64/arch/memory/mmu.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_X86_64_MEMORY_MMU_HPP
#define TEACHOS_X86_64_MEMORY_MMU_HPP
-#include "kapi/memory/address.hpp"
+#include <kapi/memory/address.hpp>
namespace arch::memory
{
diff --git a/arch/x86_64/src/memory/page_table.cpp b/arch/x86_64/arch/memory/page_table.cpp
index 26cdd29..2180420 100644
--- a/arch/x86_64/src/memory/page_table.cpp
+++ b/arch/x86_64/arch/memory/page_table.cpp
@@ -1,6 +1,6 @@
-#include "arch/memory/page_table.hpp"
+#include <arch/memory/page_table.hpp>
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <algorithm>
#include <bit>
diff --git a/arch/x86_64/include/arch/memory/page_table.hpp b/arch/x86_64/arch/memory/page_table.hpp
index 2889d38..ce3d3a1 100644
--- a/arch/x86_64/include/arch/memory/page_table.hpp
+++ b/arch/x86_64/arch/memory/page_table.hpp
@@ -1,9 +1,10 @@
#ifndef TEACHOS_X86_64_PAGE_TABLE_HPP
#define TEACHOS_X86_64_PAGE_TABLE_HPP
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <kstd/ext/bitfield_enum>
+#include <kstd/units>
#include <array>
#include <cstddef>
@@ -116,7 +117,7 @@ namespace arch::memory
};
//! The maximum number of entries in this table.
- constexpr auto static entry_count{kapi::memory::page::size / sizeof(entry)};
+ constexpr auto static entry_count{kapi::memory::page::size / kstd::units::bytes{sizeof(entry)}};
//! Get the entry at the given index.
//!
@@ -173,7 +174,7 @@ namespace arch::memory
result |= mapper_flags::writable;
}
- if ((flags & table_flags::disable_cache) != table_flags::empty)
+ if ((flags & (table_flags::disable_cache | table_flags::write_through)) != table_flags::empty)
{
result |= mapper_flags::uncached;
}
@@ -210,7 +211,7 @@ namespace arch::memory
if ((flags & mapper_flags::uncached) != mapper_flags::empty)
{
- result |= table_flags::disable_cache;
+ result |= (table_flags::disable_cache | table_flags::write_through);
}
if ((flags & mapper_flags::supervisor_only) == mapper_flags::empty)
@@ -228,4 +229,4 @@ namespace arch::memory
} // namespace arch::memory
-#endif \ No newline at end of file
+#endif
diff --git a/arch/x86_64/include/arch/memory/page_utilities.hpp b/arch/x86_64/arch/memory/page_utilities.hpp
index c48e74f..068e824 100644
--- a/arch/x86_64/include/arch/memory/page_utilities.hpp
+++ b/arch/x86_64/arch/memory/page_utilities.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_X86_64_PAGE_UTILITIES_HPP
#define TEACHOS_X86_64_PAGE_UTILITIES_HPP
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <cstddef>
diff --git a/arch/x86_64/src/memory/region_allocator.cpp b/arch/x86_64/arch/memory/region_allocator.cpp
index facb1f2..4086a10 100644
--- a/arch/x86_64/src/memory/region_allocator.cpp
+++ b/arch/x86_64/arch/memory/region_allocator.cpp
@@ -1,7 +1,7 @@
-#include "arch/memory/region_allocator.hpp"
+#include <arch/memory/region_allocator.hpp>
-#include "kapi/memory/address.hpp"
-#include "kapi/memory/frame.hpp"
+#include <kapi/memory/address.hpp>
+#include <kapi/memory/frame.hpp>
#include <multiboot2/information.hpp>
@@ -35,7 +35,7 @@ namespace arch::memory
, m_kernel_end{kapi::memory::frame::containing(mem_info.image_range.second)}
, m_multiboot_start{kapi::memory::frame::containing(mem_info.mbi_range.first)}
, m_multiboot_end{kapi::memory::frame::containing(mem_info.mbi_range.second)}
- // TODO BA-FS26: Protect MB2 boot modules
+ , m_multiboot_information{mem_info.mbi}
{
choose_next_region();
}
@@ -76,19 +76,40 @@ namespace arch::memory
}
}
- if (falls_within(m_next_frame, m_kernel_start, m_kernel_end))
+ auto advanced = true;
+ while (advanced)
{
- m_next_frame = m_kernel_end + 1;
- }
+ advanced = false;
- if (falls_within(m_next_frame, m_multiboot_start, m_multiboot_end))
- {
- m_next_frame = m_multiboot_end + 1;
- }
+ if (falls_within(m_next_frame, m_kernel_start, m_kernel_end))
+ {
+ m_next_frame = m_kernel_end + 1;
+ advanced = true;
+ continue;
+ }
- if (falls_within(m_next_frame, m_kernel_start, m_kernel_end))
- {
- m_next_frame = m_kernel_end + 1;
+ if (falls_within(m_next_frame, m_multiboot_start, m_multiboot_end))
+ {
+ m_next_frame = m_multiboot_end + 1;
+ advanced = true;
+ continue;
+ }
+
+ if (m_multiboot_information)
+ {
+ for (auto const & module : m_multiboot_information->modules())
+ {
+ auto module_start = kapi::memory::frame::containing(kapi::memory::physical_address{module.start_address});
+ auto module_end = kapi::memory::frame::containing(kapi::memory::physical_address{module.end_address});
+
+ if (falls_within(m_next_frame, module_start, module_end))
+ {
+ m_next_frame = module_end + 1;
+ advanced = true;
+ break;
+ }
+ }
+ }
}
if (m_next_frame > last_frame(*m_current_region))
diff --git a/arch/x86_64/include/arch/memory/region_allocator.hpp b/arch/x86_64/arch/memory/region_allocator.hpp
index 1c5885e..5d9da2e 100644
--- a/arch/x86_64/include/arch/memory/region_allocator.hpp
+++ b/arch/x86_64/arch/memory/region_allocator.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP
#define TEACHOS_X86_64_MEMORY_REGION_ALLOCATOR_HPP
-#include "kapi/memory/address.hpp"
-#include "kapi/memory/frame.hpp"
-#include "kapi/memory/frame_allocator.hpp"
+#include <kapi/memory/address.hpp>
+#include <kapi/memory/frame.hpp>
+#include <kapi/memory/frame_allocator.hpp>
#include <multiboot2/information.hpp>
@@ -42,6 +42,11 @@ namespace arch::memory
//! These include available, unavailable, and reclaimable regions. In general, only frames that are located in
//! non-reserved (as in available) regions should be allocated for page storage.
multiboot2::memory_map memory_map;
+
+ //! The loader supplied Multiboot2 information structure.
+ //!
+ //! This is used to query boot module ranges so these frames can be excluded from early allocations.
+ multiboot2::information_view const * mbi;
};
using region = multiboot2::memory_map::region;
@@ -80,6 +85,7 @@ namespace arch::memory
kapi::memory::frame m_kernel_end; //!< The end of the kernel image in physical memory.
kapi::memory::frame m_multiboot_start; //!< The start of the Multiboot2 information in physical memory.
kapi::memory::frame m_multiboot_end; //!< The end of the Multiboot2 information in physical memory.
+ multiboot2::information_view const * m_multiboot_information; //!< Source of Multiboot2 module ranges.
};
} // namespace arch::memory
diff --git a/arch/x86_64/include/arch/vga/crtc.hpp b/arch/x86_64/arch/vga/crtc.hpp
index dbdc365..a8bec93 100644
--- a/arch/x86_64/include/arch/vga/crtc.hpp
+++ b/arch/x86_64/arch/vga/crtc.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_X86_64_VGA_IO_HPP
#define TEACHOS_X86_64_VGA_IO_HPP
-#include "arch/device_io/port_io.hpp"
+#include <arch/device_io/port_io.hpp>
#include <cstddef>
diff --git a/arch/x86_64/arch/vga/text.hpp b/arch/x86_64/arch/vga/text.hpp
new file mode 100644
index 0000000..2e73dd2
--- /dev/null
+++ b/arch/x86_64/arch/vga/text.hpp
@@ -0,0 +1,10 @@
+#ifndef TEACHOS_X86_64_VGA_TEXT_HPP
+#define TEACHOS_X86_64_VGA_TEXT_HPP
+
+#include <arch/vga/text/attribute.hpp> // IWYU pragma: export
+#include <arch/vga/text/color.hpp> // IWYU pragma: export
+#include <arch/vga/text/common_attributes.hpp> // IWYU pragma: export
+#include <arch/vga/text/device.hpp> // IWYU pragma: export
+#include <arch/vga/text/flags.hpp> // IWYU pragma: export
+
+#endif // TEACHOS_ARCH_X86_64_VIDEO_VGA_TEXT_HPP \ No newline at end of file
diff --git a/arch/x86_64/include/arch/vga/text/attribute.hpp b/arch/x86_64/arch/vga/text/attribute.hpp
index 6a0f995..6395aed 100644
--- a/arch/x86_64/include/arch/vga/text/attribute.hpp
+++ b/arch/x86_64/arch/vga/text/attribute.hpp
@@ -1,10 +1,10 @@
#ifndef TEACHOS_X86_64_VGA_TEXT_ATTRIBUTE_HPP
#define TEACHOS_X86_64_VGA_TEXT_ATTRIBUTE_HPP
-// IWYU pragma: private, include "arch/vga/text.hpp"
+// IWYU pragma: private, include <arch/vga/text.hpp>
-#include "arch/vga/text/color.hpp"
-#include "arch/vga/text/flags.hpp"
+#include <arch/vga/text/color.hpp>
+#include <arch/vga/text/flags.hpp>
namespace arch::vga::text
{
diff --git a/arch/x86_64/src/vga/text/buffer.cpp b/arch/x86_64/arch/vga/text/buffer.cpp
index 7112573..498b9a3 100644
--- a/arch/x86_64/src/vga/text/buffer.cpp
+++ b/arch/x86_64/arch/vga/text/buffer.cpp
@@ -1,6 +1,6 @@
-#include "arch/vga/text/buffer.hpp"
+#include <arch/vga/text/buffer.hpp>
-#include "arch/vga/text/attribute.hpp"
+#include <arch/vga/text/attribute.hpp>
#include <algorithm>
#include <bit>
diff --git a/arch/x86_64/include/arch/vga/text/buffer.hpp b/arch/x86_64/arch/vga/text/buffer.hpp
index 648d37a..8eb6645 100644
--- a/arch/x86_64/include/arch/vga/text/buffer.hpp
+++ b/arch/x86_64/arch/vga/text/buffer.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_X86_64_VGA_TEXT_BUFFER_HPP
#define TEACHOS_X86_64_VGA_TEXT_BUFFER_HPP
-// IWYU pragma: private, include "arch/vga/text.hpp"
+// IWYU pragma: private, include <arch/vga/text.hpp>
-#include "arch/vga/text/attribute.hpp"
+#include <arch/vga/text/attribute.hpp>
#include <cstddef>
#include <span>
diff --git a/arch/x86_64/include/arch/vga/text/color.hpp b/arch/x86_64/arch/vga/text/color.hpp
index a541830..e0ad6df 100644
--- a/arch/x86_64/include/arch/vga/text/color.hpp
+++ b/arch/x86_64/arch/vga/text/color.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_X86_64_VGA_TEXT_COLOR_HPP
#define TEACHOS_X86_64_VGA_TEXT_COLOR_HPP
-// IWYU pragma: private, include "arch/vga/text.hpp"
+// IWYU pragma: private, include <arch/vga/text.hpp>
#include <cstdint>
diff --git a/arch/x86_64/include/arch/vga/text/common_attributes.hpp b/arch/x86_64/arch/vga/text/common_attributes.hpp
index 9bd61a5..3d8929f 100644
--- a/arch/x86_64/include/arch/vga/text/common_attributes.hpp
+++ b/arch/x86_64/arch/vga/text/common_attributes.hpp
@@ -1,11 +1,11 @@
#ifndef TEACHOS_X86_64_VGA_TEXT_COMMON_ATTRIBUTES_HPP
#define TEACHOS_X86_64_VGA_TEXT_COMMON_ATTRIBUTES_HPP
-// IWYU pragma: private, include "arch/vga/text.hpp"
+// IWYU pragma: private, include <arch/vga/text.hpp>
-#include "arch/vga/text/attribute.hpp"
-#include "arch/vga/text/color.hpp"
-#include "arch/vga/text/flags.hpp"
+#include <arch/vga/text/attribute.hpp>
+#include <arch/vga/text/color.hpp>
+#include <arch/vga/text/flags.hpp>
namespace arch::vga::text
{
diff --git a/arch/x86_64/src/vga/text/device.cpp b/arch/x86_64/arch/vga/text/device.cpp
index dcacd8c..8468358 100644
--- a/arch/x86_64/src/vga/text/device.cpp
+++ b/arch/x86_64/arch/vga/text/device.cpp
@@ -1,9 +1,9 @@
-#include "kapi/cio.hpp"
+#include <arch/boot/boot.hpp>
+#include <arch/boot/ld.hpp>
+#include <arch/vga/crtc.hpp>
+#include <arch/vga/text.hpp>
-#include "arch/boot/boot.hpp"
-#include "arch/boot/ld.hpp"
-#include "arch/vga/crtc.hpp"
-#include "arch/vga/text.hpp"
+#include <kapi/cio.hpp>
#include <bit>
#include <cstddef>
diff --git a/arch/x86_64/include/arch/vga/text/device.hpp b/arch/x86_64/arch/vga/text/device.hpp
index 11b3281..0a0e017 100644
--- a/arch/x86_64/include/arch/vga/text/device.hpp
+++ b/arch/x86_64/arch/vga/text/device.hpp
@@ -1,11 +1,11 @@
#ifndef TEACHOS_X86_64_VGA_TEXT_DEVICE_HPP
#define TEACHOS_X86_64_VGA_TEXT_DEVICE_HPP
-// IWYU pragma: private, include "arch/vga/text.hpp"
+// IWYU pragma: private, include <arch/vga/text.hpp>
-#include "kapi/cio.hpp"
+#include <arch/vga/text/buffer.hpp>
-#include "arch/vga/text/buffer.hpp"
+#include <kapi/cio.hpp>
#include <string_view>
diff --git a/arch/x86_64/include/arch/vga/text/flags.hpp b/arch/x86_64/arch/vga/text/flags.hpp
index 67c6c11..7a29e33 100644
--- a/arch/x86_64/include/arch/vga/text/flags.hpp
+++ b/arch/x86_64/arch/vga/text/flags.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_X86_64_VGA_TEXT_FLAGS_HPP
#define TEACHOS_X86_64_VGA_TEXT_FLAGS_HPP
-// IWYU pragma: private, include "arch/vga/text.hpp"
+// IWYU pragma: private, include <arch/vga/text.hpp>
namespace arch::vga::text
{
diff --git a/arch/x86_64/include/arch/vga/text.hpp b/arch/x86_64/include/arch/vga/text.hpp
deleted file mode 100644
index f81ab60..0000000
--- a/arch/x86_64/include/arch/vga/text.hpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef TEACHOS_X86_64_VGA_TEXT_HPP
-#define TEACHOS_X86_64_VGA_TEXT_HPP
-
-#include "text/attribute.hpp" // IWYU pragma: export
-#include "text/color.hpp" // IWYU pragma: export
-#include "text/common_attributes.hpp" // IWYU pragma: export
-#include "text/device.hpp" // IWYU pragma: export
-#include "text/flags.hpp" // IWYU pragma: export
-
-#endif // TEACHOS_ARCH_X86_64_VIDEO_VGA_TEXT_HPP \ No newline at end of file
diff --git a/arch/x86_64/kapi/boot_modules.cpp b/arch/x86_64/kapi/boot_modules.cpp
new file mode 100644
index 0000000..fb6bf46
--- /dev/null
+++ b/arch/x86_64/kapi/boot_modules.cpp
@@ -0,0 +1,53 @@
+#include <kapi/boot_modules.hpp>
+
+#include <arch/boot/boot.hpp>
+#include <arch/boot/ld.hpp>
+
+#include <kapi/boot.hpp>
+#include <kapi/boot_module/boot_module.hpp>
+#include <kapi/boot_module/boot_module_registry.hpp>
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/print>
+
+#include <multiboot2/information.hpp>
+
+#include <algorithm>
+#include <atomic>
+#include <bit>
+#include <cstdint>
+#include <optional>
+
+namespace kapi::boot_modules
+{
+ namespace
+ {
+ auto constinit registry = std::optional<kapi::boot_modules::boot_module_registry>{};
+ } // namespace
+
+ auto init() -> void
+ {
+ auto static constinit is_initialized = std::atomic_flag{};
+ if (is_initialized.test_and_set())
+ {
+ system::panic("[x86_64] Boot module registry has already been initialized.");
+ }
+
+ kstd::println("[x86_64:BOOT_MODULES] Initializing boot module registry.");
+
+ registry.emplace(kapi::boot_modules::boot_module_registry{});
+
+ auto modules = boot::bootstrap_information.mbi->modules();
+ std::ranges::for_each(modules, [](auto const & module) {
+ registry->add_boot_module(kapi::boot_modules::boot_module{
+ .name = module.string(),
+ .start_address =
+ memory::linear_address{module.start_address + std::bit_cast<std::uintptr_t>(&arch::boot::TEACHOS_VMA)},
+ .size = module.end_address - module.start_address,
+ });
+ });
+
+ set_boot_module_registry(*registry);
+ }
+} // namespace kapi::boot_modules \ No newline at end of file
diff --git a/arch/x86_64/kapi/cio.cpp b/arch/x86_64/kapi/cio.cpp
index 015cf5e..b33c6e0 100644
--- a/arch/x86_64/kapi/cio.cpp
+++ b/arch/x86_64/kapi/cio.cpp
@@ -1,7 +1,7 @@
-#include "kapi/cio.hpp"
+#include <kapi/cio.hpp>
-#include "arch/debug/qemu_output.hpp"
-#include "arch/vga/text.hpp"
+#include <arch/debug/qemu_output.hpp>
+#include <arch/vga/text.hpp>
#include <optional>
diff --git a/arch/x86_64/kapi/cpu.cpp b/arch/x86_64/kapi/cpu.cpp
index 2a0f8f7..40dc228 100644
--- a/arch/x86_64/kapi/cpu.cpp
+++ b/arch/x86_64/kapi/cpu.cpp
@@ -1,12 +1,90 @@
-#include "kapi/cpu.hpp"
+#include <kapi/cpu.hpp>
+
+#include <arch/cpu/initialization.hpp>
+#include <arch/devices/local_apic.hpp>
+
+#include <kapi/acpi.hpp>
+#include <kapi/devices.hpp>
+#include <kapi/devices/cpu.hpp>
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
+
+#include <acpi/acpi.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <atomic>
+#include <ranges>
+#include <utility>
namespace kapi::cpu
{
+ namespace
+ {
+ constexpr auto candidate_flags = ::acpi::processor_local_apic_entry::flags::processor_enabled //
+ | ::acpi::processor_local_apic_entry::flags::online_capable;
+ }
+
+ auto init() -> void
+ {
+ auto static constinit is_initialized = std::atomic_flag{};
+
+ if (is_initialized.test_and_set())
+ {
+ system::panic("[x86_64] CPU has already been initialized.");
+ }
+
+ arch::cpu::initialize_descriptors();
+ arch::cpu::initialize_legacy_interrupts();
+ }
+
auto halt() -> void
{
asm volatile("1: hlt\njmp 1b");
__builtin_unreachable();
}
+ auto discover_topology() -> bool
+ {
+ auto static const cpu_major = kapi::devices::allocate_major_number();
+ auto static const core_major = kapi::devices::allocate_major_number();
+ auto static const interrupt_controller_major = kapi::devices::allocate_major_number();
+
+ auto madt = kapi::acpi::get_table<::acpi::table_signature_v<::acpi::madt>>();
+ if (!madt)
+ {
+ kstd::println("[x86_64:PLT] Failed to find ACPI APIC table");
+ return false;
+ }
+
+ auto lapic_entries = *madt | std::views::filter([](auto const & entry) {
+ return entry.type() == ::acpi::madt_entry::type::processor_local_apic;
+ }) | std::views::transform([](auto const & entry) {
+ return static_cast<::acpi::processor_local_apic_entry const &>(entry);
+ }) | std::views::filter([](auto const & entry) { return static_cast<bool>(entry.flags() & candidate_flags); });
+
+ auto bsp_found = false;
+ auto core_count = 0uz;
+ auto local_apic_address = memory::physical_address{madt->local_interrupt_controller_address()};
+ auto cpu_bus = kstd::make_unique<devices::cpu>(cpu_major, 0);
+
+ for (auto const & apic : lapic_entries)
+ {
+ auto is_bsp = !bsp_found;
+ bsp_found = true;
+ auto core = kstd::make_unique<devices::cpu::core>(core_major, core_count, apic.processor_id(), is_bsp);
+ core->add_child(kstd::make_unique<arch::devices::local_apic>(interrupt_controller_major, core_count, apic.id(),
+ local_apic_address, is_bsp));
+ cpu_bus->add_child(std::move(core));
+ ++core_count;
+ }
+
+ devices::get_root_bus().add_child(std::move(cpu_bus));
+
+ kstd::println("[x86_64:PLT] Found {} CPU cores", core_count);
+ return core_count > 0;
+ }
+
} // namespace kapi::cpu
diff --git a/arch/x86_64/kapi/devices.cpp b/arch/x86_64/kapi/devices.cpp
new file mode 100644
index 0000000..f188c92
--- /dev/null
+++ b/arch/x86_64/kapi/devices.cpp
@@ -0,0 +1,14 @@
+#include <kapi/devices.hpp>
+
+#include <arch/devices/init.hpp>
+
+namespace kapi::devices
+{
+
+ auto init_platform_devices() -> void
+ {
+ arch::devices::init_acpi_devices();
+ arch::devices::init_legacy_devices();
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/arch/x86_64/kapi/interrupts.cpp b/arch/x86_64/kapi/interrupts.cpp
new file mode 100644
index 0000000..85acc0f
--- /dev/null
+++ b/arch/x86_64/kapi/interrupts.cpp
@@ -0,0 +1,16 @@
+#include <kapi/interrupts.hpp>
+
+namespace kapi::interrupts
+{
+
+ auto enable() -> void
+ {
+ asm volatile("sti");
+ }
+
+ auto disable() -> void
+ {
+ asm volatile("cli");
+ }
+
+} // namespace kapi::interrupts \ No newline at end of file
diff --git a/arch/x86_64/kapi/memory.cpp b/arch/x86_64/kapi/memory.cpp
index a9e1216..5b870d5 100644
--- a/arch/x86_64/kapi/memory.cpp
+++ b/arch/x86_64/kapi/memory.cpp
@@ -1,18 +1,19 @@
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
-#include "kapi/boot.hpp"
-#include "kapi/system.hpp"
+#include <arch/boot/boot.hpp>
+#include <arch/boot/ld.hpp>
+#include <arch/cpu/registers.hpp>
+#include <arch/memory/higher_half_mapper.hpp>
+#include <arch/memory/kernel_mapper.hpp>
+#include <arch/memory/page_table.hpp>
+#include <arch/memory/page_utilities.hpp>
+#include <arch/memory/region_allocator.hpp>
-#include "arch/boot/boot.hpp"
-#include "arch/boot/ld.hpp"
-#include "arch/cpu/registers.hpp"
-#include "arch/memory/higher_half_mapper.hpp"
-#include "arch/memory/kernel_mapper.hpp"
-#include "arch/memory/page_table.hpp"
-#include "arch/memory/page_utilities.hpp"
-#include "arch/memory/region_allocator.hpp"
+#include <kapi/boot.hpp>
+#include <kapi/system.hpp>
#include <kstd/print>
+#include <kstd/units>
#include <multiboot2/constants.hpp>
#include <multiboot2/information.hpp>
@@ -28,6 +29,8 @@
#include <span>
#include <utility>
+using namespace kstd::units_literals;
+
namespace kapi::memory
{
@@ -46,13 +49,14 @@ namespace kapi::memory
}
auto const & mbi = boot::bootstrap_information.mbi;
- auto mbi_span = std::span{std::bit_cast<std::byte *>(mbi), mbi->size_bytes()};
+ auto mbi_span = std::span{std::bit_cast<std::byte *>(mbi), static_cast<std::size_t>(mbi->size())};
auto image_span = std::span{&arch::boot::_start_physical, &arch::boot::_end_physical};
return arch::memory::region_allocator::memory_information{
.image_range = std::make_pair(physical_address{&image_span.front()}, physical_address{&image_span.back()}),
.mbi_range = std::make_pair(physical_address{&mbi_span.front()}, physical_address{&mbi_span.back()}),
.memory_map = *memory_map,
+ .mbi = mbi,
};
}
@@ -106,10 +110,10 @@ namespace kapi::memory
[[maybe_unused]] auto remap_multiboot_information(page_mapper & mapper) -> void
{
auto mbi_base = std::bit_cast<std::uintptr_t>(boot::bootstrap_information.mbi);
- auto mbi_size = boot::bootstrap_information.mbi->size_bytes();
+ auto mbi_size = boot::bootstrap_information.mbi->size();
auto mbi_physical_start = physical_address{mbi_base & ~std::bit_cast<std::uintptr_t>(&arch::boot::TEACHOS_VMA)};
auto mbi_virtual_start = linear_address{mbi_base};
- auto mbi_block_count = (mbi_size + PLATFORM_FRAME_SIZE - 1) / PLATFORM_FRAME_SIZE;
+ auto mbi_block_count = (mbi_size + frame::size - 1_B) / frame::size;
for (auto i = 0uz; i < mbi_block_count; ++i)
{
@@ -119,6 +123,24 @@ namespace kapi::memory
}
}
+ [[maybe_unused]] auto remap_bootloader_modules(page_mapper & mapper) -> void
+ {
+ std::ranges::for_each(boot::bootstrap_information.mbi->modules(), [&mapper](auto const & module) {
+ auto module_physical_start = physical_address{module.start_address};
+ auto module_virtual_start =
+ linear_address{module.start_address + std::bit_cast<std::uintptr_t>(&arch::boot::TEACHOS_VMA)};
+ auto module_size = static_cast<kstd::units::bytes>(module.end_address - module.start_address);
+ auto module_block_count = (module_size + frame::size - 1_B) / frame::size;
+
+ for (auto i = 0uz; i < module_block_count; ++i)
+ {
+ auto page = page::containing(module_virtual_start) + i;
+ auto frame = frame::containing(module_physical_start) + i;
+ mapper.map(page, frame, page_mapper::flags::writable | page_mapper::flags::supervisor_only);
+ }
+ });
+ }
+
[[maybe_unused]] auto handoff_to_kernel_pmm(frame_allocator & new_allocator) -> void
{
auto memory_map = boot::bootstrap_information.mbi->memory_map();
@@ -128,7 +150,7 @@ namespace kapi::memory
}))
{
auto start = frame::containing(physical_address{region.base});
- auto count = region.size_in_B / page::size;
+ auto count = kstd::units::bytes{region.size_in_B} / page::size;
new_allocator.release_many({start, count});
}
@@ -148,14 +170,22 @@ namespace kapi::memory
[&](auto frame) { new_allocator.mark_used(frame); });
auto mbi_base = std::bit_cast<std::uintptr_t>(boot::bootstrap_information.mbi);
- auto mbi_size = boot::bootstrap_information.mbi->size_bytes();
+ auto mbi_size = boot::bootstrap_information.mbi->size();
auto mbi_address = physical_address{mbi_base & ~std::bit_cast<std::uintptr_t>(&arch::boot::TEACHOS_VMA)};
auto mbi_start = frame::containing(mbi_address);
auto mbi_end = frame::containing(mbi_address + mbi_size) + 1;
- // TODO BA-FS26: Protect MB2 boot modules
-
std::ranges::for_each(std::views::iota(mbi_start, mbi_end), [&](auto frame) { new_allocator.mark_used(frame); });
+
+ std::ranges::for_each(boot::bootstrap_information.mbi->modules(), [&](auto const & module) {
+ auto module_physical_start = physical_address{module.start_address};
+ auto module_size = module.end_address - module.start_address;
+ auto module_start = frame::containing(module_physical_start);
+ auto module_end = frame::containing(module_physical_start + module_size) + 1;
+
+ std::ranges::for_each(std::views::iota(module_start, module_end),
+ [&](auto frame) { new_allocator.mark_used(frame); });
+ });
}
} // namespace
@@ -196,6 +226,7 @@ namespace kapi::memory
remap_kernel(*higher_half_mapper);
remap_vga_text_mode_buffer(*higher_half_mapper);
remap_multiboot_information(*higher_half_mapper);
+ remap_bootloader_modules(*higher_half_mapper);
auto current_cr3 = arch::cpu::cr3::read();
auto old_pml4 = static_cast<arch::memory::page_table *>(current_cr3.address());
diff --git a/arch/x86_64/kapi/system.cpp b/arch/x86_64/kapi/system.cpp
index ca4418e..73a77e5 100644
--- a/arch/x86_64/kapi/system.cpp
+++ b/arch/x86_64/kapi/system.cpp
@@ -1,122 +1,8 @@
-#include "kapi/system.hpp"
-
-#include "arch/cpu/global_descriptor_table.hpp"
-#include "arch/cpu/segment_descriptor.hpp"
-#include "arch/cpu/task_state_segment.hpp"
-
-#include <kstd/print>
-
-#include <bit>
-#include <cstdint>
+#include <kapi/system.hpp>
namespace kapi::system
{
- namespace
- {
- constexpr auto gdt_null_descriptor = arch::cpu::segment_descriptor{};
-
- constexpr auto gdt_kernel_code_descriptor = arch::cpu::segment_descriptor{
- .limit_low = 0xffff,
- .base_low = 0,
- .accessed = false,
- .read_write = false,
- .direction_or_conforming = false,
- .executable = true,
- .type = arch::cpu::segment_type::code_or_data,
- .privilege_level = 0,
- .present = true,
- .limit_high = 0xf,
- .long_mode = true,
- .protected_mode = false,
- .granularity = arch::cpu::segment_granularity::page,
- .base_high = 0,
- };
-
- constexpr auto gdt_kernel_data_descriptor = arch::cpu::segment_descriptor{
- .limit_low = 0xffff,
- .base_low = 0,
- .accessed = false,
- .read_write = true,
- .direction_or_conforming = false,
- .executable = false,
- .type = arch::cpu::segment_type::code_or_data,
- .privilege_level = 0,
- .present = true,
- .limit_high = 0xf,
- .long_mode = false,
- .protected_mode = true,
- .granularity = arch::cpu::segment_granularity::page,
- .base_high = 0,
- };
-
- constexpr auto gdt_user_code_descriptor = arch::cpu::segment_descriptor{
- .limit_low = 0xffff,
- .base_low = 0,
- .accessed = false,
- .read_write = false,
- .direction_or_conforming = false,
- .executable = true,
- .type = arch::cpu::segment_type::code_or_data,
- .privilege_level = 3,
- .present = true,
- .limit_high = 0xf,
- .long_mode = true,
- .protected_mode = false,
- .granularity = arch::cpu::segment_granularity::page,
- .base_high = 0,
- };
-
- constexpr auto gdt_user_data_descriptor = arch::cpu::segment_descriptor{
- .limit_low = 0xffff,
- .base_low = 0,
- .accessed = false,
- .read_write = true,
- .direction_or_conforming = false,
- .executable = false,
- .type = arch::cpu::segment_type::code_or_data,
- .privilege_level = 3,
- .present = true,
- .limit_high = 0xf,
- .long_mode = false,
- .protected_mode = false,
- .granularity = arch::cpu::segment_granularity::page,
- .base_high = 0,
- };
- } // namespace
-
- auto memory_initialized() -> void
- {
- auto static tss = arch::cpu::task_state_segment{};
- auto static tss_descriptor = arch::cpu::system_segment_descriptor{
- {
- .limit_low = (sizeof(tss) - 1) & 0xffff, // NOLINT(readability-magic-numbers)
- .base_low = std::bit_cast<std::uintptr_t>(&tss) & 0xffffff, // NOLINT(readability-magic-numbers)
- .accessed = false,
- .read_write = false,
- .direction_or_conforming = false,
- .executable = false,
- .type = arch::cpu::segment_type::system,
- .privilege_level = 0,
- .present = true,
- .limit_high = ((sizeof(tss) - 1) >> 16) & 0xf, // NOLINT(readability-magic-numbers)
- .long_mode = false,
- .protected_mode = false,
- .granularity = arch::cpu::segment_granularity::byte,
- .base_high = (std::bit_cast<std::uintptr_t>(&tss) >> 24) & 0xff, // NOLINT(readability-magic-numbers)
- },
- (std::bit_cast<std::uintptr_t>(&tss) >> 32) & 0xffff'ffff, // NOLINT(readability-magic-numbers)
- };
-
- auto static gdt = arch::cpu::global_descriptor_table{
- gdt_null_descriptor, gdt_kernel_code_descriptor, gdt_kernel_data_descriptor,
- gdt_user_code_descriptor, gdt_user_data_descriptor, tss_descriptor,
- };
-
- kstd::println("[x86_64:SYS] Reloading Global Descriptor Table.");
- gdt.load(1, 2);
-
- kstd::println("[x86_64:SYS] TODO: initialize Interrupt Descriptor Table.");
- }
+ auto memory_initialized() -> void {}
} // namespace kapi::system \ No newline at end of file
diff --git a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp b/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp
deleted file mode 100644
index 07110c8..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_GATE_DESCRIPTOR_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_GATE_DESCRIPTOR_HPP
-
-#include "arch/context_switching/interrupt_descriptor_table/idt_flags.hpp"
-#include "arch/context_switching/interrupt_descriptor_table/ist_offset.hpp"
-#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp"
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- __extension__ typedef __int128 int128_t;
- __extension__ typedef unsigned __int128 uint128_t;
-
- /**
- * @brief Defines helper function for all states and the actual data the gate descriptor can have.
- */
- struct [[gnu::packed]] gate_descriptor
- {
- /**
- * @brief Default Constructor.
- */
- gate_descriptor() = default;
-
- /**
- * @brief Constructor.
- *
- * @note Created gate descriptor copies the given bytes into these components ending with a 32 bit reserved
- * field that has to be used, because the 64-bit gate descriptor needs to be big enough for two 32-bit gate
- * descriptor.
- * - 16 bit Segment Selector
- * - 3 bit Interrupt Stack Table Offset
- * - 8 bit Type and Flags
- * - 64 bit Offset
- *
- * @param flags Copies the bits set from the given data into the individual components of a gate
- * descriptor.
- */
- explicit gate_descriptor(uint128_t flags);
-
- /**
- * @brief Constructor.
- *
- * @param selector, ist, flags, offset Copies the bits set from the given data into the individual components of
- * a gate descriptor.
- */
- gate_descriptor(segment_selector selector, ist_offset ist, idt_flags flags, uint64_t offset);
-
- /**
- * @brief Allows to compare the underlying bits of two instances.
- *
- * @param other Other instance that we want to compare with.
- * @return Whether the underlying set bits of both types are the same.
- */
- auto operator==(gate_descriptor const & other) const -> bool = default;
-
- private:
- // The order in private variables starts for the first variable being the rightmost bit.
- uint16_t _offset_1 = {}; ///< Lower 16 bits of handler function address (0 - 15)
- segment_selector _selector = {}; ///< Segment selector (16 - 31)
- ist_offset _ist = {}; ///< Interrupt Stack Table offset (32 - 39)
- idt_flags _flags = {}; ///< Gate Type and Flags (40 - 47)
- uint64_t _offset_2 : 48 = {}; ///< Upper 48 bits of handler function address (48 - 95)
- uint32_t : 32; ///< Reserved field used to ensure this struct is 128 bits big (96 - 127)
- };
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_GATE_DESCRIPTOR_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/idt_flags.hpp b/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/idt_flags.hpp
deleted file mode 100644
index 5104c36..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/idt_flags.hpp
+++ /dev/null
@@ -1,81 +0,0 @@
-
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_IDT_FLAGS_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_IDT_FLAGS_HPP
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- /**
- * @brief Defines helper function for all states that the access byte field of a segment descriptor can
- * have.
- */
- struct [[gnu::packed]] idt_flags
- {
- /**
- * @brief Possible set bits in our underlying bits and the meaning when they are set.
- */
- enum bitset : uint8_t
- {
- INTERRUPT_GATE = 0b01110, ///< The actual type of gate segment is a interrupt gate.
- TRAP_GATE = 0b01111, ///< The actual type of gate segment is a trap gate.
- DESCRIPTOR_LEVEL_KERNEL =
- 0U << 5U, ///< Highest privileged level used by the kernel to allow for full access of resources.
- DESCRIPTOR_LEVEL_ADMIN =
- 1U << 5U, ///< Restricts access to own application and thoose of lower privilege. Should only be used if more
- ///< than two privilege levels are required, otherwise using Level 3 and Level 0 is recommended.
- DESCRIPTOR_LEVEL_PRIVILEGED_USER =
- 2U << 5U, ///< Restricts access to own application and thoose of lower privilege. Should only be used if more
- ///< than two privilege levels are required, otherwise using Level 3 and Level 0 is recommended.
- DESCRIPTOR_LEVEL_USER = 3U << 5U, ///< Restricts access to only application and their specific memory.
- PRESENT = 1U << 7U, ///< Present bit; Allows an entry to refer to a valid segment.
- ///< Must be set (1) for any valid segment.
- };
-
- /**
- * @brief Default Constructor.
- */
- idt_flags() = default;
-
- /**
- * @brief Constructor.
- *
- * @param flags Allows to set flags for the access byte field using the unscoped enum contained in this class, used
- * to allow for direct integer conversion. This value is saved and can later be used to check whether certain flags
- * are enabled or not using contains_flags method.
- */
- idt_flags(uint8_t flags);
-
- /**
- * @brief Checks if the given std::bitset is a subset or equivalent to the underlying data.
- *
- * @note Meaning that all bits that are set in the given std::bitset also have to be set in the underlyng
- * data. Any additional bits that are set are not relevant.
- *
- * @param other Flags that we want to compare against and check if the underlying data has the same bits set.
- * @return Whether the given flags are a subset or equivalent with the underlying data.
- */
- auto contains_flags(std::bitset<8U> other) const -> bool;
-
- /**
- * @brief Allows to compare the underlying bits of two instances.
- *
- * @param other Other instance that we want to compare with.
- * @return Whether the underlying set bits of both types are the same.
- */
- auto operator==(idt_flags const & other) const -> bool = default;
-
- /**
- * @brief Combines all bits that are set in the std::bitset flags with the bits already set in the underlying data.
- *
- * @param other Additional bits that should be set.
- */
- auto operator|=(std::bitset<8U> other) -> void;
-
- private:
- uint8_t _flags = {}; ///< Underlying bits used to read the flags from.
- };
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_IDT_FLAGS_HPP \ No newline at end of file
diff --git a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp b/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp
deleted file mode 100644
index b388e0e..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_INTERRUPT_DESCRIPTOR_TABLE_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_INTERRUPT_DESCRIPTOR_TABLE_HPP
-
-#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- /**
- * @brief Updates the IDTR with the created interrupt descriptor table. If it has not been created yet this
- * method will create it.
- */
- auto update_interrupt_descriptor_table_register() -> void;
-
- /**
- * @brief Creates the interrupt descriptor table, with the minimum required configuration. If this method is called
- * more than once, the previously created instance is returned instead.
- *
- * @return Reference to the created interrupt_descriptor_table.
- */
- auto get_or_create_interrupt_descriptor_table() -> interrupt_descriptor_table &;
-
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_INTERRUPT_DESCRIPTOR_TABLE_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp b/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp
deleted file mode 100644
index 7fe933b..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_INTERRUPT_DESCRIPTOR_TABLE_POINTER_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_INTERRUPT_DESCRIPTOR_TABLE_POINTER_HPP
-
-#include "arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp"
-#include "arch/stl/vector.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- using interrupt_descriptor_table = stl::vector<gate_descriptor>;
-
- /**
- * @brief Represents a pointer to the Interrupt Descriptor Table (IDT).
- *
- * This structure is used to store the base address and length of the IDT.
- */
- struct [[gnu::packed]] interrupt_descriptor_table_pointer
- {
- /**
- * @brief Default constructor.
- */
- interrupt_descriptor_table_pointer() = default;
-
- /**
- * @brief Constructor.
- */
- interrupt_descriptor_table_pointer(uint16_t table_length, gate_descriptor * address);
-
- /**
- * @brief Defaulted three-way comparsion operator.
- */
- auto operator<=>(interrupt_descriptor_table_pointer const & other) const -> std::strong_ordering = default;
-
- private:
- uint16_t table_length = {}; ///< The amount of segment descriptor entries in the global descriptor table - 1.
- gate_descriptor * address = {}; ///< Non-owning pointer to the IDT base address.
- };
-
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_INTERRUPT_DESCRIPTOR_TABLE_POINTER_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/ist_offset.hpp b/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/ist_offset.hpp
deleted file mode 100644
index e45bcf4..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/ist_offset.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_IST_OFFSET_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_IST_OFFSET_HPP
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- /**
- * @brief Defines helper function for all states that the interrupt stack table offset field of a gate descriptor can
- * have. Is automatically increased to one byte in size, to include the following 5 reserved bits in the gate
- * descriptor.
- */
- struct [[gnu::packed]] ist_offset
- {
- /**
- * @brief Default Constructor.
- */
- ist_offset() = default;
-
- /**
- * @brief Constructor.
- *
- * @param offset Offset into the interrupt stack table. A value of of 0 means we do not switch stacks, whereas 1 - 7
- * mean we switch to the n-th stack in the Interrupt Stack Table, contained in the TSS if the gate descriptor that
- * contains this field is called.
- */
- ist_offset(uint8_t offset);
-
- /**
- * @brief Allows to compare the underlying set bits of two instances.
- *
- * @param other Other instance that we want to compare with.
- * @return Whether the underlying set bits of both types are the same.
- */
- auto operator==(ist_offset const & other) const -> bool = default;
-
- private:
- uint8_t _ist : 3 = {}; ///< Offset into the interrupt stack table. A value of of 0 means we do not switch stacks,
- ///< whereas 1 - 7 mean we switch to the n-th stack in the Interrupt Stack Table, contained
- ///< in the TSS if the gate descriptor that contains this field is called.
- };
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_IST_OFFSET_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/segment_selector.hpp b/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/segment_selector.hpp
deleted file mode 100644
index ea8c145..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/interrupt_descriptor_table/segment_selector.hpp
+++ /dev/null
@@ -1,105 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_SEGMENT_SELECTOR_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_SEGMENT_SELECTOR_HPP
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- /**
- * @brief Represents a segment selector in the x86_64 architecture, which points to a valid code segment in the global
- * descriptor table.
- *
- * A segment selector is a 16-bit identifier used to select a segment descriptor
- * from the Global Descriptor Table (GDT) or the Local Descriptor Table (LDT).
- * It contains an index, a table indicator (TI), and a requested privilege level (RPL).
- */
- struct [[gnu::packed]] segment_selector
- {
- /**
- * @brief Possible set bits in our underlying bits and the meaning when they are set.
- */
- enum bitset : uint8_t
- {
- REQUEST_LEVEL_KERNEL =
- 0U << 0U, ///< Highest privileged level used by the kernel to allow for full access of resources.
- REQUEST_LEVEL_ADMIN =
- 1U << 0U, ///< Restricts access to own application and thoose of lower privilege. Should only be used if more
- ///< than two privilege levels are required, otherwise using Level 3 and Level 0 is recommended.
- REQUEST_LEVEL_PRIVILEGED_USER =
- 2U << 0U, ///< Restricts access to own application and thoose of lower privilege. Should only be used if more
- ///< than two privilege levels are required, otherwise using Level 3 and Level 0 is recommended.
- REQUEST_LEVEL_USER = 3U << 0U, ///< Restricts access to only application and their specific memory.
- LOCAL_DESCRIPTOR_TABLE = 1U << 2U, ///< Wheter the index referes to an entry in the local or global descriptor
- ///< table. If enabled the index points to a local descriptor table, if it is
- ///< cleared it referes to a global descriptor table instead.
- };
-
- /**
- * @brief Default constructor.
- */
- segment_selector() = default;
-
- /**
- * @brief Constructor.
- *
- * @param index Index into the local or global descriptor table. Processor multiplies the index value by 8 (number
- * of bytes in 32-bit segment descriptor) and adds the result to the base GDT or LDT address.
- * @param flags Allows to set flags for the flags field using the unscoped enum contained in this class, used to
- * allow for direct integer conversion.
- */
- constexpr segment_selector(uint16_t index, uint8_t flags)
- : _flags(flags)
- , _index(index)
- {
- // Nothing to do.
- }
-
- /**
- * @brief Checks if the given std::bitset is a subset or equivalent to the underlying data.
- *
- * @note Meaning that all bits that are set in the given std::bitset also have to be set in the underlyng
- * data. Any additional bits that are set are not relevant.
- *
- * @param other Flags that we want to compare against and check if the underlying data has the same bits set.
- * @return Whether the given flags are a subset or equivalent with the underlying data.
- */
- auto contains_flags(std::bitset<3U> other) const -> bool;
-
- /**
- * @brief Gets the index into the global descriptor table or the local descriptor table this segment selector is
- * pointing too.
- *
- * @return Underlying value of the index field, bit 3 - 16.
- */
- [[gnu::section(".user_text")]]
- auto get_index() const -> uint16_t;
-
- /**
- * @brief Defaulted three-way comparsion operator.
- */
- auto operator<=>(segment_selector const & other) const -> std::strong_ordering = default;
-
- /**
- * @brief Combines all bits that are set in the std::bitset flags with the bits already set in the underlying data.
- *
- * @param other Additional bits that should be set.
- */
- auto operator|=(std::bitset<3U> other) -> void;
-
- /**
- * @brief Cast the underlying data into a combined 16-bit form, that contains all data.
- *
- * @return Underlying value combined into it's full size.
- */
- operator uint16_t() const;
-
- private:
- uint8_t _flags : 3 = {}; ///< Underlying bits used to read the flags from.
- uint16_t _index
- : 13 = {}; ///< Index into the local or global descriptor table. Processor multiplies the index value by 16
- ///< (number of bytes in segment descriptor) and adds the result to the base address.
- };
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_INTERRUPT_DESCRIPTOR_TABLE_SEGMENT_SELECTOR_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/main.hpp b/arch/x86_64/pre/include/arch/context_switching/main.hpp
deleted file mode 100644
index f8477ea..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/main.hpp
+++ /dev/null
@@ -1,51 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_MAIN_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_MAIN_HPP
-
-#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp"
-#include "arch/context_switching/segment_descriptor_table/global_descriptor_table.hpp"
-
-namespace teachos::arch::context_switching
-{
- /**
- * @brief Contains the references to the tables required for context switching
- */
- struct descriptor_tables
- {
- segment_descriptor_table::global_descriptor_table & gdt; ///< Reference to the global descriptor table.
- interrupt_descriptor_table::interrupt_descriptor_table & idt; ///< Reference to the interrupt descriptor table.
- };
-
- /**
- * @brief Creates the Interrupt Descriptor Table and Global Descriptor Table as a static variable the first time this
- * method is called and update IDTR and GDTR registers values.
- *
- * @note Subsequent calls after the first one, will simply return the previously created tables, but not update the
- * registers again.
- *
- * @return References to the statically created Interrupt Descriptor and Global Descriptor Table.
- */
- auto initialize_descriptor_tables() -> descriptor_tables;
-
- /**
- * @brief Switches from the current Kernel Mode (Level 0) to User Mode (Level 3). Will simply use predefined Segment
- * Selectors for the User Data and User Code Segment, which are Index 3 and 4 in the GDT respectively.
- */
- auto switch_to_user_mode() -> void;
-
- /**
- * @brief Switches from the current Code and Data Segment to the given Code and Data Segment.
- *
- * @note This method will additionally call initialize_descriptor_tables, to ensure the GDTR and IDTR have been setup
- * correctly before attempting to switch the context. This switch is achieved using a far return, which will once
- * executed call the given void function.
- *
- * @param data_segment Data Segment that the SS, DS; ES, FS and GS register will be set too.
- * @param code_segment Code Segment that the CS register will be set too.
- * @param return_function Function that will be called once the switch has been achieved.
- */
- auto switch_context(interrupt_descriptor_table::segment_selector data_segment,
- interrupt_descriptor_table::segment_selector code_segment, void (*return_function)()) -> void;
-
-} // namespace teachos::arch::context_switching
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_MAIN_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp b/arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp
deleted file mode 100644
index f507c61..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/syscall/main.hpp
+++ /dev/null
@@ -1,91 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_MAIN_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_MAIN_HPP
-
-#include <cstdint>
-
-namespace teachos::arch::context_switching::syscall
-{
- /**
- * @brief Possible syscall implementation that should actually be called.
- *
- * @note Attempts to reflect the Linux interface partially. See https://filippo.io/linux-syscall-table/ for more
- * information.
- */
- enum class type : uint64_t
- {
- WRITE = 1U, ///< Loads the arg_0 parameter as an address pointing to a const char array, which will then be printed
- ///< onto the VGA buffer screen.
- EXPAND_HEAP = 2U, /// Expands the User Heap by additonally mapping 100 KiB of virtual page memory. Ignores the
- /// parameters and uses them as out parameters instead, where arg_0 is the start of the newly
- /// mapped heap area and arg_1 is the size of the entire area. Can be less than 100 KiB if less
- /// space remains.
- ASSERT = 3U, /// Loads the arg_0 parameter as a boolean which needs to be true or it will print the message in
- /// arg_1 parameter onto the VGA buffer screen, keep it as a nullptr if it shouldn't print anything
- /// and then it halts the further execution of the application.
- };
-
- /**
- * @brief Possible error codes that can be returned by the different syscall methods called depending on the type
- * enum.
- */
- enum class error : uint8_t
- {
- OK = 0U, ///< No error occured in syscall.
- OUT_OF_MEMORY = 1U, ///< Expanding heap failed because we have run out of mappable virtual address space.
- };
-
- /**
- * @brief Allows to convert the error enum type into a boolean directly. Where any code besides 0 being no error, will
- * return true. The remaining errors return true.
- *
- * @param e Error code that was returned by the syscall.
- * @return Return true if there was no error and false otherwise.
- */
- constexpr bool operator!(error e)
- {
- return e == error::OK;
- }
-
- /**
- * @brief Maximum amount of arguments that can be passed to a syscall. Default value is 0 and arguments are only ever
- * used depending on the actual enum type and if the method requires thoose parameters.
- */
- struct arguments
- {
- uint64_t arg_0{}; ///< First optional paramter to the syscall representing the RDI register.
- uint64_t arg_1{}; ///< Second optional paramter to the syscall representing the RSI register.
- uint64_t arg_2{}; ///< Third optional paramter to the syscall representing the RDX register.
- uint64_t arg_3{}; ///< Fourth optional paramter to the syscall representing the R10 register.
- uint64_t arg_4{}; ///< Fifth optional paramter to the syscall representing the R8 register.
- uint64_t arg_5{}; ///< Sixth optional paramter to the syscall representing the R9 register.
- };
-
- /**
- * @brief Response of a systemcall always containin an error code, signaling if the syscall even succeeded or not.
- * Additionally it may contain up to 6 return values in the values struct.
- */
- struct response
- {
- error error_code; ///< Error code returned by the syscall. If it failed all the values will be 0.
- arguments values = {}; ///< Optional return values of the syscall implementation.
- };
-
- /**
- * @brief Calls the method associated with the given syscall number and passes the given optional arguments to it,
- * over the RDI, RSI, RDX, R10, R8 and R9 register.
- *
- * @param syscall_number Syscall method that should be called. See enum values in type for possible implemented
- * methods.
- * @param args Optional arguments passable to the different syscall methods, called depending on the syscall_number.
- * Not passing the required parameters to the method, will result in passing 0 instead, which might make the fail or
- * not function correctly.
- * @return The syscall implementation always returns a bool-convertable error code converting to true if the syscall
- * failed or false if it didn't. Additionally it might pase additional values besides the error code, they will be set
- * in the arguments struct. So the value can be read and used for further processing.
- */
- [[gnu::section(".user_text")]]
- auto syscall(type syscall_number, arguments args = {}) -> response;
-
-} // namespace teachos::arch::context_switching::syscall
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_MAIN_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp b/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp
deleted file mode 100644
index 8cb468a..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_enable.hpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_ENABLE_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_ENABLE_HPP
-
-namespace teachos::arch::context_switching::syscall
-{
- /**
- * @brief Enables and sets up internal CPU registers to allow for syscall to function correctly and switch context
- * temporarily back to the kernel level.
- *
- * @note Configures the Model Specific Register required by syscall (LSTAR, FMASK, STAR) with the correct values so
- * that the syscall_handler is called and sets the System Call Extension bit on the EFER flags to allow for syscall
- * to be used.
- */
- auto enable_syscall() -> void;
-
-} // namespace teachos::arch::context_switching::syscall
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_ENABLE_HPP
diff --git a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp b/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp
deleted file mode 100644
index 2e7bcd1..0000000
--- a/arch/x86_64/pre/include/arch/context_switching/syscall/syscall_handler.hpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_HANDLER_HPP
-#define TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_HANDLER_HPP
-
-#include <cstdint>
-
-namespace teachos::arch::context_switching::syscall
-{
- /**
- * @brief Handler for SYSCALL instruction. Calls a specific implementation based
- * on the register RAX.
- *
- * @return Returns with LEAVE, SYSRETQ
- */
- auto syscall_handler() -> void;
-
-} // namespace teachos::arch::context_switching::syscall
-
-#endif // TEACHOS_ARCH_X86_64_CONTEXT_SWITCHING_SYSCALL_SYSCALL_HANDLER_HPP
diff --git a/arch/x86_64/pre/include/arch/interrupt_handling/generic_interrupt_handler.hpp b/arch/x86_64/pre/include/arch/interrupt_handling/generic_interrupt_handler.hpp
deleted file mode 100644
index 15b35c1..0000000
--- a/arch/x86_64/pre/include/arch/interrupt_handling/generic_interrupt_handler.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_INTERRUPT_HANDLING_GENERIC_INTERRUPT_HANDLER_HPP
-#define TEACHOS_ARCH_X86_64_INTERRUPT_HANDLING_GENERIC_INTERRUPT_HANDLER_HPP
-
-#include <cstdint>
-
-namespace teachos::arch::interrupt_handling
-{
- /**
- * @brief Represents the CPU state during an interrupt.
- *
- * Some interrupts push an error code, while others do not. The full list
- * of which vector number contains the error code can be found here: https://wiki.osdev.org/Exceptions
- */
- struct [[gnu::packed]] interrupt_frame
- {
- // uint64_t error_code; ///< Error code only pushed by some exceptions, therefore it is commented out.
- uint64_t ip; ///< Instruction pointer at the time of the interrupt.
- uint64_t cs; ///< Code segment selector indicating privilege level.
- uint64_t flags; ///< CPU flags (RFLAGS) storing processor state.
- uint64_t sp; ///< Stack pointer at the time of the interrupt.
- uint64_t ss; ///< Stack segment selector, usually unused in 64-bit mode.
- };
-
- /**
- * @brief Generic interrupt handler function.
- *
- * @param frame Pointer to the interrupt frame containing CPU state.
- */
- [[gnu::interrupt]]
- auto generic_interrupt_handler(interrupt_frame * frame) -> void;
-
-} // namespace teachos::arch::interrupt_handling
-
-#endif // TEACHOS_ARCH_X86_64_INTERRUPT_HANDLING_GENERIC_INTERRUPT_HANDLER_HPP
diff --git a/arch/x86_64/pre/include/arch/kernel/cpu/call.hpp b/arch/x86_64/pre/include/arch/kernel/cpu/call.hpp
deleted file mode 100644
index 3c43304..0000000
--- a/arch/x86_64/pre/include/arch/kernel/cpu/call.hpp
+++ /dev/null
@@ -1,30 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_CPU_JMP_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_CPU_JMP_HPP
-
-#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp"
-
-#include <cstdint>
-
-namespace teachos::arch::kernel::cpu
-{
- /**
- * @brief Far Pointer. Address to function located in another code segment.
- */
- struct far_pointer
- {
- void (*function)(); ///< Address of the function we want to call. (0-63)
- context_switching::interrupt_descriptor_table::segment_selector
- selector; ///< Segment selector pointing to the GDT entry we want to load into register CS. (64-79)
- };
-
- /**
- * @brief Far call - A call to an instruction located in a different segment than the current code segment but at the
- * same privilege level.
- *
- * @param pointer 64-bit operand size far pointer that we want to call.
- */
- auto call(far_pointer pointer) -> void;
-
-} // namespace teachos::arch::kernel::cpu
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_CPU_JMP_HPP
diff --git a/arch/x86_64/pre/include/arch/kernel/cpu/idtr.hpp b/arch/x86_64/pre/include/arch/kernel/cpu/idtr.hpp
deleted file mode 100644
index cb800d0..0000000
--- a/arch/x86_64/pre/include/arch/kernel/cpu/idtr.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_CPU_IDTR_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_CPU_IDTR_HPP
-
-#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp"
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::kernel::cpu
-{
- /**
- * @brief Returns the value in the IDTR register.
- *
- * @return Value of IDTR register.
- */
- auto store_interrupt_descriptor_table()
- -> context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer;
-
- /**
- * @brief Loads the interrupt_descriptor_table_pointer into the interrupt descriptor table register (IDTR).
- */
- auto load_interrupt_descriptor_table(
- context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer const & idt_pointer) -> void;
-
-} // namespace teachos::arch::kernel::cpu
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_CPU_IDTR_HPP
diff --git a/arch/x86_64/pre/include/arch/kernel/cpu/if.hpp b/arch/x86_64/pre/include/arch/kernel/cpu/if.hpp
deleted file mode 100644
index 48707dc..0000000
--- a/arch/x86_64/pre/include/arch/kernel/cpu/if.hpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_CPU_IF_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_CPU_IF_HPP
-
-namespace teachos::arch::kernel::cpu
-{
- /**
- * @brief Sets the interrupt flag (IF) in the EFLAGS register.
- * This allows the processor to respond to maskable hardware interrupts.
- */
- auto set_interrupt_flag() -> void;
-
- /**
- * @brief Clears the interrupt flag (IF) in the EFLAGS register.
- * This will stop the processor to respond to maskable hardware interrupts and needs to be done before changing the
- * Interrupt Descriptor Table with lidt.
- */
- auto clear_interrupt_flag() -> void;
-
-} // namespace teachos::arch::kernel::cpu
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_CPU_IF_HPP
diff --git a/arch/x86_64/pre/include/arch/kernel/cpu/msr.hpp b/arch/x86_64/pre/include/arch/kernel/cpu/msr.hpp
deleted file mode 100644
index 99d6378..0000000
--- a/arch/x86_64/pre/include/arch/kernel/cpu/msr.hpp
+++ /dev/null
@@ -1,64 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_CPU_NXE_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_CPU_NXE_HPP
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::kernel::cpu
-{
- /**
- * @brief Important flags that can be writen into the Extended Feature Enable Register (EFER).
- *
- * @note EFER is a model-specific register allowing to configure CPU extensions. Only the most important extensions
- * are listed below, the rest are excluded for brevity. See https://en.wikipedia.org/wiki/Control_register#EFER for
- * more information.
- */
- enum class efer_flags : uint64_t
- {
- SCE = 1UL << 0UL, ///< System Call Extensions.
- LME = 1UL << 8UL, ///< Long Mode Enabled.
- LMA = 1UL << 10UL, ///< Long Mode Active.
- NXE = 1UL << 11UL, ///< No-Execute Enable.
- SVME = 1UL << 12UL, ///< Secure Virtual Machine Enable.
- LMSLE = 1UL << 13UL, ///< Long Mode Segment Limit Enable.
- FFXSR = 1UL << 14UL, ///< Fast FXSAVE/FXSTOR.
- TCE = 1UL << 15UL, ///< Translation Cache Extension.
- };
-
- /**
- * @brief Reads a 64-bit from the Model-Specific Register (MSR).
- *
- * @note This function reads the value of an MSR specified by the given address. It combines the lower and upper
- * 32-bits of the MSR value read using the 'rdmsr' instruction and returns it as a 64-bit unsigned integer.
- *
- * @param msr The address of the MSR to read.
- * @return The 64-bit value read from the MSR.
- */
- auto read_msr(uint32_t msr) -> uint64_t;
-
- /**
- * @brief Writes a 64-bit value to a Model-Specific Register (MSR).
- *
- * @note This function writes a 64-bit value to the MSR specified by the given address.
- * It splits the 64-bit value into two 32-bit parts and writes them using the
- * `wrmsr` instruction.
- *
- * @param msr The address of the MSR to write to.
- * @param new_value The 64-bit value to write to the MSR.
- */
- auto write_msr(uint32_t msr, uint64_t new_value) -> void;
-
- /**
- * @brief Sets a specific bit in the Extended Feature Enable Register (EFER), which is a Model-Specific Register
- * (MSR).
- *
- * @note This function reads the current value of the EFER register, ORs the specified
- * bit with the current value, and writes the updated value back to the EFER register.
- *
- * @param flag The flag to set in the EFER register.
- */
- auto set_efer_bit(efer_flags flag) -> void;
-
-} // namespace teachos::arch::kernel::cpu
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_CPU_NXE_HPP \ No newline at end of file
diff --git a/arch/x86_64/pre/include/arch/kernel/cpu/tr.hpp b/arch/x86_64/pre/include/arch/kernel/cpu/tr.hpp
deleted file mode 100644
index 7c856f1..0000000
--- a/arch/x86_64/pre/include/arch/kernel/cpu/tr.hpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_CPU_TR_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_CPU_TR_HPP
-
-#include <bitset>
-#include <cstdint>
-
-namespace teachos::arch::kernel::cpu
-{
-
- /**
- * @brief Returns the value in the LTR register.
- *
- * @return Value of LTR register.
- */
- auto store_task_register() -> uint16_t;
-
- /**
- * @brief Loads the gdt offset to the tss segment descriptor into the task register (TR).
- */
- auto load_task_register(uint16_t gdt_offset) -> void;
-
-} // namespace teachos::arch::kernel::cpu
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_CPU_TR_HPP
diff --git a/arch/x86_64/pre/include/arch/kernel/halt.hpp b/arch/x86_64/pre/include/arch/kernel/halt.hpp
deleted file mode 100644
index 377acc0..0000000
--- a/arch/x86_64/pre/include/arch/kernel/halt.hpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_HALT_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_HALT_HPP
-
-namespace teachos::arch::kernel
-{
- /**
- * @brief Halts the kernel execution, meaning any code after a call to this will not run anymore.
- */
- extern "C" [[noreturn]] auto halt() -> void;
-
-} // namespace teachos::arch::kernel
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_HALT_HPP
diff --git a/arch/x86_64/pre/include/arch/kernel/main.hpp b/arch/x86_64/pre/include/arch/kernel/main.hpp
deleted file mode 100644
index a13e5f4..0000000
--- a/arch/x86_64/pre/include/arch/kernel/main.hpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_KERNEL_MAIN_HPP
-#define TEACHOS_ARCH_X86_64_KERNEL_MAIN_HPP
-
-namespace teachos::arch::kernel
-{
- /**
- * @brief Initalizes the kernel system.
- */
- auto main() -> void;
-
-} // namespace teachos::arch::kernel
-
-#endif // TEACHOS_ARCH_X86_64_KERNEL_MAIN_HPP
diff --git a/arch/x86_64/pre/include/arch/user/main.hpp b/arch/x86_64/pre/include/arch/user/main.hpp
deleted file mode 100644
index c168a1f..0000000
--- a/arch/x86_64/pre/include/arch/user/main.hpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef TEACHOS_ARCH_X86_64_USER_MAIN_HPP
-#define TEACHOS_ARCH_X86_64_USER_MAIN_HPP
-
-namespace teachos::arch::user
-{
- /**
- * @brief User Main method. If this method finishes there is no code left to run and the whole OS will shut down.
- * Additionally this main method is executed at Ring 3 and accessing CPU Registers or Kernel level functionality can
- * only be done over syscalls and not directly anymore.
- */
- [[gnu::section(".user_text")]]
- auto main() -> void;
-
-} // namespace teachos::arch::user
-
-#endif // TEACHOS_ARCH_X86_64_USER_MAIN_HPP
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp
deleted file mode 100644
index 28f289c..0000000
--- a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/gate_descriptor.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#include "arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- gate_descriptor::gate_descriptor(uint128_t flags)
- : _offset_1(flags)
- , _selector(flags >> 19U, flags >> 16U)
- , _ist(flags >> 32U)
- , _flags(flags >> 40U)
- , _offset_2(flags >> 48U)
- {
- // Nothing to do.
- }
-
- gate_descriptor::gate_descriptor(segment_selector selector, ist_offset ist, idt_flags flags, uint64_t offset)
- : _offset_1(offset)
- , _selector(selector)
- , _ist(ist)
- , _flags(flags)
- , _offset_2(offset >> 16U)
- {
- // Nothing to do.
- }
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp
deleted file mode 100644
index f3b9d5e..0000000
--- a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/idt_flags.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-#include "arch/context_switching/interrupt_descriptor_table/idt_flags.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- idt_flags::idt_flags(uint8_t flags)
- : _flags(flags)
- {
- // Nothing to do.
- }
-
- auto idt_flags::contains_flags(std::bitset<8U> other) const -> bool
- {
- return (std::bitset<8U>{_flags} & other) == other;
- }
-
- auto idt_flags::operator|=(std::bitset<8U> other) -> void
- {
- _flags |= other.to_ulong();
- }
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp
deleted file mode 100644
index 8640385..0000000
--- a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp"
-
-#include "arch/exception_handling/assert.hpp"
-#include "arch/interrupt_handling/generic_interrupt_handler.hpp"
-#include "arch/kernel/cpu/idtr.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- namespace
- {
- /// @brief Amount of currently reserved interrupt indicies.
- /// See https://wiki.osdev.org/Interrupt_Descriptor_Table#IDT_items for more information.
- constexpr uint16_t RESERVED_INTERRUPT_COUNT = 256U;
-
- auto create_interrupt_descriptor_table() -> interrupt_descriptor_table
- {
- interrupt_descriptor_table interrupt_descriptor_table{RESERVED_INTERRUPT_COUNT};
-
- uint64_t offset = reinterpret_cast<uint64_t>(interrupt_handling::generic_interrupt_handler);
- segment_selector selector{1U, segment_selector::REQUEST_LEVEL_KERNEL};
- ist_offset ist{0U};
- idt_flags flags{idt_flags::DESCRIPTOR_LEVEL_KERNEL | idt_flags::INTERRUPT_GATE | idt_flags::PRESENT};
-
- for (std::size_t i = 0; i < interrupt_descriptor_table.size(); i++)
- {
- interrupt_descriptor_table.at(i) = {selector, ist, flags, offset};
- }
-
- return interrupt_descriptor_table;
- }
- } // namespace
-
- auto get_or_create_interrupt_descriptor_table() -> interrupt_descriptor_table &
- {
- // Interrupt Descriptor Table needs to be kept alive
- interrupt_descriptor_table static idt = create_interrupt_descriptor_table();
- return idt;
- }
-
- auto update_interrupt_descriptor_table_register() -> void
- {
- decltype(auto) idt = get_or_create_interrupt_descriptor_table();
-
- interrupt_descriptor_table_pointer idt_pointer{static_cast<uint16_t>((idt.size() * sizeof(gate_descriptor)) - 1),
- idt.data()};
- kernel::cpu::load_interrupt_descriptor_table(idt_pointer);
-
- auto const stored_gdt_pointer = kernel::cpu::store_interrupt_descriptor_table();
- arch::exception_handling::assert(
- idt_pointer == stored_gdt_pointer,
- "[Interrupt Descriptor Table] Loaded IDTR value is not the same as the stored value.");
- }
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp
deleted file mode 100644
index 7bcbae6..0000000
--- a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- interrupt_descriptor_table_pointer::interrupt_descriptor_table_pointer(uint16_t table_length,
- gate_descriptor * address)
- : table_length(table_length)
- , address(address)
- {
- // Nothing to do.
- }
-
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp
deleted file mode 100644
index a70e75d..0000000
--- a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/ist_offset.cpp
+++ /dev/null
@@ -1,10 +0,0 @@
-#include "arch/context_switching/interrupt_descriptor_table/ist_offset.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- ist_offset::ist_offset(uint8_t index)
- : _ist(index)
- {
- // Nothing to do.
- }
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
diff --git a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp b/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp
deleted file mode 100644
index 25ba859..0000000
--- a/arch/x86_64/pre/src/context_switching/interrupt_descriptor_table/segment_selector.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp"
-
-namespace teachos::arch::context_switching::interrupt_descriptor_table
-{
- auto segment_selector::contains_flags(std::bitset<3U> other) const -> bool
- {
- return (std::bitset<3U>{_flags} & other) == other;
- }
-
- auto segment_selector::get_index() const -> uint16_t
- {
- return _index;
- }
-
- auto segment_selector::operator|=(std::bitset<3U> other) -> void
- {
- _flags |= other.to_ulong();
- }
-
- segment_selector::operator uint16_t() const
- {
- return *reinterpret_cast<uint16_t const *>(this);
- }
-} // namespace teachos::arch::context_switching::interrupt_descriptor_table
diff --git a/arch/x86_64/pre/src/context_switching/main.cpp b/arch/x86_64/pre/src/context_switching/main.cpp
deleted file mode 100644
index 3eb6dae..0000000
--- a/arch/x86_64/pre/src/context_switching/main.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-#include "arch/context_switching/main.hpp"
-
-#include "arch/boot/pointers.hpp"
-#include "arch/context_switching/syscall/syscall_enable.hpp"
-#include "arch/kernel/cpu/call.hpp"
-#include "arch/kernel/cpu/if.hpp"
-#include "arch/kernel/cpu/segment_register.hpp"
-#include "arch/kernel/cpu/tr.hpp"
-#include "arch/user/main.hpp"
-
-namespace teachos::arch::context_switching
-{
- namespace
- {
- constexpr interrupt_descriptor_table::segment_selector KERNEL_CODE_SEGMENT_SELECTOR{
- 1U, interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_KERNEL};
- constexpr kernel::cpu::far_pointer KERNEL_CODE_POINTER{&kernel::cpu::reload_data_segment_registers,
- KERNEL_CODE_SEGMENT_SELECTOR};
- constexpr context_switching::interrupt_descriptor_table::segment_selector USER_CODE_SEGMENT_SELECTOR{
- 3U, context_switching::interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_USER};
- constexpr context_switching::interrupt_descriptor_table::segment_selector USER_DATA_SEGMENT_SELECTOR{
- 4U, context_switching::interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_USER};
-
- auto reload_gdtr() -> void
- {
- kernel::cpu::call(KERNEL_CODE_POINTER);
- }
- } // namespace
-
- auto initialize_descriptor_tables() -> descriptor_tables
- {
- bool static initalized = false;
-
- if (!initalized)
- {
- kernel::cpu::clear_interrupt_flag();
-
- segment_descriptor_table::update_gdtr();
- interrupt_descriptor_table::update_interrupt_descriptor_table_register();
-
- reload_gdtr();
- segment_descriptor_table::update_tss_register();
-
- kernel::cpu::set_interrupt_flag();
- initalized = true;
- }
-
- descriptor_tables tables = {segment_descriptor_table::get_or_create_gdt(),
- interrupt_descriptor_table::get_or_create_interrupt_descriptor_table()};
- return tables;
- }
-
- auto switch_to_user_mode() -> void
- {
- syscall::enable_syscall();
- switch_context(USER_DATA_SEGMENT_SELECTOR, USER_CODE_SEGMENT_SELECTOR, user::main);
- }
-
- auto switch_context(interrupt_descriptor_table::segment_selector data_segment,
- interrupt_descriptor_table::segment_selector code_segment, void (*return_function)()) -> void
- {
- (void)initialize_descriptor_tables();
- kernel::cpu::set_data_segment_registers(data_segment);
- kernel::cpu::set_code_segment_register(data_segment, code_segment, reinterpret_cast<uint64_t>(return_function));
- }
-} // namespace teachos::arch::context_switching
diff --git a/arch/x86_64/pre/src/context_switching/syscall/main.cpp b/arch/x86_64/pre/src/context_switching/syscall/main.cpp
deleted file mode 100644
index b4ab468..0000000
--- a/arch/x86_64/pre/src/context_switching/syscall/main.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "arch/context_switching/syscall/main.hpp"
-
-namespace teachos::arch::context_switching::syscall
-{
- auto syscall(type syscall_number, arguments args) -> response
- {
- asm volatile("mov %[input], %%rax"
- : /* no output from call */
- : [input] "m"(syscall_number)
- : "memory");
-
- asm volatile("mov %[input], %%rdi " : /* no output from call */ : [input] "m"(args.arg_0) : "memory");
- asm volatile("mov %[input], %%rsi" : /* no output from call */ : [input] "m"(args.arg_1) : "memory");
- asm volatile("mov %[input], %%rdx" : /* no output from call */ : [input] "m"(args.arg_2) : "memory");
- asm volatile("mov %[input], %%r10" : /* no output from call */ : [input] "m"(args.arg_3) : "memory");
- asm volatile("mov %[input], %%r8" : /* no output from call */ : [input] "m"(args.arg_4) : "memory");
- asm volatile("mov %[input], %%r9" : /* no output from call */ : [input] "m"(args.arg_5) : "memory");
-
- asm volatile("syscall");
-
- arguments values{};
- asm volatile("mov %%rdi, %[output]" : [output] "=m"(values.arg_0));
- asm volatile("mov %%rsi, %[output]" : [output] "=m"(values.arg_1));
- asm volatile("mov %%rdx, %[output]" : [output] "=m"(values.arg_2));
- asm volatile("mov %%r10, %[output]" : [output] "=m"(values.arg_3));
- asm volatile("mov %%r8, %[output]" : [output] "=m"(values.arg_4));
- asm volatile("mov %%r9, %[output]" : [output] "=m"(values.arg_5));
-
- error error_code{};
- asm volatile("mov %%al, %[output]" : [output] "=m"(error_code));
-
- return {error_code, values};
- }
-
-} // namespace teachos::arch::context_switching::syscall
diff --git a/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp b/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp
deleted file mode 100644
index dbb3ed9..0000000
--- a/arch/x86_64/pre/src/context_switching/syscall/syscall_enable.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-#include "arch/context_switching/syscall/syscall_enable.hpp"
-
-#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp"
-#include "arch/context_switching/syscall/syscall_handler.hpp"
-#include "arch/kernel/cpu/msr.hpp"
-
-namespace teachos::arch::context_switching::syscall
-{
- namespace
- {
- constexpr interrupt_descriptor_table::segment_selector KERNEL_CODE_SEGMENT_SELECTOR{
- 1U, interrupt_descriptor_table::segment_selector::REQUEST_LEVEL_KERNEL};
-
- constexpr auto IA32_STAR_ADDRESS = 0xC000'0081;
- constexpr auto IA32_LSTAR_ADDRESS = 0xC000'0082;
- constexpr auto IA32_FMASK_ADDRESS = 0xC000'0084;
-
- } // namespace
-
- auto enable_syscall() -> void
- {
- uint64_t const syscall_function = reinterpret_cast<uint64_t>(syscall_handler);
- kernel::cpu::write_msr(IA32_LSTAR_ADDRESS, syscall_function);
- kernel::cpu::write_msr(IA32_FMASK_ADDRESS, 0U);
-
- uint64_t const kernel_cs = KERNEL_CODE_SEGMENT_SELECTOR;
- uint64_t const star_value = (kernel_cs << 32) | (kernel_cs << 48);
- kernel::cpu::write_msr(IA32_STAR_ADDRESS, star_value);
-
- kernel::cpu::set_efer_bit(kernel::cpu::efer_flags::SCE);
- }
-} // namespace teachos::arch::context_switching::syscall
diff --git a/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp b/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp
deleted file mode 100644
index c120f77..0000000
--- a/arch/x86_64/pre/src/context_switching/syscall/syscall_handler.cpp
+++ /dev/null
@@ -1,121 +0,0 @@
-#include "arch/context_switching/syscall/syscall_handler.hpp"
-
-#include "arch/context_switching/syscall/main.hpp"
-#include "arch/exception_handling/assert.hpp"
-#include "arch/exception_handling/panic.hpp"
-#include "arch/memory/heap/global_heap_allocator.hpp"
-#include "arch/memory/main.hpp"
-#include "arch/video/vga/text.hpp"
-
-namespace teachos::arch::context_switching::syscall
-{
-
- namespace
- {
- auto write_to_vga_buffer(uint64_t buffer) -> response
- {
- video::vga::text::write(reinterpret_cast<char const *>(buffer),
- video::vga::text::common_attributes::green_on_black);
- video::vga::text::newline();
- return {error::OK};
- }
-
- auto expand_user_heap() -> response
- {
- auto static current_heap_end = memory::heap::USER_HEAP_START;
- uint64_t const heap_start = current_heap_end;
- memory::remap_heap(heap_start, memory::heap::USER_HEAP_SIZE, memory::paging::entry::USER_ACCESSIBLE);
- current_heap_end += memory::heap::USER_HEAP_SIZE;
- return {
- error::OK,
- {heap_start, memory::heap::USER_HEAP_SIZE}
- };
- }
- } // namespace
-
- auto syscall_handler() -> void
- {
- // Saving state of rcx and r11 because it is required by sysretq to function.
- // Calls to other functions potentially overwrite these registers, because of
- // callee saved calling convention.
- uint64_t return_instruction_pointer, rflags = {};
- asm volatile("mov %%rcx, %[output]" : [output] "=m"(return_instruction_pointer));
- asm volatile("mov %%r11, %[output]" : [output] "=m"(rflags));
-
- uint64_t syscall_number, arg_0, arg_1, arg_2, arg_3, arg_4, arg_5 = {};
- asm volatile("mov %%rdi, %[output]" : [output] "=m"(arg_0));
- asm volatile("mov %%rsi, %[output]" : [output] "=m"(arg_1));
- asm volatile("mov %%rdx, %[output]" : [output] "=m"(arg_2));
- asm volatile("mov %%r10, %[output]" : [output] "=m"(arg_3));
- asm volatile("mov %%r8, %[output]" : [output] "=m"(arg_4));
- asm volatile("mov %%r9, %[output]" : [output] "=m"(arg_5));
-
- // RAX is read last, because paired with our type enum, we can use it to check
- // if the register has been written by the compiled code between executing the syscall
- // and now.
- asm volatile("mov %%rax, %[output]" : [output] "=m"(syscall_number));
-
- response result;
- switch (static_cast<type>(syscall_number))
- {
- case type::WRITE:
- result = write_to_vga_buffer(arg_0);
- break;
- case type::EXPAND_HEAP:
- result = expand_user_heap();
- break;
- case type::ASSERT:
- teachos::arch::exception_handling::assert(arg_0, reinterpret_cast<char const *>(arg_1));
- break;
- default:
- teachos::arch::exception_handling::panic("[Syscall Handler] Invalid syscall number");
- break;
- }
-
- asm volatile("mov %[input], %%rax"
- : /* no output from call */
- : [input] "m"(result.error_code)
- : "memory");
-
- asm volatile("mov %[input], %%rdi"
- : /* no output from call */
- : [input] "m"(result.values.arg_0)
- : "memory");
- asm volatile("mov %[input], %%rsi"
- : /* no output from call */
- : [input] "m"(result.values.arg_1)
- : "memory");
- asm volatile("mov %[input], %%rdx"
- : /* no output from call */
- : [input] "m"(result.values.arg_2)
- : "memory");
- asm volatile("mov %[input], %%r10"
- : /* no output from call */
- : [input] "m"(result.values.arg_3)
- : "memory");
- asm volatile("mov %[input], %%r8"
- : /* no output from call */
- : [input] "m"(result.values.arg_4)
- : "memory");
- asm volatile("mov %[input], %%r9"
- : /* no output from call */
- : [input] "m"(result.values.arg_5)
- : "memory");
-
- asm volatile("mov %[input], %%rcx"
- : /* no output from call */
- : [input] "m"(return_instruction_pointer)
- : "memory");
- asm volatile("mov %[input], %%r11"
- : /* no output from call */
- : [input] "m"(rflags)
- : "memory");
-
- // Additionally call leave, because x86 allocates stack space for the internal variables. If we do not clean up this
- // newly created stack frame the syscall instruction that landed in this syscall_handler, will never return to the
- // method that originally called it, because the RIP has not been restored from the previous stack frame.
- asm volatile("leave\n"
- "sysretq");
- }
-
-} // namespace teachos::arch::context_switching::syscall
diff --git a/arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp b/arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp
deleted file mode 100644
index 9d061a8..0000000
--- a/arch/x86_64/pre/src/interrupt_handling/generic_interrupt_handler.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "arch/interrupt_handling/generic_interrupt_handler.hpp"
-
-#include "arch/video/vga/text.hpp"
-
-namespace teachos::arch::interrupt_handling
-{
- auto generic_interrupt_handler(interrupt_frame * frame) -> void
- {
- (void)frame;
- video::vga::text::write("An Interrupt occurred.", video::vga::text::common_attributes::green_on_black);
- video::vga::text::newline();
- }
-} // namespace teachos::arch::interrupt_handling
diff --git a/arch/x86_64/pre/src/kernel/cpu/call.cpp b/arch/x86_64/pre/src/kernel/cpu/call.cpp
deleted file mode 100644
index 98fa248..0000000
--- a/arch/x86_64/pre/src/kernel/cpu/call.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#include "arch/kernel/cpu/call.hpp"
-
-namespace teachos::arch::kernel::cpu
-{
- auto call(far_pointer pointer) -> void
- {
- asm volatile("rex64 lcall *%[input]" : /* no output from call */ : [input] "m"(pointer));
- }
-} // namespace teachos::arch::kernel::cpu
diff --git a/arch/x86_64/pre/src/kernel/cpu/idtr.cpp b/arch/x86_64/pre/src/kernel/cpu/idtr.cpp
deleted file mode 100644
index 7aa20c1..0000000
--- a/arch/x86_64/pre/src/kernel/cpu/idtr.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include "arch/kernel/cpu/idtr.hpp"
-
-namespace teachos::arch::kernel::cpu
-{
- auto store_interrupt_descriptor_table()
- -> context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer
- {
- context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer current_value{};
- asm("sidt %[output]" : [output] "=m"(current_value));
- return current_value;
- }
-
- auto load_interrupt_descriptor_table(
- context_switching::interrupt_descriptor_table::interrupt_descriptor_table_pointer const & idt_pointer) -> void
- {
- asm volatile("lidt %[input]" : /* no output from call */ : [input] "m"(idt_pointer));
- }
-} // namespace teachos::arch::kernel::cpu
diff --git a/arch/x86_64/pre/src/kernel/cpu/if.cpp b/arch/x86_64/pre/src/kernel/cpu/if.cpp
deleted file mode 100644
index 5d056fc..0000000
--- a/arch/x86_64/pre/src/kernel/cpu/if.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace teachos::arch::kernel::cpu
-{
- auto set_interrupt_flag() -> void
- {
- asm volatile("sti");
- }
-
- auto clear_interrupt_flag() -> void
- {
- asm volatile("cli");
- }
-
-} // namespace teachos::arch::kernel::cpu
diff --git a/arch/x86_64/pre/src/kernel/cpu/tr.cpp b/arch/x86_64/pre/src/kernel/cpu/tr.cpp
deleted file mode 100644
index a435540..0000000
--- a/arch/x86_64/pre/src/kernel/cpu/tr.cpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#include "arch/kernel/cpu/tr.hpp"
-
-namespace teachos::arch::kernel::cpu
-{
- auto store_task_register() -> uint16_t
- {
- uint16_t current_value{};
- asm("str %[output]" : [output] "=r"(current_value));
- return current_value;
- }
-
- auto load_task_register(uint16_t gdt_offset) -> void
- {
- asm volatile("ltr %[input]" : /* no output from call */ : [input] "m"(gdt_offset));
- }
-} // namespace teachos::arch::kernel::cpu
diff --git a/arch/x86_64/pre/src/kernel/main.cpp b/arch/x86_64/pre/src/kernel/main.cpp
deleted file mode 100644
index 43b5f90..0000000
--- a/arch/x86_64/pre/src/kernel/main.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-#include "arch/kernel/main.hpp"
-
-#include "arch/boot/pointers.hpp"
-#include "arch/context_switching/interrupt_descriptor_table/segment_selector.hpp"
-#include "arch/context_switching/main.hpp"
-#include "arch/kernel/cpu/if.hpp"
-#include "arch/kernel/cpu/segment_register.hpp"
-#include "arch/memory/heap/bump_allocator.hpp"
-#include "arch/memory/heap/global_heap_allocator.hpp"
-#include "arch/memory/main.hpp"
-#include "arch/memory/multiboot/reader.hpp"
-#include "arch/stl/vector.hpp"
-#include "arch/video/vga/text.hpp"
-
-namespace teachos::arch::kernel
-{
- auto stack_overflow_test(int count) -> int
- {
- int test[5000] = {};
- if (test[0] == 0xFFFF)
- {
- return count;
- }
- count = stack_overflow_test(count);
- return count++;
- }
-
- auto heap_test() -> void
- {
- auto test2 = new memory::multiboot::memory_information{};
- auto test3 = new memory::multiboot::memory_information{};
- auto test4 = *test2;
- auto test5 = *test3;
- test4.kernel_end = 5000;
- test5.kernel_end = 3000;
- auto test6 = test4.kernel_end;
- auto test7 = test5.kernel_end;
- auto test8 = memory::multiboot::read_multiboot2();
- if (test6 && test7 && test8.kernel_end)
- {
- video::vga::text::write("Heap test successful", video::vga::text::common_attributes::green_on_black);
- }
- test2->kernel_end = 2000;
- test2->kernel_start = 1000;
- test2->multiboot_start = 2000;
- delete test2;
- delete test3;
-
- auto test9 = new int(50);
- delete test9;
- }
-
- auto main() -> void
- {
- video::vga::text::clear();
- video::vga::text::cursor(false);
- video::vga::text::write("TeachOS is starting up...", video::vga::text::common_attributes::green_on_black);
- video::vga::text::newline();
-
- memory::initialize_memory_management();
- // stack_overflow_test(0);
-
- memory::heap::global_heap_allocator::register_heap_allocator(memory::heap::heap_allocator_type::LINKED_LIST);
- // heap_test();
-
- auto address = memory::heap::global_heap_allocator::kmalloc(8U);
- (void)address;
-
- context_switching::switch_to_user_mode();
- }
-} // namespace teachos::arch::kernel
diff --git a/arch/x86_64/pre/src/user/main.cpp b/arch/x86_64/pre/src/user/main.cpp
deleted file mode 100644
index 8b07e4a..0000000
--- a/arch/x86_64/pre/src/user/main.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-#include "arch/user/main.hpp"
-
-#include "arch/context_switching/syscall/main.hpp"
-#include "arch/memory/heap/global_heap_allocator.hpp"
-
-#include <algorithm>
-#include <array>
-#include <atomic>
-#include <ranges>
-
-namespace teachos::arch::user
-{
- auto main() -> void
- {
- constexpr char syscall_message[] = "Successfully entered user mode and wrote to VGA buffer via syscall!";
- context_switching::syscall::syscall(context_switching::syscall::type::WRITE,
- {reinterpret_cast<uint64_t>(&syscall_message)});
-
- // Test C++ standard library
- std::array<std::atomic<uint8_t>, 4> array_test = {std::atomic<uint8_t>{5}, std::atomic<uint8_t>{10},
- std::atomic<uint8_t>{15}, std::atomic<uint8_t>{20}};
- std::ranges::for_each(array_test, [](auto & item) {
- auto value = item.load();
- uint8_t max_value = std::max(value, uint8_t{10});
- item.exchange(max_value + 2);
- });
-
- auto address = new uint64_t{10U};
- (void)address;
-
- for (;;)
- {
- }
- }
-} // namespace teachos::arch::user
diff --git a/arch/x86_64/scripts/kernel.ld b/arch/x86_64/scripts/kernel.ld
index a429570..dbb0f8f 100644
--- a/arch/x86_64/scripts/kernel.ld
+++ b/arch/x86_64/scripts/kernel.ld
@@ -72,6 +72,12 @@ SECTIONS
.kernel_rodata ALIGN(4K) : AT (ADDR (.kernel_rodata) - TEACHOS_VMA)
{
*(.rodata*)
+
+ . = ALIGN(8);
+
+ PROVIDE(__start_fs_types = .);
+ KEEP(*(fs_types));
+ PROVIDE(__stop_fs_types = .);
} :kernel_rodata
.kernel_data ALIGN(4K) : AT (ADDR (.kernel_data) - TEACHOS_VMA)
diff --git a/arch/x86_64/support/grub.cfg.in b/arch/x86_64/support/grub.cfg.in
index 49f19ce..45c3356 100644
--- a/arch/x86_64/support/grub.cfg.in
+++ b/arch/x86_64/support/grub.cfg.in
@@ -3,5 +3,8 @@ default=0
menuentry "TeachOS" {
multiboot2 /$<TARGET_FILE_NAME:kernel>
+ module2 /modules/ext2_4KB_fs.img
+ module2 /modules/ext2_1KB_fs.img
+ module2 /modules/ext2_2KB_fs.img
boot
} \ No newline at end of file
diff --git a/arch/x86_64/support/modules/README.md b/arch/x86_64/support/modules/README.md
new file mode 100644
index 0000000..6d235a7
--- /dev/null
+++ b/arch/x86_64/support/modules/README.md
@@ -0,0 +1,130 @@
+# Default images
+The default images contain predefined data and structures that are specifically designed for testing purposes.
+The ext2_4KB_fs image is intentionally fragmented, as some files were created and deleted before additional files were added, resulting in a non-contiguous layout.
+
+## ext2_1KB_fs
+.
+./lost+found
+./archiv
+./archiv/2024.img
+./archiv/2025.img
+./archiv/mnt
+./closed.txt
+./information
+./information/info_1.txt
+./information/info_2.txt
+./symlinks
+./symlinks/info_1_absolute -> /information/info_1.txt
+./symlinks/info_1_relative -> ../information/info_1.txt
+./symlinks/information_directory_absolute -> /information
+./symlinks/information_directory_relative -> ../information
+./symlinks/invalid_absolute -> /invalid/non_existant.txt
+./symlinks/invalid_relative -> ../invalid/non_existant.txt
+./symlinks/symloop_a -> ./symloop_b
+./symlinks/symloop_b -> /symlinks/symloop_a
+./symlinks/traverse_back_5_times -> ../../../../../
+
+### 2024.img
+(ext2 filesystem with 2KB Block size)
+.
+./lost+found
+./sheep_1.txt
+./sheep_2.txt
+./stable/pig_1.txt
+./stable/pig_2.txt
+./stable/pig_3.txt
+./symlinks
+./symlinks/traverse_back_twice -> ../../
+
+### 2025.img
+(ext2 filesystem 4KB Block size)
+.
+./lost+found
+./dev
+./dev/image_1.txt
+./dev/image_2.txt
+./snake_1.txt
+./snake_2.txt
+./petting_zoo/goat_1.txt
+./petting_zoo/goat_2.txt
+./petting_zoo/chicken_coop
+./petting_zoo/chicken_coop/chicken_1.txt
+./petting_zoo/chicken_coop/chicken_2.txt
+./petting_zoo/chicken_coop/chicken_3.txt
+
+## ext2_2KB_fs
+.
+./lost+found
+./monkey_house
+./monkey_house/infrastructure
+./monkey_house/infrastructure/info.txt
+./monkey_house/infrastructure/water.txt
+./monkey_house/monkey_1.txt
+./monkey_house/monkey_2.txt
+./monkey_house/monkey_3.txt
+./monkey_house/caretaker
+./monkey_house/caretaker/isabelle.txt
+./monkey_house/caretaker/peter.txt
+./symlinks/leave_and_reenter_mount -> ../../archiv/../information/monkey_house
+
+## ext2_4KB_fs
+.
+./lost+found
+./entrance
+./entrance/tickets.txt
+./entrance/map.txt
+./enclosures
+./enclosures/lion_house
+./enclosures/lion_house/cage_a
+./enclosures/lion_house/cage_a/history.txt
+./enclosures/lion_house/cage_a/animals.txt
+./enclosures/lion_house/cage_b
+./enclosures/lion_house/cage_b/animals.txt
+./enclosures/lion_house/cage_b/history.txt
+./enclosures/lion_house/symlink_chain_2 -> /entrance/../enclosures/aquarium/symlink_chain_3
+./enclosures/elephant_house
+./enclosures/elephant_house/elephant_1.txt
+./enclosures/aquarium
+./enclosures/aquarium/spawn_fish.sh
+./enclosures/aquarium/symlink_chain_3 -> ../../entrance
+./enclosures/aquarium/tank_1
+./enclosures/aquarium/tank_1/fish_1.txt
+./enclosures/aquarium/tank_1/fish_2.txt
+./enclosures/aquarium/tank_1/fish_3.txt
+./enclosures/aquarium/tank_1/fish_4.txt
+./enclosures/aquarium/tank_2
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_1.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_2.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_3.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_4.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_5.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_6.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_7.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_8.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_9.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_10.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_11.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_12.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_13.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_14.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_15.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_16.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_17.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_18.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_19.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_20.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_21.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_22.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_23.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_24.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_25.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_26.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_27.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_28.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_29.txt
+./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_30.txt
+./enclosures/info.txt
+./info.txt
+./symlinks
+./symlinks/symlink_chain_1 -> ../enclosures/lion_house/symlink_chain_2
+./symlinks/very_long_symlink -> ../enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_30.txt
diff --git a/arch/x86_64/support/modules/ext2_1KB_fs.img b/arch/x86_64/support/modules/ext2_1KB_fs.img
new file mode 100644
index 0000000..a5202ca
--- /dev/null
+++ b/arch/x86_64/support/modules/ext2_1KB_fs.img
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:98ac3c1be872806e25fb14eea168ca79a91959f4e6a5ac3d00c5d8224c1f73a3
+size 10485760
diff --git a/arch/x86_64/support/modules/ext2_2KB_fs.img b/arch/x86_64/support/modules/ext2_2KB_fs.img
new file mode 100644
index 0000000..7f297f0
--- /dev/null
+++ b/arch/x86_64/support/modules/ext2_2KB_fs.img
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6d9e872916e7d9107b321cc007e151899d5f19400a694666c0b24d482aef61ca
+size 5242880
diff --git a/arch/x86_64/support/modules/ext2_4KB_fs.img b/arch/x86_64/support/modules/ext2_4KB_fs.img
new file mode 100644
index 0000000..c3f6daf
--- /dev/null
+++ b/arch/x86_64/support/modules/ext2_4KB_fs.img
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:026ca30269dbd80beb2dd74677c94676d1d4a7f6b5fe406c4ddb82836ba2dc00
+size 10485760
diff --git a/cmake/Modules/EnableCoverage.cmake b/cmake/Modules/EnableCoverage.cmake
new file mode 100644
index 0000000..20be368
--- /dev/null
+++ b/cmake/Modules/EnableCoverage.cmake
@@ -0,0 +1,9 @@
+function(enable_coverage TARGET)
+ target_compile_options("${TARGET}" PUBLIC
+ "$<$<AND:$<CXX_COMPILER_ID:GNU,Clang>,$<CONFIG:Debug>>:-fcondition-coverage>"
+ "$<$<AND:$<CXX_COMPILER_ID:GNU,Clang>,$<CONFIG:Debug>>:--coverage>"
+ )
+ target_link_options("${TARGET}" PUBLIC
+ "$<$<AND:$<CXX_COMPILER_ID:GNU,Clang>,$<CONFIG:Debug>>:--coverage>"
+ )
+endfunction()
diff --git a/cmake/Modules/GenerateBootableIso.cmake b/cmake/Modules/GenerateBootableIso.cmake
index 3d1ee30..39a0ebd 100644
--- a/cmake/Modules/GenerateBootableIso.cmake
+++ b/cmake/Modules/GenerateBootableIso.cmake
@@ -3,6 +3,14 @@ include_guard(GLOBAL)
function(target_generate_bootable_iso TARGET)
find_package("grub-mkrescue")
+ set(MODULES_DIR "${PROJECT_SOURCE_DIR}/arch/${CMAKE_SYSTEM_PROCESSOR}/support/modules")
+ set(COPY_MODULES_COMMAND)
+ if(EXISTS "${MODULES_DIR}")
+ set(COPY_MODULES_COMMAND
+ COMMAND "${CMAKE_COMMAND}" -E copy_directory "${MODULES_DIR}" "$<TARGET_FILE_DIR:${TARGET}>/isofs/modules"
+ )
+ endif()
+
file(GENERATE
OUTPUT "$<TARGET_FILE_DIR:${TARGET}>/isofs/boot/grub/grub.cfg"
INPUT "${PROJECT_SOURCE_DIR}/arch/${CMAKE_SYSTEM_PROCESSOR}/support/grub.cfg.in"
@@ -10,6 +18,8 @@ function(target_generate_bootable_iso TARGET)
add_custom_command(TARGET "${TARGET}"
POST_BUILD
+ COMMAND "${CMAKE_COMMAND}" -E make_directory "$<TARGET_FILE_DIR:${TARGET}>/isofs"
+ ${COPY_MODULES_COMMAND}
COMMAND "${GRUB_MKRESCUE_EXE}"
"-o"
"$<TARGET_FILE_DIR:${TARGET}>/${TARGET}.iso"
@@ -19,4 +29,5 @@ function(target_generate_bootable_iso TARGET)
BYPRODUCTS "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/$<CONFIGURATION>/${TARGET}.iso"
COMMENT "Creating bootable ISO image"
)
+
endfunction()
diff --git a/docs/conf.py b/docs/conf.py
index b8cfe69..99613b6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,7 +13,7 @@ author = "Felix Morgner"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
-#extensions = ["breathe"]
+extensions = ["breathe"]
templates_path = ["_templates"]
exclude_patterns = ["pre/**"]
@@ -21,8 +21,9 @@ exclude_patterns = ["pre/**"]
# -- Options Breathe ---------------------------------------------------------
# https://breathe.readthedocs.io/en/stable/directives.html#config-values
-#breathe_projects = {"kernel": "../build/doxygen/xml"}
-#breathe_default_project = "kernel"
+breathe_projects = {"kernel": "../build/doxygen/xml"}
+breathe_default_project = "kernel"
+breathe_default_members = ('members', 'undoc-members', 'private-members')
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
diff --git a/docs/index.rst b/docs/index.rst
index 649e6de..425da57 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,10 +6,10 @@ Welcome to TeachOS Kernel's documentation!
:caption: Contents:
briefs
+ kapi
Indices and tables
==================
* :ref:`genindex`
-* :ref:`modindex`
* :ref:`search`
diff --git a/docs/kapi.rst b/docs/kapi.rst
new file mode 100644
index 0000000..4c60ab3
--- /dev/null
+++ b/docs/kapi.rst
@@ -0,0 +1,10 @@
+Kernel API
+==========
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+ :glob:
+
+ kapi/*
+
diff --git a/docs/kapi/cio.rst b/docs/kapi/cio.rst
new file mode 100644
index 0000000..62a3639
--- /dev/null
+++ b/docs/kapi/cio.rst
@@ -0,0 +1,17 @@
+Character I/O Interface
+=======================
+
+.. doxygengroup:: kapi-cio
+ :content-only:
+
+Kernel-defined API
+------------------
+
+.. doxygengroup:: kapi-cio-kernel-defined
+ :content-only:
+
+Platform-defined API
+--------------------
+
+.. doxygengroup:: kapi-cio-platform-defined
+ :content-only:
diff --git a/docs/kapi/cpu.rst b/docs/kapi/cpu.rst
new file mode 100644
index 0000000..0793dd8
--- /dev/null
+++ b/docs/kapi/cpu.rst
@@ -0,0 +1,17 @@
+CPU Interface
+=============
+
+.. doxygengroup:: kapi-cpu
+ :content-only:
+
+Kernel-defined API
+------------------
+
+.. doxygengroup:: kapi-cpu-kernel-defined
+ :content-only:
+
+Platform-defined API
+--------------------
+
+.. doxygengroup:: kapi-cpu-platform-defined
+ :content-only:
diff --git a/docs/kapi/devices.rst b/docs/kapi/devices.rst
new file mode 100644
index 0000000..c9853a1
--- /dev/null
+++ b/docs/kapi/devices.rst
@@ -0,0 +1,17 @@
+Device Interface
+================
+
+.. doxygengroup:: kapi-devices
+ :content-only:
+
+Kernel-defined API
+------------------
+
+.. doxygengroup:: kapi-devices-kernel-defined
+ :content-only:
+
+Platform-defined API
+--------------------
+
+.. doxygengroup:: kapi-devices-platform-defined
+ :content-only:
diff --git a/docs/kapi/interrupts.rst b/docs/kapi/interrupts.rst
new file mode 100644
index 0000000..7150f07
--- /dev/null
+++ b/docs/kapi/interrupts.rst
@@ -0,0 +1,17 @@
+Interrupt Interface
+===================
+
+.. doxygengroup:: kapi-interrupts
+ :content-only:
+
+Kernel-defined API
+------------------
+
+.. doxygengroup:: kapi-interrupts-kernel-defined
+ :content-only:
+
+Platform-defined API
+--------------------
+
+.. doxygengroup:: kapi-interrupts-platform-defined
+ :content-only:
diff --git a/docs/kapi/memory.rst b/docs/kapi/memory.rst
new file mode 100644
index 0000000..9ee1584
--- /dev/null
+++ b/docs/kapi/memory.rst
@@ -0,0 +1,14 @@
+Memory Interface
+================
+
+Kernel-defined API
+------------------
+
+.. doxygengroup:: kapi-memory-kernel-defined
+ :content-only:
+
+Platform-defined API
+--------------------
+
+.. doxygengroup:: kapi-memory-platform-defined
+ :content-only:
diff --git a/docs/kapi/system.rst b/docs/kapi/system.rst
new file mode 100644
index 0000000..2eaea6d
--- /dev/null
+++ b/docs/kapi/system.rst
@@ -0,0 +1,14 @@
+System Interface
+================
+
+Kernel-defined API
+------------------
+
+.. doxygengroup:: kapi-system-kernel-defined
+ :content-only:
+
+Platform-defined API
+--------------------
+
+.. doxygengroup:: kapi-system-platform-defined
+ :content-only:
diff --git a/docs/pre/arch/x86_64.rst b/docs/pre/arch/x86_64.rst
deleted file mode 100644
index dc432f1..0000000
--- a/docs/pre/arch/x86_64.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-x86_64
-======
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- x86_64/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/boot.rst b/docs/pre/arch/x86_64/boot.rst
deleted file mode 100644
index 8be2a57..0000000
--- a/docs/pre/arch/x86_64/boot.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Boot Information Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- boot/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/boot/pointers.rst b/docs/pre/arch/x86_64/boot/pointers.rst
deleted file mode 100644
index 3ec626a..0000000
--- a/docs/pre/arch/x86_64/boot/pointers.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Boot Information Structure
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/boot/pointers.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching.rst b/docs/pre/arch/x86_64/context_switching.rst
deleted file mode 100644
index c3b3b03..0000000
--- a/docs/pre/arch/x86_64/context_switching.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Context Switching Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- context_switching/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table.rst
deleted file mode 100644
index dd6e478..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Interrupt Descriptor Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- interrupt_descriptor_table/*
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/gate_descriptor.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/gate_descriptor.rst
deleted file mode 100644
index 29e7586..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/gate_descriptor.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Gate Descriptor
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/interrupt_descriptor_table/gate_descriptor.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/idt_flags.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/idt_flags.rst
deleted file mode 100644
index 60e8c37..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/idt_flags.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Descriptor Flags
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/interrupt_descriptor_table/idt_flags.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table copy.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table copy.rst
deleted file mode 100644
index a2b8997..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table copy.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Descriptor Table
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer copy.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer copy.rst
deleted file mode 100644
index 3a8c259..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer copy.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Descriptor Table Pointer
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/interrupt_descriptor_table/interrupt_descriptor_table_pointer.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/ist_offset.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/ist_offset.rst
deleted file mode 100644
index ddba6ee..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/ist_offset.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Stack Table Offset
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/interrupt_descriptor_table/ist_offset.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/segment_selector.rst b/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/segment_selector.rst
deleted file mode 100644
index 2da142e..0000000
--- a/docs/pre/arch/x86_64/context_switching/interrupt_descriptor_table/segment_selector.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Segment Selector
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/interrupt_descriptor_table/segment_selector.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/main.rst b/docs/pre/arch/x86_64/context_switching/main.rst
deleted file mode 100644
index e9e8a35..0000000
--- a/docs/pre/arch/x86_64/context_switching/main.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Context Switching Main
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/main.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table.rst
deleted file mode 100644
index 449622d..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Segment Descriptor Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- segment_descriptor_table/*
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/access_byte.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/access_byte.rst
deleted file mode 100644
index f2e7d67..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/access_byte.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Access Byte
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/access_byte.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/gdt_flags.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/gdt_flags.rst
deleted file mode 100644
index faa2ffc..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/gdt_flags.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Global Descriptor Table Flags
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/gdt_flags.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table.rst
deleted file mode 100644
index 35403db..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Global Descriptor Table
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/global_descriptor_table.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table_pointer.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table_pointer.rst
deleted file mode 100644
index 41ceffd..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/global_descriptor_table_pointer.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Global Descriptor Table Pointer
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/global_descriptor_table_pointer.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_base.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_base.rst
deleted file mode 100644
index 952ab2a..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_base.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Segment Descriptor Base (32-bit)
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/segment_descriptor_base.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_extension.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_extension.rst
deleted file mode 100644
index 874d1cb..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_extension.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Segment Descriptor Extension (64-bit)
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/segment_descriptor_extension.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_type.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_type.rst
deleted file mode 100644
index e45b0a5..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/segment_descriptor_type.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Segment Descriptor Type
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/segment_descriptor_type.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/task_state_segment.rst b/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/task_state_segment.rst
deleted file mode 100644
index 731d7bb..0000000
--- a/docs/pre/arch/x86_64/context_switching/segment_descriptor_table/task_state_segment.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Task State Segment
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/segment_descriptor_table/task_state_segment.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/syscall.rst b/docs/pre/arch/x86_64/context_switching/syscall.rst
deleted file mode 100644
index 28acf28..0000000
--- a/docs/pre/arch/x86_64/context_switching/syscall.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-System Call Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- syscall/*
diff --git a/docs/pre/arch/x86_64/context_switching/syscall/main.rst b/docs/pre/arch/x86_64/context_switching/syscall/main.rst
deleted file mode 100644
index 6be577b..0000000
--- a/docs/pre/arch/x86_64/context_switching/syscall/main.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-System Call Main
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/syscall/main.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/syscall/syscall_enable.rst b/docs/pre/arch/x86_64/context_switching/syscall/syscall_enable.rst
deleted file mode 100644
index e9162f1..0000000
--- a/docs/pre/arch/x86_64/context_switching/syscall/syscall_enable.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-System Call Configuration
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/syscall/syscall_enable.hpp
-
diff --git a/docs/pre/arch/x86_64/context_switching/syscall/syscall_handler.rst b/docs/pre/arch/x86_64/context_switching/syscall/syscall_handler.rst
deleted file mode 100644
index 0e86780..0000000
--- a/docs/pre/arch/x86_64/context_switching/syscall/syscall_handler.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-System Call Handler
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/context_switching/syscall/syscall_handler.hpp
-
diff --git a/docs/pre/arch/x86_64/exception_handling.rst b/docs/pre/arch/x86_64/exception_handling.rst
deleted file mode 100644
index 3bf2770..0000000
--- a/docs/pre/arch/x86_64/exception_handling.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Exception Handling Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- exception_handling/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/exception_handling/assert.rst b/docs/pre/arch/x86_64/exception_handling/assert.rst
deleted file mode 100644
index 053cf66..0000000
--- a/docs/pre/arch/x86_64/exception_handling/assert.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Exception Handling Assertion
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/exception_handling/assert.hpp
-
diff --git a/docs/pre/arch/x86_64/exception_handling/panic.rst b/docs/pre/arch/x86_64/exception_handling/panic.rst
deleted file mode 100644
index 50b6284..0000000
--- a/docs/pre/arch/x86_64/exception_handling/panic.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Exception Handling Panic
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/exception_handling/panic.hpp
-
diff --git a/docs/pre/arch/x86_64/interrupt_handling.rst b/docs/pre/arch/x86_64/interrupt_handling.rst
deleted file mode 100644
index d4ff94a..0000000
--- a/docs/pre/arch/x86_64/interrupt_handling.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Interrupt Handling Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- interrupt_handling/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/interrupt_handling/generic_interrupt_handler.rst b/docs/pre/arch/x86_64/interrupt_handling/generic_interrupt_handler.rst
deleted file mode 100644
index 6099170..0000000
--- a/docs/pre/arch/x86_64/interrupt_handling/generic_interrupt_handler.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Generic Interrupt Handler
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/interrupt_handling/generic_interrupt_handler.hpp
-
diff --git a/docs/pre/arch/x86_64/io.rst b/docs/pre/arch/x86_64/io.rst
deleted file mode 100644
index 7082bd5..0000000
--- a/docs/pre/arch/x86_64/io.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-General Input/Output Subsystem
-==============================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- io/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/io/port_io.rst b/docs/pre/arch/x86_64/io/port_io.rst
deleted file mode 100644
index 18a9f6a..0000000
--- a/docs/pre/arch/x86_64/io/port_io.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-Port-based Input/Output
-=======================
-
-.. doxygenstruct:: teachos::arch::io::port
- :members:
-
diff --git a/docs/pre/arch/x86_64/kernel.rst b/docs/pre/arch/x86_64/kernel.rst
deleted file mode 100644
index 650e3a6..0000000
--- a/docs/pre/arch/x86_64/kernel.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Kernel Main Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- kernel/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/kernel/cpu.rst b/docs/pre/arch/x86_64/kernel/cpu.rst
deleted file mode 100644
index da3dfc0..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Kernel CPU Registers
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- cpu/*
diff --git a/docs/pre/arch/x86_64/kernel/cpu/call.rst b/docs/pre/arch/x86_64/kernel/cpu/call.rst
deleted file mode 100644
index 33d15ec..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/call.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Far Call
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/call.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/control_register.rst b/docs/pre/arch/x86_64/kernel/cpu/control_register.rst
deleted file mode 100644
index a45c6d9..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/control_register.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Control Register
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/control_register.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/gdtr.rst b/docs/pre/arch/x86_64/kernel/cpu/gdtr.rst
deleted file mode 100644
index 41c0f6b..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/gdtr.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Global Descriptor Table Register
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/gdtr.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/idtr.rst b/docs/pre/arch/x86_64/kernel/cpu/idtr.rst
deleted file mode 100644
index b4c4bb0..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/idtr.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Descriptor Table Register
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/idtr.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/if.rst b/docs/pre/arch/x86_64/kernel/cpu/if.rst
deleted file mode 100644
index 2dd07b4..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/if.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Interrupt Flag
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/if.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/msr.rst b/docs/pre/arch/x86_64/kernel/cpu/msr.rst
deleted file mode 100644
index 75c4f47..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/msr.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Model Specific Register
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/msr.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/segment_register.rst b/docs/pre/arch/x86_64/kernel/cpu/segment_register.rst
deleted file mode 100644
index 8159369..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/segment_register.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-CPU Segment Register
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/segment_register.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/tlb.rst b/docs/pre/arch/x86_64/kernel/cpu/tlb.rst
deleted file mode 100644
index 1ceec1d..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/tlb.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Translation Lookaside Buffer
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/tlb.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/cpu/tr.rst b/docs/pre/arch/x86_64/kernel/cpu/tr.rst
deleted file mode 100644
index a2b234b..0000000
--- a/docs/pre/arch/x86_64/kernel/cpu/tr.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Task Register
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/cpu/tr.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/halt.rst b/docs/pre/arch/x86_64/kernel/halt.rst
deleted file mode 100644
index c425e81..0000000
--- a/docs/pre/arch/x86_64/kernel/halt.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Kernel Halt
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/halt.hpp
-
diff --git a/docs/pre/arch/x86_64/kernel/main.rst b/docs/pre/arch/x86_64/kernel/main.rst
deleted file mode 100644
index 194bd85..0000000
--- a/docs/pre/arch/x86_64/kernel/main.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Kernel Main
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/kernel/main.hpp
-
diff --git a/docs/pre/arch/x86_64/memory.rst b/docs/pre/arch/x86_64/memory.rst
deleted file mode 100644
index 58d12e9..0000000
--- a/docs/pre/arch/x86_64/memory.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Kernel Memory Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- memory/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/memory/allocator.rst b/docs/pre/arch/x86_64/memory/allocator.rst
deleted file mode 100644
index 6ce0a74..0000000
--- a/docs/pre/arch/x86_64/memory/allocator.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Physical Frame Allocator Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- allocator/*
diff --git a/docs/pre/arch/x86_64/memory/allocator/area_frame_allocator.rst b/docs/pre/arch/x86_64/memory/allocator/area_frame_allocator.rst
deleted file mode 100644
index 422f33c..0000000
--- a/docs/pre/arch/x86_64/memory/allocator/area_frame_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Area Physical Frame Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/allocator/area_frame_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/allocator/concept.rst b/docs/pre/arch/x86_64/memory/allocator/concept.rst
deleted file mode 100644
index 734a2ce..0000000
--- a/docs/pre/arch/x86_64/memory/allocator/concept.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Physical Frame Allocator Concept
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/allocator/concept.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/allocator/physical_frame.rst b/docs/pre/arch/x86_64/memory/allocator/physical_frame.rst
deleted file mode 100644
index c5d0fd2..0000000
--- a/docs/pre/arch/x86_64/memory/allocator/physical_frame.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Physical Frame
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/allocator/physical_frame.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/allocator/tiny_frame_allocator.rst b/docs/pre/arch/x86_64/memory/allocator/tiny_frame_allocator.rst
deleted file mode 100644
index 27401b2..0000000
--- a/docs/pre/arch/x86_64/memory/allocator/tiny_frame_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Tiny Physical Frame Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/allocator/tiny_frame_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/cpu.rst b/docs/pre/arch/x86_64/memory/cpu.rst
deleted file mode 100644
index 4cb5af0..0000000
--- a/docs/pre/arch/x86_64/memory/cpu.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-CPU Registers Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- cpu/*
diff --git a/docs/pre/arch/x86_64/memory/heap.rst b/docs/pre/arch/x86_64/memory/heap.rst
deleted file mode 100644
index 409d93a..0000000
--- a/docs/pre/arch/x86_64/memory/heap.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Heap Memory Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- heap/*
diff --git a/docs/pre/arch/x86_64/memory/heap/bump_allocator.rst b/docs/pre/arch/x86_64/memory/heap/bump_allocator.rst
deleted file mode 100644
index b20916e..0000000
--- a/docs/pre/arch/x86_64/memory/heap/bump_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Kernel Heap Bump Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/heap/bump_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/heap/global_heap_allocator.rst b/docs/pre/arch/x86_64/memory/heap/global_heap_allocator.rst
deleted file mode 100644
index 60ec0b5..0000000
--- a/docs/pre/arch/x86_64/memory/heap/global_heap_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Global Heap Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/heap/global_heap_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/heap/heap_allocator.rst b/docs/pre/arch/x86_64/memory/heap/heap_allocator.rst
deleted file mode 100644
index b410e41..0000000
--- a/docs/pre/arch/x86_64/memory/heap/heap_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Kernel Heap Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/heap/heap_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/heap/linked_list_allocator.rst b/docs/pre/arch/x86_64/memory/heap/linked_list_allocator.rst
deleted file mode 100644
index d156852..0000000
--- a/docs/pre/arch/x86_64/memory/heap/linked_list_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Kernel Heap Linked List Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/heap/linked_list_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/heap/memory_block.rst b/docs/pre/arch/x86_64/memory/heap/memory_block.rst
deleted file mode 100644
index 8ed6566..0000000
--- a/docs/pre/arch/x86_64/memory/heap/memory_block.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Heap Linked List Free Memory Block
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/heap/memory_block.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/heap/user_heap_allocator.rst b/docs/pre/arch/x86_64/memory/heap/user_heap_allocator.rst
deleted file mode 100644
index d0febb6..0000000
--- a/docs/pre/arch/x86_64/memory/heap/user_heap_allocator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-User Heap Linked List Allocator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/heap/user_heap_allocator.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/main.rst b/docs/pre/arch/x86_64/memory/main.rst
deleted file mode 100644
index d9a9f39..0000000
--- a/docs/pre/arch/x86_64/memory/main.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Memory Main
-===========
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/main.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/multiboot.rst b/docs/pre/arch/x86_64/memory/multiboot.rst
deleted file mode 100644
index 22ec3f2..0000000
--- a/docs/pre/arch/x86_64/memory/multiboot.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Kernel Multiboot Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- multiboot/*
diff --git a/docs/pre/arch/x86_64/memory/multiboot/elf_symbols_section.rst b/docs/pre/arch/x86_64/memory/multiboot/elf_symbols_section.rst
deleted file mode 100644
index bbd6dfb..0000000
--- a/docs/pre/arch/x86_64/memory/multiboot/elf_symbols_section.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Multiboot ELF Header Symbols Section Structure
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/multiboot/elf_symbols_section.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/multiboot/info.rst b/docs/pre/arch/x86_64/memory/multiboot/info.rst
deleted file mode 100644
index 847870d..0000000
--- a/docs/pre/arch/x86_64/memory/multiboot/info.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Multiboot Header Information Structure
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/multiboot/info.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/multiboot/memory_map.rst b/docs/pre/arch/x86_64/memory/multiboot/memory_map.rst
deleted file mode 100644
index 9c77331..0000000
--- a/docs/pre/arch/x86_64/memory/multiboot/memory_map.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Multiboot Memory Map Header Structure
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/multiboot/memory_map.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/multiboot/reader.rst b/docs/pre/arch/x86_64/memory/multiboot/reader.rst
deleted file mode 100644
index fac98e2..0000000
--- a/docs/pre/arch/x86_64/memory/multiboot/reader.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Multiboot Reader
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/multiboot/reader.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging.rst b/docs/pre/arch/x86_64/memory/paging.rst
deleted file mode 100644
index 10cd976..0000000
--- a/docs/pre/arch/x86_64/memory/paging.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Virtual Page Table Paging Subsystem
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- paging/*
diff --git a/docs/pre/arch/x86_64/memory/paging/active_page_table.rst b/docs/pre/arch/x86_64/memory/paging/active_page_table.rst
deleted file mode 100644
index 5710131..0000000
--- a/docs/pre/arch/x86_64/memory/paging/active_page_table.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Active Page Table
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/active_page_table.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging/inactive_page_table.rst b/docs/pre/arch/x86_64/memory/paging/inactive_page_table.rst
deleted file mode 100644
index 5732e64..0000000
--- a/docs/pre/arch/x86_64/memory/paging/inactive_page_table.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Inactive Page Table
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/inactive_page_table.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging/kernel_mapper.rst b/docs/pre/arch/x86_64/memory/paging/kernel_mapper.rst
deleted file mode 100644
index 9948e4e..0000000
--- a/docs/pre/arch/x86_64/memory/paging/kernel_mapper.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Kernel Mapper
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/kernel_mapper.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging/page_entry.rst b/docs/pre/arch/x86_64/memory/paging/page_entry.rst
deleted file mode 100644
index 8900b0e..0000000
--- a/docs/pre/arch/x86_64/memory/paging/page_entry.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Virtual Page Table Entry
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/page_entry.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging/page_table.rst b/docs/pre/arch/x86_64/memory/paging/page_table.rst
deleted file mode 100644
index c5ab8c7..0000000
--- a/docs/pre/arch/x86_64/memory/paging/page_table.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Virtual Page Table
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/page_table.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging/temporary_page.rst b/docs/pre/arch/x86_64/memory/paging/temporary_page.rst
deleted file mode 100644
index 0c63899..0000000
--- a/docs/pre/arch/x86_64/memory/paging/temporary_page.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Temporary Virtual Page Table
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/temporary_page.hpp
-
diff --git a/docs/pre/arch/x86_64/memory/paging/virtual_page.rst b/docs/pre/arch/x86_64/memory/paging/virtual_page.rst
deleted file mode 100644
index dd42f47..0000000
--- a/docs/pre/arch/x86_64/memory/paging/virtual_page.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Virtual Page
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/memory/paging/virtual_page.hpp
-
diff --git a/docs/pre/arch/x86_64/stl.rst b/docs/pre/arch/x86_64/stl.rst
deleted file mode 100644
index bb21f9a..0000000
--- a/docs/pre/arch/x86_64/stl.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Standard Library Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- stl/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/stl/container.rst b/docs/pre/arch/x86_64/stl/container.rst
deleted file mode 100644
index 19c735b..0000000
--- a/docs/pre/arch/x86_64/stl/container.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Container Structure
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/container.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/contiguous_pointer_iterator.rst b/docs/pre/arch/x86_64/stl/contiguous_pointer_iterator.rst
deleted file mode 100644
index 47f88c4..0000000
--- a/docs/pre/arch/x86_64/stl/contiguous_pointer_iterator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Contiguous Pointer Iterator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/contiguous_pointer_iterator.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/forward_value_iterator.rst b/docs/pre/arch/x86_64/stl/forward_value_iterator.rst
deleted file mode 100644
index 72270de..0000000
--- a/docs/pre/arch/x86_64/stl/forward_value_iterator.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Forward Value Iterator
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/forward_value_iterator.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/mutex.rst b/docs/pre/arch/x86_64/stl/mutex.rst
deleted file mode 100644
index 2098113..0000000
--- a/docs/pre/arch/x86_64/stl/mutex.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Mutex
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/mutex.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/shared_pointer.rst b/docs/pre/arch/x86_64/stl/shared_pointer.rst
deleted file mode 100644
index 46ddb65..0000000
--- a/docs/pre/arch/x86_64/stl/shared_pointer.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Shared Pointer
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/shared_pointer.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/stack.rst b/docs/pre/arch/x86_64/stl/stack.rst
deleted file mode 100644
index a554387..0000000
--- a/docs/pre/arch/x86_64/stl/stack.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Stack
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/stack.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/unique_pointer.rst b/docs/pre/arch/x86_64/stl/unique_pointer.rst
deleted file mode 100644
index f508763..0000000
--- a/docs/pre/arch/x86_64/stl/unique_pointer.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Unique Pointer
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/unique_pointer.hpp
-
diff --git a/docs/pre/arch/x86_64/stl/vector.rst b/docs/pre/arch/x86_64/stl/vector.rst
deleted file mode 100644
index b60023a..0000000
--- a/docs/pre/arch/x86_64/stl/vector.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Vector
-=======================
-
-.. doxygenfile:: arch/x86_64/include/arch/stl/vector.hpp
-
diff --git a/docs/pre/arch/x86_64/user.rst b/docs/pre/arch/x86_64/user.rst
deleted file mode 100644
index 3be32bb..0000000
--- a/docs/pre/arch/x86_64/user.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-User Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- user/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/user/main.rst b/docs/pre/arch/x86_64/user/main.rst
deleted file mode 100644
index 0f641b2..0000000
--- a/docs/pre/arch/x86_64/user/main.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-User Main
-===========
-
-.. doxygenfile:: arch/x86_64/include/arch/user/main.hpp
-
diff --git a/docs/pre/arch/x86_64/video.rst b/docs/pre/arch/x86_64/video.rst
deleted file mode 100644
index bbae5ed..0000000
--- a/docs/pre/arch/x86_64/video.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-Video Output Subsystem
-======================
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- video/* \ No newline at end of file
diff --git a/docs/pre/arch/x86_64/video/vga.rst b/docs/pre/arch/x86_64/video/vga.rst
deleted file mode 100644
index 2c32bb2..0000000
--- a/docs/pre/arch/x86_64/video/vga.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-VGA Support
-===========
-
-.. toctree::
- :maxdepth: 2
- :caption: Contents:
- :glob:
-
- vga/*
diff --git a/docs/pre/arch/x86_64/video/vga/io.rst b/docs/pre/arch/x86_64/video/vga/io.rst
deleted file mode 100644
index 39609c9..0000000
--- a/docs/pre/arch/x86_64/video/vga/io.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-VGA Input/Output Types and Constants
-====================================
-
-.. doxygenfile:: arch/x86_64/include/arch/video/vga/io.hpp
diff --git a/docs/pre/arch/x86_64/video/vga/text.rst b/docs/pre/arch/x86_64/video/vga/text.rst
deleted file mode 100644
index 592cdd5..0000000
--- a/docs/pre/arch/x86_64/video/vga/text.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-VGA Text Mode
-=============
-
-.. doxygennamespace:: teachos::arch::video::vga::text
- :members:
diff --git a/docs/pre/cross/memory.rst b/docs/pre/cross/memory.rst
deleted file mode 100644
index 3a2c1c4..0000000
--- a/docs/pre/cross/memory.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Memory Access and Management
-============================
-
-This sections details the platform-**independent** infrastructure for memory access and management.
-
-.. toctree::
- :maxdepth: 1
- :glob:
- :caption: Types:
-
- memory/*
diff --git a/docs/pre/cross/memory/asm_pointer.rst b/docs/pre/cross/memory/asm_pointer.rst
deleted file mode 100644
index 70f5c01..0000000
--- a/docs/pre/cross/memory/asm_pointer.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Access to Pointers Defined in Assembly
-======================================
-
-.. doxygenstruct:: teachos::memory::asm_pointer
- :members:
-
-Specializations
----------------
-
-.. doxygenstruct:: teachos::memory::asm_pointer< Type const > \ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 733e873..fc08790 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,2 +1,3 @@
-Sphinx~=8.2.0
-sphinx_book_theme~=1.1.0
+Sphinx~=9.1.0
+sphinx_book_theme~=1.2.0
+breathe~=4.36.0 \ No newline at end of file
diff --git a/kapi/CMakeLists.txt b/kapi/CMakeLists.txt
index 5e914bb..f74cfb3 100644
--- a/kapi/CMakeLists.txt
+++ b/kapi/CMakeLists.txt
@@ -1,34 +1,31 @@
add_library("kapi" INTERFACE)
-add_library("os::kapi" ALIAS "kapi")
+add_library("kapi::lib" ALIAS "kapi")
+
+file(GLOB_RECURSE KAPI_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "include/**.hpp"
+)
target_sources("kapi" PUBLIC
FILE_SET HEADERS
BASE_DIRS "include"
FILES
- "include/kapi/boot.hpp"
- "include/kapi/cio.hpp"
- "include/kapi/memory.hpp"
- "include/kapi/memory/address.hpp"
- "include/kapi/memory/frame_allocator.hpp"
- "include/kapi/memory/frame.hpp"
- "include/kapi/memory/page_mapper.hpp"
- "include/kapi/memory/page.hpp"
- "include/kapi/system.hpp"
+ ${KAPI_HEADERS}
)
target_include_directories("kapi" INTERFACE
- "include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries("kapi" INTERFACE
- "libs::kstd"
+ "acpi::lib"
+ "kstd::lib"
"gcc"
"stdc++"
)
-target_compile_definitions("kapi" INTERFACE
- "PLATFORM_PAGE_SIZE=${TEACHOS_PLATFORM_PAGE_SIZE}uz"
- "PLATFORM_PAGING_LEVELS=${TEACHOS_PLATFORM_PAGING_LEVELS}uz"
- "PLATFORM_FRAME_SIZE=${TEACHOS_PLATFORM_FRAME_SIZE}uz"
+set_target_properties("kapi" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
)
diff --git a/kapi/gdb/__init__.py b/kapi/gdb/__init__.py
new file mode 100644
index 0000000..1b36753
--- /dev/null
+++ b/kapi/gdb/__init__.py
@@ -0,0 +1,35 @@
+import gdb.printing
+
+from .boot_modules import *
+from .devices import *
+from .memory import *
+
+
+def build_pretty_printers():
+ pp = gdb.printing.RegexpCollectionPrettyPrinter("kapi")
+ pp.add_printer(
+ "kapi_memory_address", "^kapi::memory::address<.*>$", KapiMemoryAddressPrinter
+ )
+ pp.add_printer(
+ "kapi_memory_chunk", "^kapi::memory::chunk<.*>$", KapiMemoryChunkPrinter
+ )
+ pp.add_printer("kapi_memory_frame", "^kapi::memory::frame$", KapiMemoryFramePrinter)
+ pp.add_printer("kapi_memory_page", "^kapi::memory::page$", KapiMemoryPagePrinter)
+ pp.add_printer(
+ "kapi_devices_device", "^kapi::devices::device$", KapiDevicesDevicePrinter
+ )
+ pp.add_printer(
+ "kapi_boot_modules_boot_module",
+ "^kapi::boot_modules::boot_module$",
+ KapiBootModulesBootModulePrinter,
+ )
+ pp.add_printer(
+ "kapi_boot_modules_boot_module_registry",
+ "^kapi::boot_modules::boot_module_registry$",
+ KapiBootModulesBootModuleRegistryPrinter,
+ )
+ return pp
+
+
+def register_printers(objfile):
+ gdb.printing.register_pretty_printer(objfile, build_pretty_printers(), replace=True)
diff --git a/kapi/gdb/boot_modules/__init__.py b/kapi/gdb/boot_modules/__init__.py
new file mode 100644
index 0000000..2a54136
--- /dev/null
+++ b/kapi/gdb/boot_modules/__init__.py
@@ -0,0 +1,2 @@
+from .boot_module import KapiBootModulesBootModulePrinter
+from .boot_module_registry import KapiBootModulesBootModuleRegistryPrinter
diff --git a/kapi/gdb/boot_modules/boot_module.py b/kapi/gdb/boot_modules/boot_module.py
new file mode 100644
index 0000000..97e6584
--- /dev/null
+++ b/kapi/gdb/boot_modules/boot_module.py
@@ -0,0 +1,44 @@
+import gdb
+from teachos import format_size
+
+
+class KapiBootModulesBootModulePrinter(gdb.ValuePrinter):
+ class Iterator:
+ def __init__(self, begin: gdb.Value, end: gdb.Value):
+ self._item = begin
+ self._end = end
+ self._count = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ count = self._count
+ self._count = count + 1
+
+ if self._item == self._end:
+ raise StopIteration
+
+ element = self._item.dereference()
+ self._item += 1
+ return (f"[{count}]", element)
+
+ def __init__(self, val):
+ self.__pointer_type = gdb.lookup_type("std::byte").pointer()
+
+ self.__val = val
+ self.__name = val["name"]
+ self.__start = val["start_address"]
+ self.__size = int(val["size"])
+ self.__begin = val["start_address"]["m_value"].cast(self.__pointer_type)
+ self.__end = self.__begin + self.__size
+ self.__pretty_name = " " + str(self.__name) if str(self.__name) != '""' else ""
+
+ def to_string(self):
+ return f"boot module{self.__pretty_name} of size {format_size(self.__size)}, at {self.__start.cast(self.__pointer_type)}"
+
+ def children(self):
+ return self.Iterator(self.__begin, self.__end)
+
+ def display_hint(self):
+ return "array"
diff --git a/kapi/gdb/boot_modules/boot_module_registry.py b/kapi/gdb/boot_modules/boot_module_registry.py
new file mode 100644
index 0000000..599a823
--- /dev/null
+++ b/kapi/gdb/boot_modules/boot_module_registry.py
@@ -0,0 +1,20 @@
+import gdb
+from teachos import format_size
+
+
+class KapiBootModulesBootModuleRegistryPrinter(gdb.ValuePrinter):
+ def __init__(self, val: gdb.Value):
+ self.__val = val
+ self.__modules = val["m_modules"]
+ self.__size = int(self.__modules["m_size"])
+ self.__element_type = gdb.lookup_type("kapi::boot_modules::boot_module")
+
+ def to_string(self):
+ return f"boot module registry of size {self.__size}"
+
+ def children(self):
+ yield ("[size]", self.__size)
+ yield ("m_modules", self.__modules)
+
+ def display_hint(self):
+ return None
diff --git a/kapi/gdb/devices/__init__.py b/kapi/gdb/devices/__init__.py
new file mode 100644
index 0000000..3bab1ea
--- /dev/null
+++ b/kapi/gdb/devices/__init__.py
@@ -0,0 +1 @@
+from .device import KapiDevicesDevicePrinter
diff --git a/kapi/gdb/devices/device.py b/kapi/gdb/devices/device.py
new file mode 100644
index 0000000..8e515ef
--- /dev/null
+++ b/kapi/gdb/devices/device.py
@@ -0,0 +1,20 @@
+import gdb
+
+
+class KapiDevicesDevicePrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+
+ def to_string(self):
+ return (
+ f"{self.__val['m_name']} @ {self.__val['m_major']}:{self.__val['m_minor']}"
+ )
+
+ def children(self):
+ yield ("major", self.__val["m_major"])
+ yield ("minor", self.__val["m_minor"])
+ yield ("name", self.__val["m_name"])
+ yield ("parent", self.__val["m_parent"])
+
+ def display_hint(self):
+ return None
diff --git a/kapi/gdb/memory/__init__.py b/kapi/gdb/memory/__init__.py
new file mode 100644
index 0000000..2aa6564
--- /dev/null
+++ b/kapi/gdb/memory/__init__.py
@@ -0,0 +1,4 @@
+from .address import KapiMemoryAddressPrinter
+from .chunk import KapiMemoryFramePrinter
+from .chunk import KapiMemoryPagePrinter
+from .chunk import KapiMemoryChunkPrinter
diff --git a/kapi/gdb/memory/address.py b/kapi/gdb/memory/address.py
new file mode 100644
index 0000000..429db1d
--- /dev/null
+++ b/kapi/gdb/memory/address.py
@@ -0,0 +1,31 @@
+import gdb
+
+
+class KapiMemoryAddressPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+
+ def to_string(self):
+ try:
+ raw_address = int(self.__val["m_value"])
+ type_string = str(self.__type)
+
+ if "linear" in type_string:
+ suffix = "%lin"
+ elif "physical" in type_string:
+ suffix = "%phy"
+ else:
+ suffix = "%???"
+
+ return f"{raw_address:#018x}{suffix}"
+ except Exception as e:
+ return f"{self.__val}: {e}"
+
+ def children(self):
+ if "linear" in str(self.__type):
+ pointer_type = gdb.lookup_type("std::byte").pointer()
+ yield ("[bytes]", self.__val["m_value"].cast(pointer_type))
+
+ def display_hint(self):
+ return None
diff --git a/kapi/gdb/memory/chunk.py b/kapi/gdb/memory/chunk.py
new file mode 100644
index 0000000..74b1407
--- /dev/null
+++ b/kapi/gdb/memory/chunk.py
@@ -0,0 +1,41 @@
+import gdb
+from teachos import format_size
+
+
+class KapiMemoryChunkPrinter(gdb.ValuePrinter):
+
+ def __init__(self, val: gdb.Value, typename="chunk"):
+ self.__val = val
+ try:
+ self.__number = int(val["m_number"])
+ except gdb.error:
+ self.__number = "<number unreadable>"
+
+ try:
+ self.__size = int(gdb.parse_and_eval(f"{val.type.name}::size")["value"])
+ except gdb.error:
+ self.__size = "<size unreadable>"
+
+ self.__typename = typename
+
+ def to_string(self):
+ return f"{self.__typename} {self.__number} of size {format_size(self.__size)}"
+
+ def children(self):
+ yield ("number", self.__number)
+ yield ("size", self.__size)
+
+ def display_hint(self):
+ return None
+
+
+class KapiMemoryFramePrinter(KapiMemoryChunkPrinter):
+
+ def __init__(self, val):
+ super().__init__(val, "frame")
+
+
+class KapiMemoryPagePrinter(KapiMemoryChunkPrinter):
+
+ def __init__(self, val):
+ super().__init__(val, "page")
diff --git a/kapi/include/kapi/cpu.hpp b/kapi/include/kapi/cpu.hpp
deleted file mode 100644
index 05b84b7..0000000
--- a/kapi/include/kapi/cpu.hpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#ifndef TEACHOS_KAPI_CPU_HPP
-#define TEACHOS_KAPI_CPU_HPP
-
-namespace kapi::cpu
-{
- //! @qualifier platform-defined
- //! Halt the CPU.
- //!
- //! This function terminates execution of the kernel.
- [[noreturn]] auto halt() -> void;
-} // namespace kapi::cpu
-
-#endif
diff --git a/kapi/include/kapi/memory/layout.hpp b/kapi/include/kapi/memory/layout.hpp
deleted file mode 100644
index f5ba0f9..0000000
--- a/kapi/include/kapi/memory/layout.hpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#ifndef TEACHOS_KAPI_MEMORY_LAYOUT_HPP
-#define TEACHOS_KAPI_MEMORY_LAYOUT_HPP
-
-// IWYU pragma: private, include "kapi/memory.hpp"
-
-#include "kapi/memory/address.hpp"
-
-namespace kapi::memory
-{
-
- constexpr auto page_size = PLATFORM_PAGE_SIZE;
- constexpr auto frame_size = PLATFORM_FRAME_SIZE;
-
- constexpr auto higher_half_direct_map_base = linear_address{0xffff'8000'0000'0000uz};
-
- constexpr auto heap_base = linear_address{0xffff'c000'0000'0000uz};
-
- constexpr auto pmm_metadata_base = linear_address{0xffff'd000'0000'0000uz};
-
- constexpr auto mmio_base = linear_address{0xffff'e000'0000'0000uz};
-
- constexpr auto kernel_base = linear_address{0xffff'ffff'8000'0000uz};
-
-} // namespace kapi::memory
-
-#endif \ No newline at end of file
diff --git a/kapi/kapi.dox b/kapi/kapi.dox
new file mode 100644
index 0000000..929fc1f
--- /dev/null
+++ b/kapi/kapi.dox
@@ -0,0 +1,8 @@
+//! @namespace kapi
+//! The Kernel/Platform API
+//!
+//! This namespace defines the interface between the platform independent kernel and each supported platform.
+
+//! @defgroup kapi-kernel-defined Kernel-defined API
+
+//! @defgroup kapi-platform-defined Platform-defined API
diff --git a/kapi/kapi/acpi.hpp b/kapi/kapi/acpi.hpp
new file mode 100644
index 0000000..885fcde
--- /dev/null
+++ b/kapi/kapi/acpi.hpp
@@ -0,0 +1,40 @@
+#ifndef TEACHOS_KAPI_ACPI_HPP
+#define TEACHOS_KAPI_ACPI_HPP
+
+#include <acpi/acpi.hpp>
+
+#include <kstd/memory>
+#include <kstd/units>
+
+#include <string_view>
+
+namespace kapi::acpi
+{
+
+ //! @addtogroup kapi-acpi-kernel-defined
+ //! @{
+
+ //! Initialize the ACPI subsystem and discover the available tables.
+ //!
+ //! @return true iff. a valid system description tabled was found, false otherwise.
+ auto init(::acpi::rsdp const & sdp) -> bool;
+
+ //! Get a pointer to an ACPI table by its signature.
+ //!
+ //! @param signature The signature of the table to get.
+ //! @return A pointer to the table if found, nullptr otherwise.
+ auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>;
+
+ //! Get a type-cast pointer to an ACPI table by its signature.
+ //!
+ //! @tparam Signature The signature of the table to get
+ //! @return A pointer to the table if found, nullptr otherwise.
+ template<char const * Signature>
+ auto get_table() -> kstd::observer_ptr<::acpi::table_type_t<Signature> const>
+ {
+ return kstd::make_observer(static_cast<::acpi::table_type_t<Signature> const *>(get_table(Signature).get()));
+ }
+
+} // namespace kapi::acpi
+
+#endif
diff --git a/kapi/include/kapi/boot.hpp b/kapi/kapi/boot.hpp
index 55ca941..55ca941 100644
--- a/kapi/include/kapi/boot.hpp
+++ b/kapi/kapi/boot.hpp
diff --git a/kapi/kapi/boot_module/boot_module.hpp b/kapi/kapi/boot_module/boot_module.hpp
new file mode 100644
index 0000000..9b4b165
--- /dev/null
+++ b/kapi/kapi/boot_module/boot_module.hpp
@@ -0,0 +1,23 @@
+#ifndef TEACHOS_KAPI_BOOT_MODULE_BOOT_MODULE_HPP
+#define TEACHOS_KAPI_BOOT_MODULE_BOOT_MODULE_HPP
+
+#include <kapi/memory.hpp>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::boot_modules
+{
+ // ! The boot module struct
+ // !
+ // ! The boot module struct represents a module loaded by the bootloader, and contains information about it, such as
+ // ! its name, virtual start address, and size.
+ struct boot_module
+ {
+ std::string_view name{};
+ memory::linear_address start_address{};
+ std::size_t size{};
+ };
+} // namespace kapi::boot_modules
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/boot_module/boot_module_registry.hpp b/kapi/kapi/boot_module/boot_module_registry.hpp
new file mode 100644
index 0000000..fc3590f
--- /dev/null
+++ b/kapi/kapi/boot_module/boot_module_registry.hpp
@@ -0,0 +1,107 @@
+#ifndef TEACHOS_KAPI_BOOT_MODULE_BOOT_MODULE_REGISTRY_HPP
+#define TEACHOS_KAPI_BOOT_MODULE_BOOT_MODULE_REGISTRY_HPP
+
+#include <kapi/boot_module/boot_module.hpp>
+
+#include <kstd/vector>
+
+#include <cstddef>
+
+namespace kapi::boot_modules
+{
+
+ // ! The interface of the boot module registry
+ // !
+ // ! The boot module registry is responsible for keeping track of the modules loaded by the bootloader, and
+ // ! providing access to them for the rest of the kernel.
+ struct boot_module_registry
+ {
+ using range_type = kstd::vector<boot_module>;
+ using value_type = range_type::value_type;
+ using const_reference = range_type::const_reference;
+
+ using const_iterator = range_type::const_iterator;
+ using const_reverse_iterator = range_type::const_reverse_iterator;
+ using size_type = range_type::size_type;
+
+ [[nodiscard]] auto begin() const noexcept -> const_iterator
+ {
+ return m_modules.begin();
+ }
+
+ [[nodiscard]] auto end() const noexcept -> const_iterator
+ {
+ return m_modules.end();
+ }
+
+ [[nodiscard]] auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ [[nodiscard]] auto cend() const noexcept -> const_iterator
+ {
+ return end();
+ }
+
+ [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator
+ {
+ return m_modules.rbegin();
+ }
+
+ [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator
+ {
+ return m_modules.rend();
+ }
+
+ [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator
+ {
+ return rbegin();
+ }
+
+ [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator
+ {
+ return rend();
+ }
+
+ [[nodiscard]] auto front() const noexcept -> const_reference
+ {
+ return m_modules.front();
+ }
+
+ [[nodiscard]] auto back() const noexcept -> const_reference
+ {
+ return m_modules.back();
+ }
+
+ [[nodiscard]] auto size() const noexcept -> std::size_t
+ {
+ return m_modules.size();
+ }
+
+ [[nodiscard]] auto empty() const noexcept -> bool
+ {
+ return m_modules.empty();
+ }
+
+ [[nodiscard]] auto at(std::size_t index) const -> const_reference
+ {
+ return m_modules.at(index);
+ }
+
+ [[nodiscard]] auto operator[](std::size_t index) const noexcept -> const_reference
+ {
+ return m_modules[index];
+ }
+
+ auto add_boot_module(boot_module module) -> void
+ {
+ m_modules.push_back(module);
+ }
+
+ private:
+ range_type m_modules{};
+ };
+} // namespace kapi::boot_modules
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/boot_modules.hpp b/kapi/kapi/boot_modules.hpp
new file mode 100644
index 0000000..2a88f74
--- /dev/null
+++ b/kapi/kapi/boot_modules.hpp
@@ -0,0 +1,31 @@
+#ifndef TEACHOS_KAPI_BOOT_MODULES_HPP
+#define TEACHOS_KAPI_BOOT_MODULES_HPP
+
+#include <kapi/boot_module/boot_module_registry.hpp> // IWYU pragma: export
+
+namespace kapi::boot_modules
+{
+
+ //! @qualifier platform-defined
+ //! Initialize the boot module registry.
+ //!
+ //! @note This function must be implemented by the target platform.
+ //!
+ //! This function initializes the boot module registry, which is responsible for keeping track of the modules loaded
+ //! by the bootloader, and providing access to them for the rest of the kernel.
+ auto init() -> void;
+
+ //! @qualifier kernel-defined
+ //! Set the boot module registry
+ //!
+ //! @param registry A new boot module registry.
+ auto set_boot_module_registry(boot_module_registry & registry) -> void;
+
+ //! @qualifier kernel-defined
+ //! Get the boot module registry.
+ //!
+ //! @returns The boot module registry.
+ auto get_boot_module_registry() -> boot_module_registry const &;
+
+} // namespace kapi::boot_modules
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/cio.hpp b/kapi/kapi/cio.hpp
index 48f3000..9bbf7fa 100644
--- a/kapi/include/kapi/cio.hpp
+++ b/kapi/kapi/cio.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_KAPI_CIO_HPP
#define TEACHOS_KAPI_CIO_HPP
-#include "kapi/cio/output_device.hpp" // IWYU pragma: export
+#include <kapi/cio/output_device.hpp> // IWYU pragma: export
#include <kstd/format>
@@ -11,22 +11,38 @@
namespace kapi::cio
{
- //! @qualifier platform-defined
- //! Initialize the character I/O subsystem.
- //!
- //! @note If a platform support character output, it shall ensure that when this function returns, basic character
- //! output can be performed on the system.
- auto init() -> void;
+ //! @addtogroup kapi-cio
+ //! @{
+ //! @}
+
+ //! @addtogroup kapi-cio-kernel-defined
+ //! @{
- //! @qualifier kernel-defined
//! Set the currently active output device.
//!
//! @param device A new output device.
//! @return The previously active output device.
auto set_output_device(output_device & device) -> std::optional<output_device *>;
+ //! Write a string to the given output stream.
+ //!
+ //! @param stream The output stream to write to.
+ //! @param text The text to write to the stream.
auto write(output_stream stream, std::string_view text) -> void;
+ //! @}
+
+ //! @addtogroup kapi-cio-platform-defined
+ //! @{
+
+ //! Initialize the character I/O subsystem.
+ //!
+ //! If a platform support character output, it shall ensure that when this function returns, basic character
+ //! output can be performed on the system.
+ auto init() -> void;
+
+ //! @}
+
} // namespace kapi::cio
#endif
diff --git a/kapi/include/kapi/cio/output_device.hpp b/kapi/kapi/cio/output_device.hpp
index f08d7ba..9fe2557 100644
--- a/kapi/include/kapi/cio/output_device.hpp
+++ b/kapi/kapi/cio/output_device.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_KAPI_CIO_OUTPUT_DEVICE_HPP
#define TEACHOS_KAPI_CIO_OUTPUT_DEVICE_HPP
-// IWYU pragma: private, include "kapi/cio.hpp"
+// IWYU pragma: private, include <kapi/cio.hpp>
#include <string_view>
diff --git a/kapi/kapi/cpu.hpp b/kapi/kapi/cpu.hpp
new file mode 100644
index 0000000..deaf5cd
--- /dev/null
+++ b/kapi/kapi/cpu.hpp
@@ -0,0 +1,135 @@
+#ifndef TEACHOS_KAPI_CPU_HPP
+#define TEACHOS_KAPI_CPU_HPP
+
+#include <kapi/memory.hpp>
+
+#include <kstd/format>
+#include <kstd/memory>
+
+#include <cstdint>
+
+namespace kapi::cpu
+{
+
+ //! @addtogroup kapi-cpu
+ //! @{
+
+ //! An exception originating from the CPU directly.
+ //!
+ //! Exception generally model interrupts that are synchronous to the instruction stream. This means that they do not
+ //! originate from external hardware, but rather are a product of program execution.
+ struct exception
+ {
+ //! The type of the exception, which identifies the reason for it being raised.
+ enum class type : std::uint8_t
+ {
+ //! The reason for the exception is unknown or platform-specific
+ unknown,
+ //! A page fault occurred
+ page_fault,
+ //! A memory access (either in the data or instruction stream) violated it's alignment constraints.
+ alignment_fault,
+ //! A memory access (either in the data or instruction stream) violated it's permissions.
+ memory_access_fault,
+ //! An invalid instruction was present in the instruction stream.
+ illegal_instruction,
+ //! The preconditions for the execution of an instruction were not met.
+ privilege_violation,
+ //! An arithmetic error occurred.
+ arithmetic_error,
+ //! A breakpoint was hit in the instruction stream.
+ breakpoint,
+ //! The CPU is single-stepping through the instruction stream.
+ single_step,
+ };
+
+ //! The type of this exception.
+ type type{};
+
+ //! The value of the instruction pointer at the time this exception was raised.
+ kapi::memory::linear_address instruction_pointer{};
+
+ //! The memory address that caused this exception.
+ kapi::memory::linear_address access_address{};
+
+ //! Whether the page was present at the time of the exception.
+ bool is_present{};
+
+ //! Whether the exception was caused by a write access.
+ bool is_write_access{};
+
+ //! Whether the exception was caused by a user mode access.
+ bool is_user_mode{};
+ };
+
+ //! @}
+
+ //! @addtogroup kapi-cpu-kernel-defined
+ //! @{
+
+ //! Dispatch an exception to the appropriate handler.
+ //!
+ //! @param context The exception context.
+ //! @return Whether the exception was handled.
+ [[nodiscard]] auto dispatch(exception const & context) -> bool;
+
+ //! @}
+
+ //! @addtogroup kapi-cpu-platform-defined
+ //! @{
+
+ //! Halt the CPU.
+ //!
+ //! This function terminates execution of the kernel.
+ [[noreturn]] auto halt() -> void;
+
+ //! Perform early CPU initialization.
+ //!
+ //! When this function returns, the CPU is in a state where interrupt could be enabled. This function must not enable
+ //! interrupts itself.
+ auto init() -> void;
+
+ //! Discover the CPU topology of the platform and attach it to the given CPU bus.
+ //!
+ //! @return true iff. the CPU topology was discovered successfully, false otherwise.
+ auto discover_topology() -> bool;
+
+ //! @}
+
+} // namespace kapi::cpu
+
+template<>
+struct kstd::formatter<enum kapi::cpu::exception::type>
+{
+ constexpr auto parse(kstd::format_parse_context & ctx) -> decltype(ctx.begin())
+ {
+ return ctx.begin();
+ }
+
+ constexpr auto format(enum kapi::cpu::exception::type type, kstd::format_context & ctx) const -> void
+ {
+ switch (type)
+ {
+ case kapi::cpu::exception::type::unknown:
+ return ctx.push("unknown");
+ case kapi::cpu::exception::type::page_fault:
+ return ctx.push("page fault");
+ case kapi::cpu::exception::type::alignment_fault:
+ return ctx.push("alignment fault");
+ case kapi::cpu::exception::type::memory_access_fault:
+ return ctx.push("memory access fault");
+ case kapi::cpu::exception::type::illegal_instruction:
+ return ctx.push("illegal instruction");
+ case kapi::cpu::exception::type::privilege_violation:
+ return ctx.push("privilege violation");
+ case kapi::cpu::exception::type::arithmetic_error:
+ return ctx.push("arithmetic error");
+ case kapi::cpu::exception::type::breakpoint:
+ return ctx.push("breakpoint");
+ case kapi::cpu::exception::type::single_step:
+ return ctx.push("single step");
+ }
+ }
+};
+
+#endif
diff --git a/kapi/kapi/devices.hpp b/kapi/kapi/devices.hpp
new file mode 100644
index 0000000..b597aa8
--- /dev/null
+++ b/kapi/kapi/devices.hpp
@@ -0,0 +1,37 @@
+#ifndef TEACHOS_KAPI_DEVICES_HPP
+#define TEACHOS_KAPI_DEVICES_HPP
+
+#include <kapi/devices/bus.hpp> // IWYU pragma: export
+#include <kapi/devices/cpu.hpp> // IWYU pragma: export
+#include <kapi/devices/device.hpp> // IWYU pragma: export
+#include <kapi/devices/manager.hpp> // IWYU pragma: export
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kapi-devices-kernel-defined
+ //! @{
+
+ //! Initialize the kernel's device management subsystem.
+ auto init() -> void;
+
+ //! Get the virtual system root bus.
+ //!
+ //! @warning This function will panic if the root bus has not been initialized.
+ //!
+ //! @return a reference to the root bus.
+ auto get_root_bus() -> bus &;
+
+ //! @}
+
+ //! @addtogroup kapi-devices-platform-defined
+ //! @{
+
+ //! Initialize the platform's device tree.
+ auto init_platform_devices() -> void;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/devices/bus.hpp b/kapi/kapi/devices/bus.hpp
new file mode 100644
index 0000000..59f49f7
--- /dev/null
+++ b/kapi/kapi/devices/bus.hpp
@@ -0,0 +1,63 @@
+#ifndef TEACHOS_KAPI_DEVICES_BUS_HPP
+#define TEACHOS_KAPI_DEVICES_BUS_HPP
+
+// IWYU pragma: private, include <kapi/devices.hpp>
+
+#include <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <atomic>
+#include <cstddef>
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kapi-devices-kernel-defined
+ //! @{
+
+ //! A bus device that represents a logical/physical tree of devices and busses.
+ struct bus : device
+ {
+ //! Construct a bus with the given major number, minor number, and name.
+ //!
+ //! @param major The major number of the bus.
+ //! @param minor The minor number of the bus.
+ //! @param name The name of the bus.
+ bus(std::size_t major, std::size_t minor, kstd::string const & name);
+
+ //! Initialize the bus and all of its children.
+ //!
+ //! @return true iff. the bus and all of its children are healthy, false otherwise.
+ auto init() -> bool final;
+
+ //! Attach a child device to this bus.
+ //!
+ //! Whenever a device is attached to a bus, the bus takes sole ownership of the device.
+ //!
+ //! @param child The child device to attach.
+ auto add_child(kstd::unique_ptr<device> child) -> void;
+
+ [[nodiscard]] auto children() const -> kstd::vector<kstd::observer_ptr<device>> const &;
+
+ protected:
+ //! Probe the bus hardware state.
+ //!
+ //! @return true iff. the bus hardware is healthy, false otherwise.
+ auto virtual probe() -> bool;
+
+ private:
+ kstd::vector<kstd::unique_ptr<device>> m_devices;
+ kstd::vector<kstd::observer_ptr<device>> m_observers;
+ std::atomic_flag m_init_was_called{};
+ std::atomic_flag m_initialized{};
+ };
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/devices/cpu.hpp b/kapi/kapi/devices/cpu.hpp
new file mode 100644
index 0000000..f8ff60c
--- /dev/null
+++ b/kapi/kapi/devices/cpu.hpp
@@ -0,0 +1,37 @@
+#ifndef TEACHOS_KAPI_DEVICES_CPU_HPP
+#define TEACHOS_KAPI_DEVICES_CPU_HPP
+
+#include <kapi/devices/bus.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace kapi::devices
+{
+
+ //! A virtual CPU bus to host all CPUs in the system.
+ struct cpu final : kapi::devices::bus
+ {
+ //! A virtual CPU Core to host all core local devices.
+ struct core final : kapi::devices::bus
+ {
+ explicit core(std::size_t major_number, std::size_t minor_number, std::uint64_t hardware_id, bool is_bsp);
+
+ [[nodiscard]] auto hardware_id() const -> std::uint64_t;
+ [[nodiscard]] auto is_bsp() const -> bool;
+
+ private:
+ std::uint64_t m_hardware_id;
+ bool m_is_bsp;
+ };
+
+ //! Create a new CPU with the given major and minor numbers.
+ //!
+ //! @param major The major number of this CPU
+ //! @param minor The minor number of this CPU, identifying the physical CPU
+ cpu(std::size_t major, std::size_t minor);
+ };
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/devices/device.hpp b/kapi/kapi/devices/device.hpp
new file mode 100644
index 0000000..70cf01f
--- /dev/null
+++ b/kapi/kapi/devices/device.hpp
@@ -0,0 +1,80 @@
+#ifndef TEACH_OS_KAPI_DEVICES_DEVICE_HPP
+#define TEACH_OS_KAPI_DEVICES_DEVICE_HPP
+
+// IWYU pragma: private, include <kapi/devices.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+
+#include <cstddef>
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kapi-devices-kernel-defined
+ //! @{
+
+ /**
+ * @brief Base device identified by a major, minor number and name.
+ */
+ struct device
+ {
+ /**
+ * @brief Create a device identifier from @p major, @p minor and @p name.
+ * @param major Device major number.
+ * @param minor Device minor number.
+ * @param name Device name.
+ */
+ device(size_t major, size_t minor, kstd::string const & name);
+
+ /**
+ * @brief Virtual destructor for device.
+ */
+ virtual ~device() = default;
+
+ /**
+ * @brief Initialize the device.
+ * @return true on success, false otherwise.
+ */
+ virtual auto init() -> bool = 0;
+
+ /**
+ * @brief Returns the major number of the device.
+ * @return Device major number.
+ */
+ [[nodiscard]] auto major() const -> size_t;
+
+ /**
+ * @brief Returns the minor number of the device.
+ * @return Device minor number.
+ */
+ [[nodiscard]] auto minor() const -> size_t;
+
+ /**
+ * @brief Returns the name of the device.
+ * @return Device name.
+ */
+ [[nodiscard]] auto name() const -> kstd::string const &;
+
+ /**
+ * @brief Check if the device is a block device.
+ * @return true if this device is a block device, false otherwise.
+ */
+ [[nodiscard]] virtual auto is_block_device() const -> bool;
+
+ private:
+ friend struct bus;
+
+ auto set_parent(kstd::observer_ptr<struct bus> parent) -> void;
+
+ size_t m_major;
+ size_t m_minor;
+ kstd::string m_name;
+ kstd::observer_ptr<bus> m_parent;
+ };
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/devices/manager.hpp b/kapi/kapi/devices/manager.hpp
new file mode 100644
index 0000000..c9b90b4
--- /dev/null
+++ b/kapi/kapi/devices/manager.hpp
@@ -0,0 +1,53 @@
+#ifndef TEACHOS_KAPI_DEVICES_MANAGER_HPP
+#define TEACHOS_KAPI_DEVICES_MANAGER_HPP
+
+// IWYU pragma: private, include <kapi/devices.hpp>
+
+#include <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kapi-devices-kernel-defined
+ //! @{
+
+ //! Ask the kernel to allocate a new major number.
+ //!
+ //! @return a new, unused major number.
+ auto allocate_major_number() -> std::size_t;
+
+ //! Register a new device with the kernel's device manager.
+ //!
+ //! @param device The device to register.
+ //! @return true if the device was registered successfully, false otherwise.
+ auto register_device(device & device) -> bool;
+
+ //! Unregister a device from the kernel's device manager.
+ //!
+ //! @param device The device to unregister.
+ //! @return true if the device was unregistered successfully, false otherwise.
+ auto unregister_device(device & device) -> bool;
+
+ //! Find a device by its major and minor numbers.
+ //!
+ //! @param major the major number of the device.
+ //! @param minor the minor number of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>;
+
+ //! Find a device by its name.
+ //!
+ //! @param name the name of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/filesystem.hpp b/kapi/kapi/filesystem.hpp
new file mode 100644
index 0000000..3bd9aaf
--- /dev/null
+++ b/kapi/kapi/filesystem.hpp
@@ -0,0 +1,73 @@
+#ifndef TEACHOS_KAPI_FILESYSTEM_HPP
+#define TEACHOS_KAPI_FILESYSTEM_HPP
+
+#include <kstd/unikstd.h>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::filesystem
+{
+ /**
+ @brief The kapi::filesystem namespace provides the interface for filesystem operations in the kernel. It includes
+ functions for mounting and unmounting filesystems, as well as basic file operations such as opening, closing, reading
+ from, and writing to files. The actual implementation of these functions is in the kernel's filesystem subsystem,
+ which will handle the specifics of different filesystem types and their interactions with the underlying storage
+ devices.
+ */
+
+ /**
+ @brief Mounts a filesystem from the specified @p source at the specified @p target path.
+ @param source The source device or filesystem to mount.
+ @param target The target mount point.
+ @return 0 on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto mount(std::string_view source, std::string_view target) -> kstd::ssize_t;
+
+ /**
+ @brief Unmounts a filesystem from the specified @p target path.
+ @param target The target mount point to unmount.
+ @return 0 on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto umount(std::string_view target) -> kstd::ssize_t;
+
+ /**
+ @brief Opens a file at the specified @p path.
+ @param path The path to the file to open.
+ @return A file descriptor on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto open(std::string_view path) -> kstd::ssize_t;
+
+ /**
+ @brief Closes a @p file_descriptor.
+ @param file_descriptor The file descriptor to close.
+ @return 0 on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto close(size_t file_descriptor) -> kstd::ssize_t;
+
+ /**
+ @brief Reads @p size bytes into @p buffer from a @p file_descriptor.
+ @param file_descriptor The file descriptor to read from.
+ @param buffer The buffer to store the read data.
+ @param size The number of bytes to read.
+ @return The number of bytes read on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto read(size_t file_descriptor, void * buffer, size_t size) -> kstd::ssize_t;
+
+ /**
+ @brief Writes @p size bytes from @p buffer to a @p file_descriptor.
+ @param file_descriptor The file descriptor to write to.
+ @param buffer The buffer containing the data to write.
+ @param size The number of bytes to write.
+ @return The number of bytes written on success, -1 on failure.
+ @qualifier kernel-defined
+ */
+ auto write(size_t file_descriptor, void const * buffer, size_t size) -> kstd::ssize_t;
+} // namespace kapi::filesystem
+
+#endif // TEACHOS_KAPI_FILESYSTEM_HPP \ No newline at end of file
diff --git a/kapi/kapi/interrupts.hpp b/kapi/kapi/interrupts.hpp
new file mode 100644
index 0000000..4ba0684
--- /dev/null
+++ b/kapi/kapi/interrupts.hpp
@@ -0,0 +1,74 @@
+#ifndef TEACHOS_KAPI_INTERRUPTS_HPP
+#define TEACHOS_KAPI_INTERRUPTS_HPP
+
+#include <cstdint>
+
+namespace kapi::interrupts
+{
+
+ //! @addtogroup kapi-interrupts
+ //! @{
+
+ //! A status that indicates whether an interrupt was handled by a handler.
+ enum class status : bool
+ {
+ //! The interrupt was not handled by any handler.
+ unhandled,
+ //! The interrupt was handled by at least one handler.
+ handled,
+ };
+
+ //! The interface for all interrupt handlers.
+ struct handler
+ {
+ virtual ~handler() = default;
+
+ //! Handle an interrupt with the given IRQ number.
+ //
+ //! This function will be called by the kernel in an interrupt context. As such, the function should complete its
+ //! task quickly and must take care when acquiring globally shared locks.
+ //!
+ //! @param irq_number The identifier of the interrupt request that triggered the handler.
+ //! @return status::handled if the handler successfully handled the interrupt, status::unhandled otherwise.
+ virtual auto handle_interrupt(std::uint32_t irq_number) -> status = 0;
+ };
+
+ //! @}
+
+ //! @addtogroup kapi-interrupts-kernel-defined
+ //! @{
+
+ //! Register an interrupt handler for the given IRQ number.
+ //!
+ //! @param irq_number The IRQ number to register the handler for.
+ //! @param handler The interrupt handler to register.
+ auto register_handler(std::uint32_t irq_number, handler & handler) -> void;
+
+ //! Unregister a previously registered interrupt handler.
+ //!
+ //! @param irq_number The IRQ number to unregister the handler for.
+ //! @param handler The interrupt handler to unregister.
+ auto unregister_handler(std::uint32_t irq_number, handler & handler) -> void;
+
+ //! Dispatch an interrupt to all registered handlers.
+ //!
+ //! @param irq_number The IRQ number to dispatch.
+ //! @return status::handled if the interrupt was handled by at least one handler, status::unhandled otherwise.
+ auto dispatch(std::uint32_t irq_number) -> status;
+
+ //! @}
+
+ //! @addtogroup kapi-interrupts-platform-defined
+ //! @{
+
+ //! Enable external interrupts.
+ auto enable() -> void;
+
+ //! Disable external interrupts.
+ auto disable() -> void;
+
+ //! @}
+
+} // namespace kapi::interrupts
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/memory.hpp b/kapi/kapi/memory.hpp
index e31fa34..8ad8d6e 100644
--- a/kapi/include/kapi/memory.hpp
+++ b/kapi/kapi/memory.hpp
@@ -1,13 +1,13 @@
#ifndef TEACHOS_KAPI_MEMORY_HPP
#define TEACHOS_KAPI_MEMORY_HPP
-#include "kapi/memory/address.hpp" // IWYU pragma: export
-#include "kapi/memory/chunk.hpp" // IWYU pragma: export
-#include "kapi/memory/frame.hpp" // IWYU pragma: export
-#include "kapi/memory/frame_allocator.hpp" // IWYU pragma: export
-#include "kapi/memory/layout.hpp" // IWYU pragma: export
-#include "kapi/memory/page.hpp" // IWYU pragma: export
-#include "kapi/memory/page_mapper.hpp" // IWYU pragma: export
+#include <kapi/memory/address.hpp> // IWYU pragma: export
+#include <kapi/memory/chunk.hpp> // IWYU pragma: export
+#include <kapi/memory/frame.hpp> // IWYU pragma: export
+#include <kapi/memory/frame_allocator.hpp> // IWYU pragma: export
+#include <kapi/memory/layout.hpp> // IWYU pragma: export
+#include <kapi/memory/page.hpp> // IWYU pragma: export
+#include <kapi/memory/page_mapper.hpp> // IWYU pragma: export
#include <cstddef>
#include <optional>
@@ -16,16 +16,11 @@
namespace kapi::memory
{
- //! @qualifier platform-defined
- //! Initialize the memory subsystem.
- //!
- //! @note This function must be implemented by the target platform.
- //!
- //! This function initializes the memory subsystem and activates the platform-specific frame allocator and page
- //! mapper. When this function returns, a valid frame allocator and page mapper are expected to have been registered.
- auto init() -> void;
+ using mmio_region = std::pair<linear_address, std::size_t>;
+
+ //! @addtogroup kapi-memory-kernel-defined
+ //! @{
- //! @qualifier kernel-defined
//! Initialize the physical memory manager.
//!
//! This function initializes the kernel-wide physical memory manager. The function will invoke the handoff handler to
@@ -39,25 +34,21 @@ namespace kapi::memory
//! allocator to hand off to is passed to the handler.
auto init_pmm(std::size_t frame_count, void (&handoff_handler)(frame_allocator &)) -> void;
- //! @qualifier kernel-defined
//! Get the currently active frame allocator.
auto get_frame_allocator() -> frame_allocator &;
- //! @qualifier kernel-defined
//! Set the currently active frame allocator.
//!
//! @param allocator A new frame allocator.
//! @return The previously active frame allocator.
auto set_frame_allocator(frame_allocator & allocator) -> std::optional<frame_allocator *>;
- //! @qualifier kernel-defined
//! Set the currently active page mapper.
//!
//! @param mapper A new page mapper.
//! @return The previously active page mapper.
auto set_page_mapper(page_mapper & mapper) -> std::optional<page_mapper *>;
- //! @qualifier kernel-defined
//! Allocate a new frame of physical memory
//!
//! @warning This function will panic if no frame allocator has been registered.
@@ -65,7 +56,6 @@ namespace kapi::memory
//! @return An engaged std::optional iff. a frame could be allocated, std::nullopt otherwise.
auto allocate_frame() -> std::optional<frame>;
- //! @qualifier kernel-defined
//! Allocate multiple new frames of physical memory
//!
//! @warning This function will panic if no frame allocator has been registered.
@@ -73,7 +63,6 @@ namespace kapi::memory
//! @return An engaged std::optional iff. @p count frames could be allocated, std::nullopt otherwise.
auto allocate_many_frames(std::size_t count) -> std::optional<std::pair<frame, std::size_t>>;
- //! @qualifier kernel-defined
//! Map a page onto a frame.
//!
//! @warning This function will panic if no page mapper has been registered, or the page has already been mapped.
@@ -85,7 +74,6 @@ namespace kapi::memory
//! @return A pointer to the first byte of the mapped page.
auto map(page page, frame frame, page_mapper::flags flags = page_mapper::flags::empty) -> std::byte *;
- //! @qualifier kernel-defined
//! Unmap a page.
//!
//! @warning This function will panic if no page mapper has been registered, or the page is not mapped.
@@ -93,6 +81,51 @@ namespace kapi::memory
//! @param page The page to unmap
auto unmap(page page) -> void;
+ //! Initialize the Memory-mapped I/O region system.
+ //!
+ //! @param base The base address for the MMIO region.
+ //! @param page_count The number of pages the MMIO region is spans.
+ auto init_mmio(linear_address base, std::size_t page_count) -> void;
+
+ //! Allocate a Memory-mapped I/O region of the given size.
+ //!
+ //! @warning This function will panic if the MMIO system has not been initialized!
+ //! @param page_count The number of pages to allocate.
+ auto allocate_mmio_region(std::size_t page_count) -> mmio_region;
+
+ //! Map a region of Memory-mapped I/O address space to a given hardware address using the given flags.
+ //!
+ //! @warning This function will panic if no page mapper has been registered, or the page has already been mapped.
+ //! This function will not ensure that the frame is not already in use.
+ //!
+ //! This function will always set the @p uncached flag.
+ //!
+ //! @param region The region to map.
+ //! @param hw_base The base of the hardware region.
+ //! @param flags The flags to apply.
+ auto map_mmio_region(mmio_region region, physical_address hw_base, page_mapper::flags flags = {}) -> std::byte *;
+
+ //! Release a Memory-mapped I/O region.
+ //!
+ //! @warning This function will panic if the MMIO system has not been initialized!
+ //! @param region The region to release.
+ auto release_mmio_region(mmio_region region) -> void;
+
+ //! @}
+
+ //! @addtogroup kapi-memory-platform-defined
+ //! @{
+
+ //! Initialize the memory subsystem.
+ //!
+ //! @note This function must be implemented by the target platform.
+ //!
+ //! This function initializes the memory subsystem and activates the platform-specific frame allocator and page
+ //! mapper. When this function returns, a valid frame allocator and page mapper are expected to have been registered.
+ auto init() -> void;
+
+ //! @}
+
} // namespace kapi::memory
#endif
diff --git a/kapi/include/kapi/memory/address.hpp b/kapi/kapi/memory/address.hpp
index 3bef358..9231cfc 100644
--- a/kapi/include/kapi/memory/address.hpp
+++ b/kapi/kapi/memory/address.hpp
@@ -1,9 +1,10 @@
#ifndef TEACHOS_KAPI_MEMORY_ADDRESS_HPP
#define TEACHOS_KAPI_MEMORY_ADDRESS_HPP
-// IWYU pragma: private, include "kapi/memory.hpp"
+// IWYU pragma: private, include <kapi/memory.hpp>
#include <kstd/format>
+#include <kstd/units>
#include <bit>
#include <compare>
@@ -33,7 +34,7 @@ namespace kapi::memory
struct address
{
//! Construct a null-address.
- constexpr explicit address() noexcept = default;
+ constexpr address() noexcept = default;
//! Construct an address representing the given value.
//!
@@ -183,6 +184,16 @@ namespace kapi::memory
return static_cast<MaskType>(m_value & mask);
}
+ constexpr auto operator+(kstd::units::bytes n) const noexcept -> address
+ {
+ return address{m_value + n.value};
+ }
+
+ constexpr auto operator+=(kstd::units::bytes n) noexcept -> address &
+ {
+ return *this = *this + n;
+ }
+
//! Check if this address is equal to another one.
constexpr auto operator==(address const &) const noexcept -> bool = default;
@@ -218,13 +229,13 @@ namespace kstd
{
constexpr auto static suffix = Type == kapi::memory::address_type::linear ? "%lin" : "%phy";
- constexpr auto parse(std::string_view context) -> std::string_view
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
{
auto result = formatter<std::uintptr_t>::parse(context);
- if (!this->specs.type)
+ if (!this->specifiers.type)
{
- this->specs.type = 'p';
- this->specs.alternative_form = true;
+ this->specifiers.type = 'p';
+ this->specifiers.alternative_form = true;
}
return result;
}
diff --git a/kapi/include/kapi/memory/chunk.hpp b/kapi/kapi/memory/chunk.hpp
index 4529535..485a890 100644
--- a/kapi/include/kapi/memory/chunk.hpp
+++ b/kapi/kapi/memory/chunk.hpp
@@ -1,7 +1,9 @@
#ifndef TEACHOS_KAPI_MEMORY_CHUNK_HPP
#define TEACHOS_KAPI_MEMORY_CHUNK_HPP
-// IWYU pragma: private, include "kapi/memory.hpp"
+// IWYU pragma: private, include <kapi/memory.hpp>
+
+#include <kstd/units>
#include <compare>
#include <cstddef>
@@ -15,7 +17,7 @@ namespace kapi::memory
//! @tparam ChunkType The CRTP type of the deriving class
//! @tparam AddressType The type of addresses used to index this chunk
//! @tparam Size The size of this chunk.
- template<typename ChunkType, typename AddressType, std::size_t Size>
+ template<typename ChunkType, typename AddressType, kstd::units::bytes Size>
struct chunk
{
//! The type of addresses used to index this chunk
@@ -30,7 +32,7 @@ namespace kapi::memory
//! @return A handle to a chunk containing the given address.
constexpr auto static containing(address_type address) noexcept -> ChunkType
{
- return ChunkType{address / size};
+ return ChunkType{address / size.value};
}
//! Get the start address of the chunk referenced by this handle.
@@ -38,7 +40,7 @@ namespace kapi::memory
//! @return The address of the first byte contained by the chunk referenced by this handle.
[[nodiscard]] constexpr auto start_address() const noexcept -> address_type
{
- return address_type{m_number * size};
+ return address_type{(m_number * size).value};
}
//! Get the number of the chunk referenced by this handle.
diff --git a/kapi/include/kapi/memory/frame.hpp b/kapi/kapi/memory/frame.hpp
index a55b6ff..e423fa4 100644
--- a/kapi/include/kapi/memory/frame.hpp
+++ b/kapi/kapi/memory/frame.hpp
@@ -1,11 +1,11 @@
#ifndef TEACHOS_KAPI_MEMORY_FRAME_HPP
#define TEACHOS_KAPI_MEMORY_FRAME_HPP
-// IWYU pragma: private, include "kapi/memory.hpp"
+// IWYU pragma: private, include <kapi/memory.hpp>
-#include "kapi/memory/address.hpp"
-#include "kapi/memory/chunk.hpp"
-#include "kapi/memory/layout.hpp"
+#include <kapi/memory/address.hpp>
+#include <kapi/memory/chunk.hpp>
+#include <kapi/memory/layout.hpp>
#include <cstddef>
diff --git a/kapi/include/kapi/memory/frame_allocator.hpp b/kapi/kapi/memory/frame_allocator.hpp
index cfa8a1c..784ea93 100644
--- a/kapi/include/kapi/memory/frame_allocator.hpp
+++ b/kapi/kapi/memory/frame_allocator.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_KAPI_MEMORY_FRAME_ALLOCATOR_HPP
#define TEACHOS_KAPI_MEMORY_FRAME_ALLOCATOR_HPP
-// IWYU pragma: private, include "kapi/memory.hpp"
+// IWYU pragma: private, include <kapi/memory.hpp>
-#include "kapi/memory/frame.hpp"
+#include <kapi/memory/frame.hpp>
#include <cstddef>
#include <optional>
diff --git a/kapi/kapi/memory/layout.hpp b/kapi/kapi/memory/layout.hpp
new file mode 100644
index 0000000..733fa96
--- /dev/null
+++ b/kapi/kapi/memory/layout.hpp
@@ -0,0 +1,54 @@
+#ifndef TEACHOS_KAPI_MEMORY_LAYOUT_HPP
+#define TEACHOS_KAPI_MEMORY_LAYOUT_HPP
+
+// IWYU pragma: private, include <kapi/memory.hpp>
+
+#include <kapi/memory/address.hpp>
+
+#include <kstd/units>
+
+namespace kapi::memory
+{
+
+ //! The size of a single page of virtual memory.
+ //!
+ //! Platforms that use different sizes of pages are expected to emulate 4 KiB pages towards the kernel.
+ constexpr auto page_size = kstd::units::KiB(4);
+
+ //! The size of a single frame of physical memory.
+ //!
+ //! Platforms that use different sizes of frames are expected to emulate 4 KiB pages towards the kernel.
+ constexpr auto frame_size = kstd::units::KiB(4);
+
+ //! The linear base address of the higher-half direct map.
+ //!
+ //! Platforms are expected to provide a mapping of at least the first 512 GiB of available memory at this address.
+ constexpr auto higher_half_direct_map_base = linear_address{0xffff'8000'0000'0000uz};
+
+ //! The linear base address of the kernel heap.
+ constexpr auto heap_base = linear_address{0xffff'c000'0000'0000uz};
+
+ //! The linear base address of the memory region reserved for the metadata required by the PMM.
+ constexpr auto pmm_metadata_base = linear_address{0xffff'd000'0000'0000uz};
+
+ //! The linear base address of all Memory Mapped I/O mappings.
+ constexpr auto mmio_base = linear_address{0xffff'e000'0000'0000uz};
+
+ //! The linear base address of the loaded kernel image.
+ constexpr auto kernel_base = linear_address{0xffff'ffff'8000'0000uz};
+
+ //! Convert a physical address to a linear address using the higher-half direct map.
+ constexpr auto hhdm_to_linear(physical_address address) -> linear_address
+ {
+ return linear_address{address.raw() + higher_half_direct_map_base.raw()};
+ }
+
+ //! Convert a linear address in the higher-half direct map to a physical address.
+ constexpr auto hhdm_to_physical(linear_address address) -> physical_address
+ {
+ return physical_address{address.raw() - higher_half_direct_map_base.raw()};
+ }
+
+} // namespace kapi::memory
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/memory/page.hpp b/kapi/kapi/memory/page.hpp
index aa161ee..d987534 100644
--- a/kapi/include/kapi/memory/page.hpp
+++ b/kapi/kapi/memory/page.hpp
@@ -1,11 +1,11 @@
#ifndef TEACHOS_KAPI_MEMORY_PAGE_HPP
#define TEACHOS_KAPI_MEMORY_PAGE_HPP
-// IWYU pragma: private, include "kapi/memory.hpp"
+// IWYU pragma: private, include <kapi/memory.hpp>
-#include "kapi/memory/address.hpp"
-#include "kapi/memory/chunk.hpp"
-#include "kapi/memory/layout.hpp"
+#include <kapi/memory/address.hpp>
+#include <kapi/memory/chunk.hpp>
+#include <kapi/memory/layout.hpp>
#include <cstddef>
diff --git a/kapi/include/kapi/memory/page_mapper.hpp b/kapi/kapi/memory/page_mapper.hpp
index c6052e9..fb600b2 100644
--- a/kapi/include/kapi/memory/page_mapper.hpp
+++ b/kapi/kapi/memory/page_mapper.hpp
@@ -1,10 +1,10 @@
#ifndef TEACHOS_KAPI_MEMORY_PAGE_MAPPER_HPP
#define TEACHOS_KAPI_MEMORY_PAGE_MAPPER_HPP
-// IWYU pragma: private, include "kapi/memory.hpp"
+// IWYU pragma: private, include <kapi/memory.hpp>
-#include "kapi/memory/frame.hpp"
-#include "kapi/memory/page.hpp"
+#include <kapi/memory/frame.hpp>
+#include <kapi/memory/page.hpp>
#include <kstd/ext/bitfield_enum>
diff --git a/kapi/include/kapi/system.hpp b/kapi/kapi/system.hpp
index e5c43c5..8a20af9 100644
--- a/kapi/include/kapi/system.hpp
+++ b/kapi/kapi/system.hpp
@@ -7,7 +7,9 @@
namespace kapi::system
{
- //! @qualifier kernel-defined
+ //! @addtogroup kapi-system-kernel-defined
+ //! @{
+
//! Terminate kernel execution with the given error message.
//!
//! This function terminates the execution of the kernel and attempts to issue the given error message to the user.
@@ -15,10 +17,16 @@ namespace kapi::system
//! @param message The message associated with the panic
[[noreturn]] auto panic(std::string_view message, std::source_location = std::source_location::current()) -> void;
- //! @qualifier platform-defined
+ //! @} // end group kernel-defined
+
+ //! @addtogroup kapi-system-platform-defined
+ //! @{
+
//! A hook that runs once the memory subsystem has been initialized.
auto memory_initialized() -> void;
+ //! @} // end group platform-defined
+
} // namespace kapi::system
#endif
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index 02e517c..2388370 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -1,44 +1,229 @@
-add_executable("kernel"
- # Platform-independent KAPI implementation
+#[============================================================================[
+# Library
+#]============================================================================]
+
+add_library("kernel_lib" OBJECT)
+add_library("kernel::lib" ALIAS "kernel_lib")
+
+target_sources("kernel_lib" PRIVATE
+ # Kernel-defined KAPI Implementation
+ "kapi/acpi.cpp"
+ "kapi/boot_modules.cpp"
"kapi/cio.cpp"
+ "kapi/cpu.cpp"
+ "kapi/devices.cpp"
+ "kapi/devices/bus.cpp"
+ "kapi/devices/cpu.cpp"
+ "kapi/devices/device.cpp"
+ "kapi/filesystem.cpp"
+ "kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
- # KSTD OS Implementation
+ # Kernel-defined KSTD Implementation
"kstd/os.cpp"
"kstd/print.cpp"
- # Kernel Implementation
- "src/main.cpp"
+ # ACPI Subsystem
+ "src/acpi/manager.cpp"
+
+ # Memory Subsystem
"src/memory/bitmap_allocator.cpp"
"src/memory/block_list_allocator.cpp"
- "src/memory/operators.cpp"
+ "src/memory/mmio_allocator.cpp"
"src/memory.cpp"
+
+ # Device Subsystem
+ "src/devices/block_device.cpp"
+ "src/devices/block_device_utils.cpp"
+ "src/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"
+
+ # 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"
+
+ # DevFS Filesystem
+ "src/filesystem/devfs/filesystem.cpp"
+ "src/filesystem/devfs/inode.cpp"
+
+ # ext2 Filesystem
+ "src/filesystem/ext2/filesystem.cpp"
+ "src/filesystem/ext2/inode.cpp"
+
+ # Rootfs Filesystem
+ "src/filesystem/rootfs/filesystem.cpp"
+ "src/filesystem/rootfs/inode.cpp"
)
-target_include_directories("kernel" PRIVATE
- "include"
+file(GLOB_RECURSE KERNEL_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "include/**.hpp"
)
-target_link_libraries("kernel" PRIVATE
- "os::arch"
- "os::kapi"
+target_sources("kernel_lib" PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS "include"
+ FILES
+ ${KERNEL_HEADERS}
)
-target_link_options("kernel" PRIVATE
- "-T${KERNEL_LINKER_SCRIPT}"
- "-no-pie"
- "-nostdlib"
+target_include_directories("kernel_lib" PUBLIC
+ "include"
)
-set_property(TARGET "kernel"
- APPEND
- PROPERTY LINK_DEPENDS
- "${KERNEL_LINKER_SCRIPT}"
+target_link_libraries("kernel_lib" PUBLIC
+ "kapi::lib"
+ "kstd::lib"
+ "acpi::lib"
)
-target_disassemble("kernel")
-target_extract_debug_symbols("kernel")
-target_strip("kernel")
+set_target_properties("kernel_lib" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
+)
+
+#[============================================================================[
+# Executable
+#]============================================================================]
+
+if(NOT BUILD_TESTING)
+ add_executable("kernel")
+ add_executable(kernel::exe ALIAS "kernel")
+
+ target_sources("kernel" PRIVATE
+ "src/main.cpp"
+ "src/memory/operators.cpp"
+ )
+
+ target_link_libraries("kernel" PRIVATE
+ "kernel::lib"
+ "arch::lib"
+ )
+
+ target_link_options("kernel" PRIVATE
+ "-T${KERNEL_LINKER_SCRIPT}"
+ "-no-pie"
+ "-nostdlib"
+ )
+
+ set_property(TARGET "kernel"
+ APPEND
+ PROPERTY LINK_DEPENDS
+ "${KERNEL_LINKER_SCRIPT}"
+ )
+
+ target_disassemble("kernel")
+ target_extract_debug_symbols("kernel")
+ target_strip("kernel")
+
+ target_generate_bootable_iso("kernel")
+endif()
+
+#[============================================================================[
+# Tests
+#]============================================================================]
+
+if(BUILD_TESTING)
+ find_package("Catch2")
+ include("Catch")
+
+ enable_coverage("kernel_lib")
+
+ add_executable("kernel_tests")
+ add_executable("kernel::tests" ALIAS "kernel_tests")
+
+ 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"
+
+ # Device Subsystem Support
+ "src/test_support/devices/block_device.cpp"
+ "src/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"
+
+ # I/O Support
+ "src/test_support/log_buffer.cpp"
+ "src/test_support/output_device.cpp"
+
+ # Memory Support
+ "src/test_support/page_mapper.cpp"
+ "src/test_support/simulated_memory.cpp"
+
+ # Support System Listener
+ "src/test_support/state_reset_listener.cpp"
+
+ # KAPI Shim Tests
+ "kapi/cpu.tests.cpp"
+ "kapi/system.tests.cpp"
+ "kapi/filesystem.tests.cpp"
+
+ # KSTD Shim Tests
+ "kstd/print.tests.cpp"
+
+ # Memory Subsystem Tests
+ "src/memory/bitmap_allocator.tests.cpp"
+ "src/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"
+
+ # Storage Subsystem Tests
+ "src/devices/block_device_utils.tests.cpp"
+ "src/devices/block_device.tests.cpp"
+ "src/devices/storage/ram_disk/device.tests.cpp"
+ )
+
+ target_link_libraries("kernel_tests" PRIVATE
+ "kernel::lib"
+ "Catch2::Catch2WithMain"
+ )
+
+ target_compile_definitions("kernel_tests" PRIVATE
+ KERNEL_TEST_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src/test_support/filesystem/test_assets"
+ )
+
+ set_target_properties("kernel_tests" PROPERTIES
+ C_CLANG_TIDY ""
+ CXX_CLANG_TIDY ""
+ )
-target_generate_bootable_iso("kernel")
+ enable_coverage("kernel_tests")
+ catch_discover_tests("kernel_tests" ${CATCH_TEST_ARGS})
+endif()
diff --git a/kernel/include/kernel/acpi/manager.hpp b/kernel/include/kernel/acpi/manager.hpp
new file mode 100644
index 0000000..1e8c1e8
--- /dev/null
+++ b/kernel/include/kernel/acpi/manager.hpp
@@ -0,0 +1,32 @@
+#ifndef TEACHOS_KERNEL_ACPI_MANAGER_HPP
+#define TEACHOS_KERNEL_ACPI_MANAGER_HPP
+
+#include <acpi/acpi.hpp>
+
+#include <kstd/flat_map>
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <string_view>
+
+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<std::string_view, ::acpi::table_header const *> 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
new file mode 100644
index 0000000..a6d68ee
--- /dev/null
+++ b/kernel/include/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 <kapi/devices/device.hpp>
+
+#include <kstd/string>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..8be75b6
--- /dev/null
+++ b/kernel/include/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 <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+
+#include <cstddef>
+
+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<kapi::devices::device> 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<kapi::devices::device> 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
new file mode 100644
index 0000000..c8fee52
--- /dev/null
+++ b/kernel/include/kernel/devices/root_bus.hpp
@@ -0,0 +1,16 @@
+#ifndef TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+#define TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+
+#include <kapi/devices/bus.hpp>
+
+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
new file mode 100644
index 0000000..bea18f3
--- /dev/null
+++ b/kernel/include/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 <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <cstddef>
+
+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<kstd::shared_ptr<kapi::devices::device>> 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<kapi::devices::device>;
+
+ protected:
+ size_t m_major{};
+ size_t m_minors_per_device{};
+ kstd::vector<kstd::shared_ptr<kapi::devices::device>> 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
new file mode 100644
index 0000000..9a84087
--- /dev/null
+++ b/kernel/include/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 <kernel/devices/storage/controller.hpp>
+
+#include <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <cstddef>
+
+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<controller> const & controller) -> void;
+
+ /**
+ * @brief Return all registered storage controllers.
+ * @return Vector of all registered storage controllers.
+ */
+ [[nodiscard]] auto all_controllers() const -> kstd::vector<kstd::shared_ptr<controller>> 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<kapi::devices::device>;
+
+ /**
+ * @brief Determine the boot device.
+ * @return Boot device, or nullptr if it cannot be determined.
+ */
+ auto determine_boot_device() -> kstd::shared_ptr<kapi::devices::device>;
+
+ private:
+ /**
+ * @brief Private default constructor for storage management singleton.
+ */
+ management() = default;
+
+ kstd::vector<kstd::shared_ptr<controller>> 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
new file mode 100644
index 0000000..93cf30d
--- /dev/null
+++ b/kernel/include/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 <kernel/devices/storage/controller.hpp>
+
+#include <kapi/boot_module/boot_module_registry.hpp>
+
+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
new file mode 100644
index 0000000..89789ea
--- /dev/null
+++ b/kernel/include/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 <kernel/devices/block_device.hpp>
+
+#include <kapi/boot_module/boot_module.hpp>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..8388d05
--- /dev/null
+++ b/kernel/include/kernel/filesystem/constants.hpp
@@ -0,0 +1,14 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..478596a
--- /dev/null
+++ b/kernel/include/kernel/filesystem/dentry.hpp
@@ -0,0 +1,104 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_DENTRY_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_DENTRY_HPP
+
+#include <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstdint>
+#include <string_view>
+
+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<dentry> const & parent, kstd::shared_ptr<inode> 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<inode> const &;
+
+ /**
+ @brief Get the parent dentry.
+ @return A reference to the parent dentry.
+ */
+ [[nodiscard]] auto parent() const -> kstd::shared_ptr<dentry> const &;
+
+ /**
+ @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<dentry> 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<dentry>;
+
+ /**
+ @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::shared_ptr<dentry> m_parent;
+ kstd::vector<kstd::shared_ptr<dentry>> m_children;
+ kstd::shared_ptr<inode> 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
new file mode 100644
index 0000000..dbaa387
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/device_inode.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <string_view>
+
+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<kernel::filesystem::inode> 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<kernel::filesystem::inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<kernel::filesystem::inode> override;
+
+ private:
+ auto build_device_inode_table() -> void;
+
+ kstd::vector<kstd::shared_ptr<device_inode>> 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
new file mode 100644
index 0000000..e428891
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/inode.hpp>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..f4aa2d1
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/inode.hpp>
+
+#include <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+
+#include <cstddef>
+
+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<kapi::devices::device> 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<kapi::devices::device> 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<kapi::devices::device> 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
new file mode 100644
index 0000000..7fbba3f
--- /dev/null
+++ b/kernel/include/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 <array>
+#include <cstdint>
+
+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<uint8_t, 2> padding;
+ std::array<uint8_t, 12> 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
new file mode 100644
index 0000000..2284d7b
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/ext2/block_group_descriptor.hpp>
+#include <kernel/filesystem/ext2/inode.hpp>
+#include <kernel/filesystem/ext2/superblock.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/unikstd.h>
+#include <kstd/vector>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+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<kernel::filesystem::inode> 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<kernel::filesystem::inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<kernel::filesystem::inode> 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<indirect_level, 3>;
+
+ [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::shared_ptr<kernel::filesystem::ext2::inode>;
+ [[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<block_group_descriptor> 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
new file mode 100644
index 0000000..f2496f0
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+
+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<uint32_t, 15> block; // NOLINT(readability-magic-numbers)
+ uint32_t generation;
+ uint32_t file_acl;
+ uint32_t dir_acl;
+ uint32_t faddr;
+ std::array<uint8_t, 12> 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
new file mode 100644
index 0000000..4097cbb
--- /dev/null
+++ b/kernel/include/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 <array>
+#include <cstdint>
+
+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<char, 255> 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
new file mode 100644
index 0000000..41ad935
--- /dev/null
+++ b/kernel/include/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 <array>
+#include <cstdint>
+
+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<uint8_t, 16> uuid;
+ std::array<uint8_t, 16> volume_name;
+ std::array<uint8_t, 64> 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<uint8_t, 16> journal_uuid;
+ uint32_t journal_inum;
+ uint32_t journal_dev;
+ uint32_t last_orphan;
+
+ // Directory Indexing Support
+ std::array<uint32_t, 4> hash_seed;
+ uint8_t def_hash_version;
+ std::array<uint8_t, 3> padding2;
+
+ // Other options
+ uint32_t default_mount_options;
+ uint32_t first_meta_bg;
+ std::array<uint8_t, 760> 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
new file mode 100644
index 0000000..bec1b16
--- /dev/null
+++ b/kernel/include/kernel/filesystem/filesystem.hpp
@@ -0,0 +1,79 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP
+
+#include <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <string_view>
+
+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<inode> const & backing_inode) -> kstd::shared_ptr<filesystem>;
+
+ /**
+ @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<inode> 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<inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<inode> = 0;
+
+ /**
+ @brief Returns a reference to the root inode of the filesystem.
+ */
+ [[nodiscard]] auto root_inode() const -> kstd::shared_ptr<inode> const &;
+
+ /**
+ @brief Returns a reference to the backing inode of the filesystem.
+ */
+ [[nodiscard]] auto backing_inode() const -> kstd::shared_ptr<inode> const &;
+
+ protected:
+ kstd::shared_ptr<inode> m_root_inode{};
+ kstd::shared_ptr<inode> 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
new file mode 100644
index 0000000..b34b921
--- /dev/null
+++ b/kernel/include/kernel/filesystem/inode.hpp
@@ -0,0 +1,69 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_INODE_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_INODE_HPP
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..ced4f81
--- /dev/null
+++ b/kernel/include/kernel/filesystem/mount.hpp
@@ -0,0 +1,97 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_MOUNT_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_MOUNT_HPP
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+
+#include <atomic>
+#include <cstddef>
+
+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<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry,
+ kstd::shared_ptr<filesystem> const & fs, kstd::shared_ptr<mount> const & parent_mount,
+ kstd::shared_ptr<mount> const & source_mount);
+
+ /**
+ @brief Get the dentry where the filesystem is mounted.
+ */
+ [[nodiscard]] auto mount_dentry() const -> kstd::shared_ptr<dentry> const &;
+
+ /**
+ @brief Get the root dentry of the mounted filesystem.
+ */
+ [[nodiscard]] auto root_dentry() const -> kstd::shared_ptr<dentry> const &;
+
+ /**
+ @brief Get the filesystem instance being mounted.
+ */
+ [[nodiscard]] auto get_filesystem() const -> kstd::shared_ptr<filesystem> 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<mount> const &;
+
+ /**
+ @brief Get the source mount where this mount originates from.
+ */
+ [[nodiscard]] auto source_mount() const -> kstd::shared_ptr<mount>;
+
+ /**
+ @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<dentry> m_mount_dentry{};
+ kstd::shared_ptr<dentry> m_root_dentry{};
+ kstd::shared_ptr<filesystem> m_filesystem{};
+ kstd::shared_ptr<mount> m_parent_mount{};
+ kstd::weak_ptr<mount> 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
new file mode 100644
index 0000000..4f2d1b7
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/mount.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <string_view>
+
+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<mount> 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<mount>;
+
+ private:
+ [[nodiscard]] auto has_child_mounts(kstd::shared_ptr<mount> const & parent_mount) const -> bool;
+ [[nodiscard]] auto find_mount_iterator(std::string_view path) const
+ -> kstd::vector<kstd::shared_ptr<mount>>::const_iterator;
+
+ kstd::vector<kstd::shared_ptr<mount>> 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
new file mode 100644
index 0000000..fd10e64
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/dentry.hpp>
+
+#include <kstd/memory>
+
+#include <cstddef>
+
+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<dentry> 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<dentry> const &;
+
+ private:
+ kstd::shared_ptr<dentry> 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
new file mode 100644
index 0000000..7e754ac
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/open_file_descriptor.hpp>
+
+#include <kstd/memory>
+#include <kstd/unikstd.h>
+#include <kstd/vector>
+
+#include <cstddef>
+
+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<open_file_descriptor> 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<open_file_descriptor>;
+
+ /**
+ @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<kstd::shared_ptr<open_file_descriptor>> 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
new file mode 100644
index 0000000..4845bf1
--- /dev/null
+++ b/kernel/include/kernel/filesystem/path.hpp
@@ -0,0 +1,71 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP
+
+#include <kernel/filesystem/constants.hpp>
+
+#include <kstd/string>
+
+#include <ranges>
+#include <string_view>
+
+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
new file mode 100644
index 0000000..3c2dcb1
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <string_view>
+
+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<kernel::filesystem::inode> 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<kernel::filesystem::inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<kernel::filesystem::inode> 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
new file mode 100644
index 0000000..0f21eaa
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..0948e54
--- /dev/null
+++ b/kernel/include/kernel/filesystem/type.hpp
@@ -0,0 +1,42 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_TYPE_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_TYPE_HPP
+
+#include <kernel/filesystem/filesystem.hpp>
+
+#include <kstd/memory>
+
+#include <string_view>
+
+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<filesystem> = 0;
+ };
+
+ template<typename Type>
+ struct type_registration
+ {
+ constexpr auto static instance = Type{};
+ [[using gnu: section("fs_types"), used, visibility("hidden")]] constexpr auto static pointer{
+ kstd::make_observer<type const>(&instance),
+ };
+ };
+
+} // namespace kernel::filesystem
+
+#endif
diff --git a/kernel/include/kernel/filesystem/type_registry.hpp b/kernel/include/kernel/filesystem/type_registry.hpp
new file mode 100644
index 0000000..3be7295
--- /dev/null
+++ b/kernel/include/kernel/filesystem/type_registry.hpp
@@ -0,0 +1,53 @@
+#ifndef TEACH_OS_KERNEL_TYPE_REGISRY_HPP
+#define TEACH_OS_KERNEL_TYPE_REGISRY_HPP
+
+#include <kernel/filesystem/type.hpp>
+
+#include <kstd/flat_map>
+#include <kstd/memory>
+#include <kstd/string>
+
+#include <cstddef>
+#include <span>
+#include <string_view>
+
+namespace kernel::filesystem
+{
+
+ struct type_registry
+ {
+ using value_type = type;
+ using pointer = kstd::observer_ptr<value_type const>;
+
+ 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<pointer const>;
+
+ //! 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<std::string_view, pointer> m_descriptors{};
+ };
+
+} // namespace kernel::filesystem
+
+#endif
diff --git a/kernel/include/kernel/filesystem/vfs.hpp b/kernel/include/kernel/filesystem/vfs.hpp
new file mode 100644
index 0000000..ddc9a9b
--- /dev/null
+++ b/kernel/include/kernel/filesystem/vfs.hpp
@@ -0,0 +1,113 @@
+#ifndef TEACH_OS_KERNEL_FILESYSTEM_VFS_HPP
+#define TEACH_OS_KERNEL_FILESYSTEM_VFS_HPP
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/devfs/filesystem.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/mount.hpp>
+#include <kernel/filesystem/mount_table.hpp>
+
+#include <kstd/memory>
+
+#include <string_view>
+#include <utility>
+
+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<dentry>;
+
+ /**
+ @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<dentry>, kstd::shared_ptr<mount>>;
+ [[nodiscard]] auto resolve_path(std::string_view path) const -> kstd::shared_ptr<dentry>;
+ [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::shared_ptr<mount>;
+
+ auto do_mount_internal(kstd::shared_ptr<dentry> const & mount_point_dentry,
+ kstd::shared_ptr<mount> const & parent_mount, kstd::shared_ptr<filesystem> const & fs,
+ kstd::shared_ptr<mount> const & source_mount = nullptr) -> void;
+
+ auto graft_persistent_device_fs(kstd::shared_ptr<devfs::filesystem> 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
index f09c519..17fb82b 100644
--- a/kernel/include/kernel/memory.hpp
+++ b/kernel/include/kernel/memory.hpp
@@ -1,9 +1,9 @@
#ifndef TEACHOS_KERNEL_MEMORY_HPP
#define TEACHOS_KERNEL_MEMORY_HPP
-#include "kapi/memory.hpp"
+#include <kernel/memory/heap_allocator.hpp> // IWYU pragma: export
-#include "kernel/memory/heap_allocator.hpp" // IWYU pragma: export
+#include <kapi/memory.hpp>
namespace kernel::memory
{
diff --git a/kernel/include/kernel/memory/bitmap_allocator.hpp b/kernel/include/kernel/memory/bitmap_allocator.hpp
index fb5bf55..370ce64 100644
--- a/kernel/include/kernel/memory/bitmap_allocator.hpp
+++ b/kernel/include/kernel/memory/bitmap_allocator.hpp
@@ -1,7 +1,7 @@
#ifndef TEACHOS_KERNEL_MEMORY_BITMAP_ALLOCATOR_HPP
#define TEACHOS_KERNEL_MEMORY_BITMAP_ALLOCATOR_HPP
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <cstddef>
#include <cstdint>
diff --git a/kernel/include/kernel/memory/block_list_allocator.hpp b/kernel/include/kernel/memory/block_list_allocator.hpp
index 5e81c44..51b226e 100644
--- a/kernel/include/kernel/memory/block_list_allocator.hpp
+++ b/kernel/include/kernel/memory/block_list_allocator.hpp
@@ -1,11 +1,12 @@
#ifndef TEACHOS_KERNEL_MEMORY_BLOCK_LIST_ALLOCATOR_HPP
#define TEACHOS_KERNEL_MEMORY_BLOCK_LIST_ALLOCATOR_HPP
-#include "kapi/memory.hpp"
+#include <kernel/memory/heap_allocator.hpp>
-#include "kernel/memory/heap_allocator.hpp"
+#include <kapi/memory.hpp>
#include <kstd/mutex>
+#include <kstd/units>
#include <cstddef>
#include <new>
@@ -32,7 +33,7 @@ namespace kernel::memory
//! @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(std::size_t size, std::align_val_t alignment) noexcept -> void * override;
+ [[nodiscard]] auto allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * override;
//! Deallocate a block of memory previously allocated.
//!
@@ -42,26 +43,26 @@ namespace kernel::memory
private:
struct block_header final
{
- std::size_t usable_size;
- bool free;
- block_header * next;
- block_header * prev;
+ 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 = sizeof(block_header) + sizeof(block_header *);
+ 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 = 16uz;
+ 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(std::size_t delta) noexcept -> bool;
+ auto expand(kstd::units::bytes delta) noexcept -> bool;
//! Split a given free block to accommodate and allocation.
//!
@@ -70,7 +71,7 @@ namespace kernel::memory
//! @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, std::size_t size, std::size_t padding) noexcept -> void;
+ 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.
//!
@@ -86,4 +87,4 @@ namespace kernel::memory
} // namespace kernel::memory
-#endif \ No newline at end of file
+#endif
diff --git a/kernel/include/kernel/memory/heap_allocator.hpp b/kernel/include/kernel/memory/heap_allocator.hpp
index bc771e3..fd39bef 100644
--- a/kernel/include/kernel/memory/heap_allocator.hpp
+++ b/kernel/include/kernel/memory/heap_allocator.hpp
@@ -1,10 +1,7 @@
#ifndef TEACHOS_KERNEL_MEMORY_HEAP_ALLOCATOR_HPP
#define TEACHOS_KERNEL_MEMORY_HEAP_ALLOCATOR_HPP
-#include <kstd/mutex>
-
-#include <cstddef>
-#include <new>
+#include <kstd/units>
namespace kernel::memory
{
@@ -19,7 +16,7 @@ namespace kernel::memory
//! @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(std::size_t size, std::align_val_t alignment) noexcept -> void * = 0;
+ [[nodiscard]] virtual auto allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * = 0;
//! Deallocate a block of memory previously allocated.
//!
diff --git a/kernel/include/kernel/memory/mmio_allocator.hpp b/kernel/include/kernel/memory/mmio_allocator.hpp
new file mode 100644
index 0000000..c7a8ed0
--- /dev/null
+++ b/kernel/include/kernel/memory/mmio_allocator.hpp
@@ -0,0 +1,41 @@
+#ifndef TEACHOS_KERNEL_MEMORY_MMIO_ALLOCATOR_HPP
+#define TEACHOS_KERNEL_MEMORY_MMIO_ALLOCATOR_HPP
+
+#include <kapi/memory.hpp>
+
+#include <kstd/allocator>
+#include <kstd/memory>
+
+#include <cstddef>
+
+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<node> 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
new file mode 100644
index 0000000..2343a10
--- /dev/null
+++ b/kernel/include/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/include/kernel/test_support/bump_frame_allocator.hpp b/kernel/include/kernel/test_support/bump_frame_allocator.hpp
new file mode 100644
index 0000000..a8ffd48
--- /dev/null
+++ b/kernel/include/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 <kapi/memory.hpp>
+
+#include <cstddef>
+#include <optional>
+#include <utility>
+
+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<std::pair<kapi::memory::frame, std::size_t>> 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<kapi::memory::frame, std::size_t>) -> 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
new file mode 100644
index 0000000..afe27e0
--- /dev/null
+++ b/kernel/include/kernel/test_support/cio.hpp
@@ -0,0 +1,39 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_CIO_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_CIO_HPP
+
+#include <kernel/test_support/log_buffer.hpp>
+
+#include <kapi/cio.hpp>
+
+#include <string_view>
+
+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
new file mode 100644
index 0000000..c99d1a7
--- /dev/null
+++ b/kernel/include/kernel/test_support/cpu.hpp
@@ -0,0 +1,23 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_CPU_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_CPU_HPP
+
+#include <stdexcept>
+
+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
new file mode 100644
index 0000000..89a2bf1
--- /dev/null
+++ b/kernel/include/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 <kernel/devices/block_device.hpp>
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <cstdint>
+
+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<uint8_t> 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
new file mode 100644
index 0000000..aba183a
--- /dev/null
+++ b/kernel/include/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 <kapi/devices/device.hpp>
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..581aa91
--- /dev/null
+++ b/kernel/include/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/include/kernel/test_support/filesystem/ext2.hpp b/kernel/include/kernel/test_support/filesystem/ext2.hpp
new file mode 100644
index 0000000..18cef1c
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/ext2/superblock.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+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
new file mode 100644
index 0000000..5f26022
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+
+#include <kstd/memory>
+
+#include <string_view>
+
+namespace kernel::tests::filesystem
+{
+ struct filesystem : kernel::filesystem::filesystem
+ {
+ filesystem() = default;
+
+ [[nodiscard]] auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<kernel::filesystem::inode> 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
new file mode 100644
index 0000000..8a76437
--- /dev/null
+++ b/kernel/include/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 <kernel/filesystem/inode.hpp>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..46b0334
--- /dev/null
+++ b/kernel/include/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/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp
new file mode 100644
index 0000000..94a6668
--- /dev/null
+++ b/kernel/include/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 <kapi/boot_module/boot_module_registry.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <string>
+#include <vector>
+
+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<std::string> const & module_names,
+ std::vector<std::filesystem::path> 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<std::string> m_module_names{};
+ std::vector<std::vector<std::byte>> m_module_data{};
+ std::vector<mapped_image> 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
new file mode 100644
index 0000000..55f4b29
--- /dev/null
+++ b/kernel/include/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 <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <string>
+#include <vector>
+
+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<std::string> const & module_names,
+ std::vector<std::filesystem::path> 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
new file mode 100644
index 0000000..739e353
--- /dev/null
+++ b/kernel/include/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/include/kernel/test_support/log_buffer.hpp b/kernel/include/kernel/test_support/log_buffer.hpp
new file mode 100644
index 0000000..41d9a76
--- /dev/null
+++ b/kernel/include/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 <string>
+#include <vector>
+
+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<std::string> const &;
+
+ private:
+ std::vector<std::string> 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
new file mode 100644
index 0000000..21030a4
--- /dev/null
+++ b/kernel/include/kernel/test_support/memory.hpp
@@ -0,0 +1,22 @@
+#ifndef TEACHOS_KERNEL_TEST_SUPPORT_MEMORY_HPP
+#define TEACHOS_KERNEL_TEST_SUPPORT_MEMORY_HPP
+
+#include <kapi/memory.hpp>
+
+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
new file mode 100644
index 0000000..be4403b
--- /dev/null
+++ b/kernel/include/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 <kernel/test_support/simulated_memory.hpp>
+
+#include <kapi/memory.hpp>
+
+#include <kstd/units>
+
+#include <cstddef>
+#include <cstdint>
+#include <unordered_map>
+
+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<std::uint64_t, kapi::memory::frame> 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
new file mode 100644
index 0000000..a201c3d
--- /dev/null
+++ b/kernel/include/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 <kapi/memory.hpp>
+
+#include <kstd/units>
+
+#include <cstddef>
+
+#include <sys/types.h>
+
+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/kapi/acpi.cpp b/kernel/kapi/acpi.cpp
new file mode 100644
index 0000000..b6d5cdf
--- /dev/null
+++ b/kernel/kapi/acpi.cpp
@@ -0,0 +1,40 @@
+#include <kapi/acpi.hpp>
+
+#include <kernel/acpi/manager.hpp>
+
+#include <kapi/system.hpp>
+
+#include <acpi/acpi.hpp>
+
+#include <kstd/memory>
+
+#include <atomic>
+#include <optional>
+#include <string_view>
+
+namespace kapi::acpi
+{
+
+ namespace
+ {
+ auto constinit manager = std::optional<kernel::acpi::manager>{};
+ } // namespace
+
+ auto init(::acpi::rsdp const & sdp) -> bool
+ {
+ auto static constinit initialized = std::atomic_flag{};
+ if (initialized.test_and_set())
+ {
+ system::panic("[OS:ACPI] The ACPI manager has already been initialized!");
+ }
+
+ manager.emplace(sdp);
+ return manager->load_tables();
+ }
+
+ auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>
+ {
+ return manager->get_table(signature);
+ }
+
+}; // namespace kapi::acpi
diff --git a/kernel/kapi/boot_modules.cpp b/kernel/kapi/boot_modules.cpp
new file mode 100644
index 0000000..5629b77
--- /dev/null
+++ b/kernel/kapi/boot_modules.cpp
@@ -0,0 +1,41 @@
+#include <kapi/boot_modules.hpp>
+
+#include <kapi/system.hpp>
+
+#include <optional>
+
+namespace
+{
+ constinit auto static registry = std::optional<kapi::boot_modules::boot_module_registry>{};
+} // namespace
+
+namespace kapi::boot_modules
+{
+ auto set_boot_module_registry(boot_module_registry & new_registry) -> void
+ {
+ if (registry)
+ {
+ system::panic("[x86_64] Boot module registry has already been set.");
+ }
+
+ registry = new_registry;
+ }
+
+ auto get_boot_module_registry() -> boot_module_registry const &
+ {
+ if (!registry)
+ {
+ system::panic("[x86_64] Boot module registry has not been initialized.");
+ }
+
+ return *registry;
+ }
+} // namespace kapi::boot_modules
+
+namespace kernel::tests::boot_modules
+{
+ auto deinit() -> void
+ {
+ registry.reset();
+ }
+} // namespace kernel::tests::boot_modules
diff --git a/kernel/kapi/cio.cpp b/kernel/kapi/cio.cpp
index d447a6a..96f043c 100644
--- a/kernel/kapi/cio.cpp
+++ b/kernel/kapi/cio.cpp
@@ -1,4 +1,4 @@
-#include "kapi/cio.hpp"
+#include <kapi/cio.hpp>
#include <optional>
#include <string_view>
diff --git a/kernel/kapi/cpu.cpp b/kernel/kapi/cpu.cpp
new file mode 100644
index 0000000..7b1a43b
--- /dev/null
+++ b/kernel/kapi/cpu.cpp
@@ -0,0 +1,35 @@
+#include <kapi/cpu.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/print>
+
+namespace kapi::cpu
+{
+
+ namespace
+ {
+ auto handle_page_fault(kapi::cpu::exception const & context) -> bool
+ {
+ kstd::println(kstd::print_sink::stderr, "\tFault address: {:#018x}", context.access_address);
+ kstd::println(kstd::print_sink::stderr, "\tPresent: {}", context.is_present);
+ kstd::println(kstd::print_sink::stderr, "\tWrite: {}", context.is_write_access);
+ kstd::println(kstd::print_sink::stderr, "\tUser: {}", context.is_user_mode);
+
+ kapi::system::panic("Halting the system due to an unrecoverable page fault.");
+ }
+ } // namespace
+
+ auto dispatch(exception const & context) -> bool
+ {
+ kstd::println(kstd::print_sink::stderr, "[OS:CPU] {} @ {:#018x}", context.type, context.instruction_pointer);
+ switch (context.type)
+ {
+ case kapi::cpu::exception::type::page_fault:
+ return handle_page_fault(context);
+ default:
+ return false;
+ }
+ }
+
+} // namespace kapi::cpu \ No newline at end of file
diff --git a/kernel/kapi/cpu.tests.cpp b/kernel/kapi/cpu.tests.cpp
new file mode 100644
index 0000000..9ce2917
--- /dev/null
+++ b/kernel/kapi/cpu.tests.cpp
@@ -0,0 +1,19 @@
+#include <kapi/cpu.hpp>
+
+#include <kernel/test_support/cpu.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Kernel testing kapi::cpu shims", "[support]")
+{
+ GIVEN("the test support infrastructure is initialized")
+ {
+ WHEN("a CPU halt is requested")
+ {
+ THEN("the correct exception is thrown")
+ {
+ REQUIRE_THROWS_AS(kapi::cpu::halt(), kernel::tests::cpu::halt);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kernel/kapi/devices.cpp b/kernel/kapi/devices.cpp
new file mode 100644
index 0000000..572227e
--- /dev/null
+++ b/kernel/kapi/devices.cpp
@@ -0,0 +1,86 @@
+#include <kapi/devices.hpp>
+
+#include <kernel/devices/root_bus.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/flat_map>
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <atomic>
+#include <cstddef>
+#include <optional>
+#include <string_view>
+#include <utility>
+
+namespace kapi::devices
+{
+ namespace
+ {
+ auto constinit next_major_number = std::atomic_size_t{1};
+ auto constinit root_bus = std::optional<kernel::devices::root_bus>{};
+ auto constinit device_tree = kstd::flat_map<std::pair<std::size_t, std::size_t>, kstd::observer_ptr<device>>{};
+ } // namespace
+
+ auto init() -> void
+ {
+ auto static is_initialized = std::atomic_flag{};
+ if (is_initialized.test_and_set())
+ {
+ return;
+ }
+
+ auto & bus = root_bus.emplace();
+ register_device(bus);
+ bus.init();
+ }
+
+ auto get_root_bus() -> bus &
+ {
+ if (!root_bus.has_value())
+ {
+ kapi::system::panic("[OS:DEV] Root bus not initialized!");
+ }
+ return *root_bus;
+ }
+
+ auto allocate_major_number() -> std::size_t
+ {
+ return next_major_number++;
+ }
+
+ auto register_device(device & device) -> bool
+ {
+ kstd::println("[OS:DEV] Registering device {}@{}:{}", device.name(), device.major(), device.minor());
+ return device_tree.emplace(std::pair{device.major(), device.minor()}, &device).second;
+ }
+
+ auto unregister_device(device &) -> bool
+ {
+ kstd::println("[OS:DEV] TODO: implement device deregistration");
+ return false;
+ }
+
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>
+ {
+ if (device_tree.contains(std::pair{major, minor}))
+ {
+ return device_tree.at(std::pair{major, minor});
+ }
+ return nullptr;
+ }
+
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>
+ {
+ for (auto const & [key, value] : device_tree)
+ {
+ if (value->name() == name)
+ {
+ return value;
+ }
+ }
+ return nullptr;
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/kapi/devices/bus.cpp b/kernel/kapi/devices/bus.cpp
new file mode 100644
index 0000000..59753f7
--- /dev/null
+++ b/kernel/kapi/devices/bus.cpp
@@ -0,0 +1,72 @@
+#include <kapi/devices/bus.hpp>
+
+#include <kapi/devices.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstddef>
+#include <utility>
+
+namespace kapi::devices
+{
+ bus::bus(std::size_t major, std::size_t minor, kstd::string const & name)
+ : device(major, minor, name)
+ {}
+
+ auto bus::init() -> bool
+ {
+ if (m_init_was_called.test_and_set())
+ {
+ kstd::println(kstd::print_sink::stderr, "[OS:DEV] Bus {}:{}:{} already initialized", name(), major(), minor());
+ return true;
+ }
+
+ if (!probe())
+ {
+ kstd::println(kstd::print_sink::stderr, "[OS:DEV] Bus {}:{}:{} probe failed", name(), major(), minor());
+ return false;
+ }
+
+ auto child_status = std::ranges::fold_left(m_devices, true, [&](bool acc, auto & child) -> bool {
+ kstd::println("[OS:DEV] Initializing child device {}@{}", child->name(), name());
+ return child->init() && acc;
+ });
+
+ m_initialized.test_and_set();
+
+ return child_status;
+ }
+
+ auto bus::add_child(kstd::unique_ptr<device> child) -> void
+ {
+ auto observer = m_observers.emplace_back(child.get());
+ child->set_parent(kstd::make_observer(this));
+ m_devices.push_back(std::move(child));
+ kapi::devices::register_device(*observer);
+
+ if (m_initialized.test())
+ {
+ kstd::println("[OS:DEV] Initializing child device {}@{}", observer->name(), name());
+ if (!observer->init())
+ {
+ kapi::system::panic("[OS:DEV] Failed to initialize child device");
+ }
+ }
+ }
+
+ [[nodiscard]] auto bus::children() const -> kstd::vector<kstd::observer_ptr<device>> const &
+ {
+ return m_observers;
+ }
+
+ auto bus::probe() -> bool
+ {
+ return true;
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/kapi/devices/cpu.cpp b/kernel/kapi/devices/cpu.cpp
new file mode 100644
index 0000000..f0e1d72
--- /dev/null
+++ b/kernel/kapi/devices/cpu.cpp
@@ -0,0 +1,31 @@
+#include <kapi/devices/cpu.hpp>
+
+#include <kapi/devices.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace kapi::devices
+{
+
+ cpu::core::core(std::size_t major_number, std::size_t minor_number, std::uint64_t hardware_id, bool is_bsp)
+ : kapi::devices::bus{major_number, minor_number, "cpu_core"}
+ , m_hardware_id{hardware_id}
+ , m_is_bsp{is_bsp}
+ {}
+
+ auto cpu::core::hardware_id() const -> std::uint64_t
+ {
+ return m_hardware_id;
+ }
+
+ auto cpu::core::is_bsp() const -> bool
+ {
+ return m_is_bsp;
+ }
+
+ cpu::cpu(std::size_t major, std::size_t minor)
+ : kapi::devices::bus{major, minor, "cpu"}
+ {}
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/kapi/devices/device.cpp b/kernel/kapi/devices/device.cpp
new file mode 100644
index 0000000..8b5d6b9
--- /dev/null
+++ b/kernel/kapi/devices/device.cpp
@@ -0,0 +1,43 @@
+#include <kapi/devices/device.hpp>
+
+#include <kapi/devices/bus.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+
+#include <cstddef>
+
+namespace kapi::devices
+{
+ device::device(size_t major, size_t minor, kstd::string const & name)
+ : m_major(major)
+ , m_minor(minor)
+ , m_name(name)
+ {}
+
+ [[nodiscard]] auto device::major() const -> size_t
+ {
+ return m_major;
+ }
+
+ [[nodiscard]] auto device::minor() const -> size_t
+ {
+ return m_minor;
+ }
+
+ [[nodiscard]] auto device::name() const -> kstd::string const &
+ {
+ return m_name;
+ }
+
+ [[nodiscard]] auto device::is_block_device() const -> bool
+ {
+ return false;
+ }
+
+ auto device::set_parent(kstd::observer_ptr<bus> parent) -> void
+ {
+ m_parent = parent;
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/kapi/filesystem.cpp b/kernel/kapi/filesystem.cpp
new file mode 100644
index 0000000..68b51c9
--- /dev/null
+++ b/kernel/kapi/filesystem.cpp
@@ -0,0 +1,76 @@
+#include <kapi/filesystem.hpp>
+
+#include <kernel/filesystem/open_file_descriptor.hpp>
+#include <kernel/filesystem/open_file_table.hpp>
+#include <kernel/filesystem/vfs.hpp>
+
+#include <kstd/memory>
+#include <kstd/unikstd.h>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::filesystem
+{
+ auto mount(std::string_view source, std::string_view target) -> kstd::ssize_t
+ {
+ if (kernel::filesystem::vfs::get().do_mount(source, target) == kernel::filesystem::vfs::operation_result::success)
+ {
+ return 0;
+ }
+ return -1;
+ }
+
+ auto umount(std::string_view target) -> kstd::ssize_t
+ {
+ if (kernel::filesystem::vfs::get().unmount(target) == kernel::filesystem::vfs::operation_result::success)
+ {
+ return 0;
+ }
+ return -1;
+ }
+
+ auto open(std::string_view path) -> kstd::ssize_t
+ {
+ if (auto dentry = kernel::filesystem::vfs::get().open(path))
+ {
+ auto open_file_descriptor = kstd::make_shared<kernel::filesystem::open_file_descriptor>(dentry);
+ return kernel::filesystem::open_file_table::get().add_file(open_file_descriptor);
+ }
+
+ return -1;
+ }
+
+ auto close(size_t file_descriptor) -> kstd::ssize_t
+ {
+ if (auto open_file_descriptor = kernel::filesystem::open_file_table::get().file(file_descriptor))
+ {
+ if (kernel::filesystem::vfs::get().close(open_file_descriptor->get_dentry()->absolute_path()) ==
+ kernel::filesystem::vfs::operation_result::success)
+ {
+ return kernel::filesystem::open_file_table::get().remove_file(file_descriptor);
+ }
+ }
+ return -1;
+ }
+
+ auto read(size_t file_descriptor, void * buffer, size_t size) -> kstd::ssize_t
+ {
+ if (auto open_file_descriptor = kernel::filesystem::open_file_table::get().file(file_descriptor))
+ {
+ return open_file_descriptor->read(buffer, size);
+ }
+
+ return -1;
+ }
+
+ auto write(size_t file_descriptor, void const * buffer, size_t size) -> kstd::ssize_t
+ {
+ if (auto open_file_descriptor = kernel::filesystem::open_file_table::get().file(file_descriptor))
+ {
+ return open_file_descriptor->write(buffer, size);
+ }
+
+ return -1;
+ }
+} // namespace kapi::filesystem \ No newline at end of file
diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp
new file mode 100644
index 0000000..d241afa
--- /dev/null
+++ b/kernel/kapi/filesystem.tests.cpp
@@ -0,0 +1,199 @@
+#include <kapi/filesystem.hpp>
+
+#include <kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <cstddef>
+#include <filesystem>
+#include <string_view>
+#include <vector>
+
+SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kapi filesystem with real images",
+ "[kapi][filesystem]")
+{
+ 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";
+
+ GIVEN("Two real image files")
+ {
+ 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}));
+
+ THEN("files can be opened, read and closed again")
+ {
+ auto fd = kapi::filesystem::open("/information/info_1.txt");
+ REQUIRE(fd >= 0);
+
+ auto buffer = std::vector<std::byte>(6);
+ auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ REQUIRE(buffer_as_str == "info_1");
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ }
+
+ THEN("files can be opened through absolute symbolic link, read and closed again")
+ {
+ auto fd = kapi::filesystem::open("/symlinks/information_directory_absolute/info_1.txt");
+ REQUIRE(fd >= 0);
+
+ auto buffer = std::vector<std::byte>(6);
+ auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ REQUIRE(buffer_as_str == "info_1");
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ }
+
+ THEN("files can be opened through relative symbolic link, read and closed again")
+ {
+ auto fd = kapi::filesystem::open("/symlinks/information_directory_relative/info_1.txt");
+ REQUIRE(fd >= 0);
+
+ auto buffer = std::vector<std::byte>(6);
+ auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ REQUIRE(buffer_as_str == "info_1");
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ }
+
+ THEN("files can be opened through relative symbolic link over multiple mount points, read and closed again")
+ {
+ kapi::filesystem::mount("/archiv/2024.img", "/information");
+
+ auto fd = kapi::filesystem::open("/information/symlinks/traverse_back_twice/information/sheep_1.txt");
+ REQUIRE(fd >= 0);
+
+ auto buffer = std::vector<std::byte>(7);
+ auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ REQUIRE(buffer_as_str == "sheep_1");
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ }
+
+ THEN("a filesystem can be mounted, files can be opened, read and closed again and unmounted")
+ {
+ REQUIRE(kapi::filesystem::mount("/dev/ram16", "/information") == 0);
+
+ auto fd = kapi::filesystem::open("/information/monkey_house/monkey_1.txt");
+ REQUIRE(fd >= 0);
+
+ auto buffer = std::vector<std::byte>(8);
+ auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ REQUIRE(buffer_as_str == "monkey_1");
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ REQUIRE(kapi::filesystem::umount("/information") == 0);
+ }
+
+ THEN("a filesystem cannot be unmounted if files are still open and can be unmounted after files are closed")
+ {
+ REQUIRE(kapi::filesystem::mount("/dev/ram16", "/information") == 0);
+
+ auto fd = kapi::filesystem::open("/information/monkey_house/monkey_1.txt");
+ REQUIRE(fd >= 0);
+
+ REQUIRE(kapi::filesystem::umount("/information") < 0);
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ REQUIRE(kapi::filesystem::umount("/information") == 0);
+ }
+
+ THEN("device can be opened as file and read from")
+ {
+ auto fd = kapi::filesystem::open("/dev/ram0");
+ REQUIRE(fd >= 0);
+
+ auto buffer = std::vector<std::byte>(512);
+ auto bytes_read = kapi::filesystem::read(fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ }
+
+ THEN("device can be opened as file and written to and read from again")
+ {
+ auto read_fd = kapi::filesystem::open("/dev/ram16");
+ REQUIRE(read_fd >= 0);
+
+ auto buffer = std::vector<std::byte>(512, std::byte{0xAB});
+ auto bytes_written = kapi::filesystem::write(read_fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_written >= 0);
+
+ auto write_fd = kapi::filesystem::open("/dev/ram16");
+ REQUIRE(write_fd >= 0);
+
+ auto read_buffer = std::vector<std::byte>(512);
+ auto bytes_read = kapi::filesystem::read(write_fd, read_buffer.data(), read_buffer.size());
+ REQUIRE(bytes_read >= 0);
+
+ REQUIRE(std::equal(buffer.begin(), buffer.end(), read_buffer.begin()));
+
+ REQUIRE(kapi::filesystem::close(write_fd) == 0);
+ REQUIRE(kapi::filesystem::close(read_fd) == 0);
+ }
+
+ THEN("invalid paths cannot be mounted or unmounted")
+ {
+ REQUIRE(kapi::filesystem::mount("/dev/ram16", "invalid_path") < 0);
+ }
+
+ THEN("invalid paths cannot be unmounted")
+ {
+ REQUIRE(kapi::filesystem::umount("invalid_path") < 0);
+ }
+
+ THEN("non existent files cannot be opened")
+ {
+ auto fd = kapi::filesystem::open("/information/non_existent.txt");
+ REQUIRE(fd < 0);
+ }
+
+ THEN("not opened files cannot closed")
+ {
+ REQUIRE(kapi::filesystem::close(999) < 0);
+ }
+
+ THEN("same file cannot be closed twice")
+ {
+ auto fd = kapi::filesystem::open("/information/info_1.txt");
+ REQUIRE(fd >= 0);
+
+ REQUIRE(kapi::filesystem::close(fd) == 0);
+ REQUIRE(kapi::filesystem::close(fd) < 0);
+ }
+
+ THEN("not opened files cannot be read from")
+ {
+ std::vector<std::byte> buffer(10);
+ auto const invalid_fd = 999uz;
+ auto bytes_read = kapi::filesystem::read(invalid_fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_read < 0);
+ }
+
+ THEN("not opened files cannot be written to")
+ {
+ std::vector<std::byte> buffer(10);
+ auto const invalid_fd = 999uz;
+ auto bytes_written = kapi::filesystem::write(invalid_fd, buffer.data(), buffer.size());
+ REQUIRE(bytes_written < 0);
+ }
+ }
+}
diff --git a/kernel/kapi/interrupts.cpp b/kernel/kapi/interrupts.cpp
new file mode 100644
index 0000000..4efcaa3
--- /dev/null
+++ b/kernel/kapi/interrupts.cpp
@@ -0,0 +1,65 @@
+#include <kapi/interrupts.hpp>
+
+#include <kstd/flat_map>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstdint>
+
+namespace kapi::interrupts
+{
+
+ namespace
+ {
+ auto constinit handlers = kstd::flat_map<std::uint32_t, kstd::vector<handler *>>{};
+ } // namespace
+
+ auto register_handler(std::uint32_t irq_number, handler & handler) -> void
+ {
+ if (handlers.contains(irq_number))
+ {
+ auto & handler_list = handlers.at(irq_number);
+ handler_list.push_back(&handler);
+ }
+ else
+ {
+ handlers.emplace(irq_number, kstd::vector{&handler});
+ }
+ }
+
+ auto unregister_handler(std::uint32_t irq_number, handler & handler) -> void
+ {
+ auto & handler_list = handlers.at(irq_number);
+ auto [first, last] = std::ranges::remove(handler_list, &handler);
+ handler_list.erase(first, last);
+ }
+
+ auto dispatch(std::uint32_t irq_number) -> status
+ {
+ if (!handlers.contains(irq_number))
+ {
+ kstd::println(kstd::print_sink::stderr, "[OS:interrupts] No handler for IRQ{}", irq_number);
+ return status::unhandled;
+ }
+
+ auto & handler_list = handlers.at(irq_number);
+
+ if (handler_list.empty())
+ {
+ kstd::println(kstd::print_sink::stderr, "[OS:interrupts] No handler for IRQ{}", irq_number);
+ return status::unhandled;
+ }
+
+ for (auto handler : handler_list)
+ {
+ if (handler && handler->handle_interrupt(irq_number) == status::handled)
+ {
+ return status::handled;
+ }
+ }
+
+ return status::unhandled;
+ }
+
+} // namespace kapi::interrupts \ No newline at end of file
diff --git a/kernel/kapi/memory.cpp b/kernel/kapi/memory.cpp
index d8065d4..5ea08b1 100644
--- a/kernel/kapi/memory.cpp
+++ b/kernel/kapi/memory.cpp
@@ -1,10 +1,12 @@
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
-#include "kapi/system.hpp"
+#include <kernel/memory/bitmap_allocator.hpp>
+#include <kernel/memory/mmio_allocator.hpp>
-#include "kernel/memory/bitmap_allocator.hpp"
+#include <kapi/system.hpp>
#include <kstd/print>
+#include <kstd/units>
#include <algorithm>
#include <cstddef>
@@ -62,6 +64,7 @@ namespace kapi::memory
constinit bad_frame_allocator bad_frame_allocator::instance{};
constinit bad_page_mapper bad_page_mapper::instance{};
auto constinit allocator = std::optional<kernel::memory::bitmap_frame_allocator>{};
+ auto constinit mmio_allocator = std::optional<kernel::memory::mmio_allocator>{};
} // namespace
constinit auto static active_frame_allocator = static_cast<frame_allocator *>(&bad_frame_allocator::instance);
@@ -112,8 +115,10 @@ namespace kapi::memory
auto init_pmm(std::size_t frame_count, void (&handoff_handler)(frame_allocator &)) -> void
{
- auto const bitmap_bytes = (frame_count + 7uz) / 8uz;
- auto const bitmap_pages = (bitmap_bytes + page::size - 1uz) / page::size;
+ using namespace kstd::units_literals;
+
+ auto const bitmap_bytes = kstd::units::bytes{(frame_count + 7uz) / 8uz};
+ auto const bitmap_pages = (bitmap_bytes + page::size - 1_B) / page::size;
auto const bitmap_frames = allocate_many_frames(bitmap_pages);
if (!bitmap_frames)
@@ -122,15 +127,20 @@ namespace kapi::memory
}
auto const flags = page_mapper::flags::writable | page_mapper::flags::supervisor_only | page_mapper::flags::global;
+ auto bitmap_ptr = static_cast<std::uint64_t *>(nullptr);
std::ranges::for_each(std::views::iota(0uz, bitmap_pages), [&](auto index) {
auto page = page::containing(pmm_metadata_base + index * page::size);
auto frame = memory::frame(bitmap_frames->first.number() + index);
- active_page_mapper->map(page, frame, flags);
+ auto mapped = active_page_mapper->map(page, frame, flags);
+ if (!bitmap_ptr)
+ {
+ bitmap_ptr = reinterpret_cast<std::uint64_t *>(mapped);
+ }
});
- auto bitmap_ptr = static_cast<std::uint64_t *>(pmm_metadata_base);
- auto bitmap = std::span{bitmap_ptr, (bitmap_bytes + sizeof(std::uint64_t) - 1uz) / sizeof(std::uint64_t)};
+ auto bitmap =
+ std::span{bitmap_ptr, (bitmap_bytes + kstd::type_size<std::uint64_t> - 1_B) / kstd::type_size<std::uint64_t>};
allocator.emplace(bitmap, frame_count);
@@ -139,4 +149,38 @@ namespace kapi::memory
kstd::println("[OS:MEM] Physical memory manager initialized.");
}
+ auto init_mmio(linear_address base, std::size_t page_count) -> void
+ {
+ mmio_allocator.emplace(base, page_count);
+ }
+
+ auto allocate_mmio_region(std::size_t page_count) -> mmio_region
+ {
+ auto region = mmio_allocator->allocate(page_count);
+ return {region, page_count};
+ }
+
+ auto map_mmio_region(mmio_region region, physical_address hw_base, page_mapper::flags flags) -> std::byte *
+ {
+ auto start_page = page::containing(region.first);
+ auto start_frame = frame::containing(hw_base);
+
+ flags |= page_mapper::flags::uncached;
+
+ auto start = map(start_page, start_frame, flags);
+
+ std::ranges::for_each(std::views::iota(1uz, region.second), [&](auto index) {
+ auto page = page::containing(region.first + index * page::size);
+ auto frame = frame::containing(hw_base + index * page::size);
+ map(page, frame, flags);
+ });
+
+ return start;
+ }
+
+ auto release_mmio_region(mmio_region region) -> void
+ {
+ mmio_allocator->release(region.first);
+ }
+
} // namespace kapi::memory
diff --git a/kernel/kapi/system.cpp b/kernel/kapi/system.cpp
index a17d9b9..9819ceb 100644
--- a/kernel/kapi/system.cpp
+++ b/kernel/kapi/system.cpp
@@ -1,6 +1,6 @@
-#include "kapi/system.hpp"
+#include <kapi/system.hpp>
-#include "kapi/cpu.hpp"
+#include <kapi/cpu.hpp>
#include <kstd/print>
diff --git a/kernel/kapi/system.tests.cpp b/kernel/kapi/system.tests.cpp
new file mode 100644
index 0000000..1e30031
--- /dev/null
+++ b/kernel/kapi/system.tests.cpp
@@ -0,0 +1,30 @@
+#include <kapi/system.hpp>
+
+#include <kernel/test_support/cio.hpp>
+#include <kernel/test_support/cpu.hpp>
+
+#include <kstd/print>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Kernel testing kapi::system shims", "[support]")
+{
+ GIVEN("the test support infrastructure is initialized")
+ {
+ WHEN("a the system panics")
+ {
+ kernel::tests::cio::log_buffer().clear();
+
+ THEN("the correct exception is thrown")
+ {
+ REQUIRE_THROWS_AS(kapi::system::panic("[kernel:tests] Test Panic"), kernel::tests::cpu::halt);
+ }
+
+ THEN("the message is appended to the log buffer")
+ {
+ CHECK_THROWS(kapi::system::panic("[kernel:tests] Test Panic"));
+ REQUIRE(kernel::tests::cio::log_buffer().flat_messages().contains("[kernel:tests] Test Panic"));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kernel/kstd/os.cpp b/kernel/kstd/os.cpp
index 21254c4..ae69e7e 100644
--- a/kernel/kstd/os.cpp
+++ b/kernel/kstd/os.cpp
@@ -1,4 +1,4 @@
-#include "kapi/system.hpp"
+#include <kapi/system.hpp>
#include <kstd/os/error.hpp>
diff --git a/kernel/kstd/print.cpp b/kernel/kstd/print.cpp
index c7d26ba..d0611b2 100644
--- a/kernel/kstd/print.cpp
+++ b/kernel/kstd/print.cpp
@@ -1,12 +1,14 @@
-#include "kapi/cio.hpp"
+#include <kstd/os/print.hpp>
+
+#include <kapi/cio.hpp>
+#include <kstd/bits/format/output_buffer.hpp>
#include <kstd/format>
-#include <kstd/os/print.hpp>
#include <kstd/print>
+#include <algorithm>
#include <array>
#include <cstddef>
-#include <iterator>
#include <string_view>
namespace kstd::os
@@ -14,7 +16,7 @@ namespace kstd::os
namespace
{
- struct write_buffer
+ struct write_buffer final : kstd::bits::format::output_buffer
{
using output_stream = kapi::cio::output_stream;
@@ -29,35 +31,36 @@ namespace kstd::os
: m_stream{stream}
{}
- ~write_buffer() noexcept
+ ~write_buffer() noexcept final
{
flush();
}
- auto flush() noexcept -> void
+ auto push(std::string_view text) -> void final
{
- if (m_position > 0)
+ std::ranges::for_each(text, [this](auto c) { this->push(c); });
+ }
+
+ auto push(char character) -> void final
+ {
+ if (m_position >= size)
{
- std::string_view chunk{m_buffer.data(), m_position};
- kapi::cio::write(m_stream, chunk);
- m_position = 0;
+ flush();
}
+ m_buffer.at(m_position++) = character;
}
- auto static callback(void * object, std::string_view text) -> void
+ private:
+ auto flush() noexcept -> void
{
- auto * self = static_cast<write_buffer *>(object);
- for (char const character : text)
+ if (m_position > 0)
{
- if (self->m_position >= size)
- {
- self->flush();
- }
- self->m_buffer.at(self->m_position++) = character;
+ std::string_view chunk{m_buffer.data(), m_position};
+ kapi::cio::write(m_stream, chunk);
+ m_position = 0;
}
}
- private:
output_stream m_stream;
std::array<char, size> m_buffer{};
std::size_t m_position{};
@@ -69,77 +72,7 @@ namespace kstd::os
{
auto writer = write_buffer{(sink == print_sink::stderr) ? kapi::cio::output_stream::stderr
: kapi::cio::output_stream::stdout};
- auto context = kstd::format_context{.writer = write_buffer::callback, .user_data = &writer};
-
- auto current = format.begin();
- auto end = format.end();
- auto next_automatic_index = 0uz;
-
- while (current != end)
- {
- if (*current != '{')
- {
- auto start = current;
- while (current != end && *current != '{')
- {
- std::advance(current, 1);
- }
- context.push(std::string_view(start, current - start));
- continue;
- }
-
- if (std::next(current) != end && *(std::next(current)) == '{')
- {
- context.push('{');
- std::advance(current, 2);
- continue;
- }
-
- std::advance(current, 1);
-
- auto index = 0uz;
- if (current != end && *current >= '0' && *current <= '9')
- {
- while (current != end && *current >= '0' && *current <= '9')
- {
- index = index * 10 + static_cast<std::size_t>(*current - '0');
- std::advance(current, 1);
- }
- }
- else
- {
- index = next_automatic_index++;
- }
-
- auto remaining_fmt = std::string_view{current, static_cast<std::size_t>(std::distance(current, end))};
-
- auto const arg = args.get(index);
- if (arg.format)
- {
- auto const after_specs = arg.format(arg.value, remaining_fmt, context);
- auto const consumed = remaining_fmt.size() - after_specs.size();
- std::advance(current, consumed);
- }
- else
- {
- context.push("{?}");
- while (current != end && *current != '}')
- std::advance(current, 1);
- }
-
- if (current != end && *current == '}')
- {
- std::advance(current, 1);
- }
- else
- {
- context.push("{fmt-err}");
- while (current != end && *current != '}')
- std::advance(current, 1);
- if (current != end)
- std::advance(current, 1);
- }
- }
+ kstd::bits::format::vformat_to(writer, format, args);
}
} // namespace kstd::os
diff --git a/kernel/kstd/print.tests.cpp b/kernel/kstd/print.tests.cpp
new file mode 100644
index 0000000..4963f46
--- /dev/null
+++ b/kernel/kstd/print.tests.cpp
@@ -0,0 +1,23 @@
+#include <kstd/print>
+
+#include <kernel/test_support/cio.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Kernel testing kstd shims", "[support]")
+{
+ GIVEN("the test support infrastructure is initialized")
+ {
+ WHEN("a regular print is issued")
+ {
+ kernel::tests::cio::log_buffer().clear();
+
+ kstd::println("[kernel:tests] Test Print");
+
+ THEN("the message is appended to the log buffer")
+ {
+ REQUIRE(kernel::tests::cio::log_buffer().flat_messages().contains("[kernel:tests] Test Print"));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kernel/src/acpi/manager.cpp b/kernel/src/acpi/manager.cpp
new file mode 100644
index 0000000..bb895fd
--- /dev/null
+++ b/kernel/src/acpi/manager.cpp
@@ -0,0 +1,98 @@
+#include <kernel/acpi/manager.hpp>
+
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
+
+#include <acpi/acpi.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <ranges>
+#include <string_view>
+
+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<std::byte const *>(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<std::uintptr_t>(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<std::byte const *>(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
new file mode 100644
index 0000000..13d73ac
--- /dev/null
+++ b/kernel/src/devices/block_device.cpp
@@ -0,0 +1,42 @@
+#include <kernel/devices/block_device.hpp>
+
+#include <kapi/devices/device.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/string>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..a2ddd2b
--- /dev/null
+++ b/kernel/src/devices/block_device.tests.cpp
@@ -0,0 +1,46 @@
+#include <kernel/test_support/devices/block_device.hpp>
+
+#include <kernel/test_support/cpu.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+
+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<kernel::tests::devices::block_device>(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
new file mode 100644
index 0000000..18d1e9d
--- /dev/null
+++ b/kernel/src/devices/block_device_utils.cpp
@@ -0,0 +1,106 @@
+#include <kernel/devices/block_device_utils.hpp>
+
+#include <kernel/devices/block_device.hpp>
+
+#include <kapi/devices/device.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/cstring>
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstddef>
+
+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<kapi::devices::device> 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<devices::block_device *>(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<std::byte> 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<kapi::devices::device> 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<std::byte *>(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<kapi::devices::device> const & device, void const * buffer, size_t offset, size_t size)
+ -> size_t
+ {
+ return process_blocks(device, offset, size, const_cast<void *>(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<std::byte const *>(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
new file mode 100644
index 0000000..e2e1e65
--- /dev/null
+++ b/kernel/src/devices/block_device_utils.tests.cpp
@@ -0,0 +1,219 @@
+#include <kernel/devices/block_device_utils.hpp>
+
+#include <kernel/test_support/cpu.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+#include <kernel/test_support/devices/character_device.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+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<kernel::tests::devices::block_device>(0, 0, "test_block_device", block_size);
+ kstd::vector<uint8_t> block_data(block_size);
+ for (size_t i = 0; i < block_data.size(); ++i)
+ {
+ block_data[i] = static_cast<uint8_t>(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<uint8_t> 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<uint8_t> 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<uint8_t>((256 + i) % 256);
+ REQUIRE(read_buffer[i] == expected_value);
+ }
+ }
+ }
+
+ WHEN("reading beyond the device capacity")
+ {
+ kstd::vector<uint8_t> 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<uint8_t> 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<kernel::tests::devices::block_device>(0, 0, "test_block_device", block_size, 2 * block_size);
+
+ WHEN("writing to the block device using block_device_utils")
+ {
+ kstd::vector<uint8_t> write_buffer(block_size);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(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<uint8_t> read_buffer(block_size);
+ device->read_block(0, read_buffer.data());
+ REQUIRE(read_buffer == write_buffer);
+ }
+ }
+
+ WHEN("writing over block boundaries")
+ {
+ kstd::vector<uint8_t> write_buffer(2 * block_size);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(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<uint8_t> 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<uint8_t> 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<uint8_t> 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<kernel::tests::devices::character_device>(0, 0, "test_character_device");
+
+ WHEN("attempting to read from the non-block device using block_device_utils")
+ {
+ kstd::vector<uint8_t> 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<uint8_t> 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
new file mode 100644
index 0000000..1b754f2
--- /dev/null
+++ b/kernel/src/devices/root_bus.cpp
@@ -0,0 +1,12 @@
+#include <kernel/devices/root_bus.hpp>
+
+#include <kapi/devices.hpp>
+
+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
new file mode 100644
index 0000000..171b918
--- /dev/null
+++ b/kernel/src/devices/storage/controller.cpp
@@ -0,0 +1,44 @@
+#include <kernel/devices/storage/controller.hpp>
+
+#include <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstddef>
+
+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<kapi::devices::device>
+ {
+ 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<kstd::shared_ptr<kapi::devices::device>> 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
new file mode 100644
index 0000000..06efc27
--- /dev/null
+++ b/kernel/src/devices/storage/management.cpp
@@ -0,0 +1,95 @@
+#include <kernel/devices/storage/management.hpp>
+
+#include <kernel/devices/storage/controller.hpp>
+#include <kernel/devices/storage/ram_disk/controller.hpp>
+
+#include <kapi/boot_modules.hpp>
+#include <kapi/devices/device.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstddef>
+#include <optional>
+#include <ranges>
+
+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<kernel::devices::storage::management>{};
+} // 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<ram_disk::controller>(&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<controller> const & controller) -> void
+ {
+ controller->set_ids(next_free_major++, minors_per_device);
+ m_controllers.push_back(controller);
+ }
+
+ auto management::all_controllers() const -> kstd::vector<kstd::shared_ptr<controller>> const &
+ {
+ return m_controllers;
+ }
+
+ auto management::device_by_major_minor(size_t major, size_t minor) -> kstd::shared_ptr<kapi::devices::device>
+ {
+ 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<kapi::devices::device>
+ {
+ 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
new file mode 100644
index 0000000..30441fa
--- /dev/null
+++ b/kernel/src/devices/storage/ram_disk/controller.cpp
@@ -0,0 +1,27 @@
+#include <kernel/devices/storage/ram_disk/controller.hpp>
+
+#include <kernel/devices/storage/ram_disk/device.hpp>
+
+#include <kapi/boot_module/boot_module_registry.hpp>
+
+#include <kstd/memory>
+
+#include <algorithm>
+#include <cstddef>
+
+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, &current_device_index](auto const & module) {
+ auto const minor = current_device_index++ * m_minors_per_device;
+ m_devices.push_back(kstd::make_shared<device>(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
new file mode 100644
index 0000000..1557204
--- /dev/null
+++ b/kernel/src/devices/storage/ram_disk/device.cpp
@@ -0,0 +1,71 @@
+#include <kernel/devices/storage/ram_disk/device.hpp>
+
+#include <kernel/devices/block_device.hpp>
+
+#include <kapi/boot_module/boot_module.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/cstring>
+#include <kstd/string>
+
+#include <cstddef>
+
+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<std::byte const *>(m_boot_module.start_address) + info.offset;
+ kstd::libc::memcpy(buffer, src, info.to_transfer);
+ }
+
+ if (info.remainder > 0)
+ {
+ kstd::libc::memset(static_cast<std::byte *>(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<std::byte *>(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
new file mode 100644
index 0000000..d0fab76
--- /dev/null
+++ b/kernel/src/devices/storage/ram_disk/device.tests.cpp
@@ -0,0 +1,116 @@
+#include <kernel/devices/storage/ram_disk/device.hpp>
+
+#include <kapi/boot_module/boot_module.hpp>
+#include <kapi/memory.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers.hpp>
+#include <catch2/matchers/catch_matchers_range_equals.hpp>
+
+#include <cstddef>
+#include <ranges>
+#include <stdexcept>
+#include <vector>
+
+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<std::byte *>(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<std::byte>(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<std::byte>(device.block_size());
+ device.read_block(10, buffer.data());
+
+ THEN("the buffer is filled with zeros")
+ {
+ REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals(std::vector<std::byte>(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<std::byte>(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<std::byte>(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
new file mode 100644
index 0000000..3d8e01a
--- /dev/null
+++ b/kernel/src/filesystem/dentry.cpp
@@ -0,0 +1,95 @@
+#include <kernel/filesystem/dentry.hpp>
+
+#include <kernel/filesystem/inode.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+
+#include <algorithm>
+#include <cstdint>
+#include <string_view>
+
+namespace kernel::filesystem
+{
+ dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> 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<inode> const &
+ {
+ return m_inode;
+ }
+
+ auto dentry::parent() const -> kstd::shared_ptr<dentry> const &
+ {
+ return m_parent;
+ }
+
+ auto dentry::name() const -> std::string_view
+ {
+ return m_name;
+ }
+
+ auto dentry::absolute_path() const -> kstd::string
+ {
+ kstd::string path = m_name;
+
+ auto parent = m_parent;
+ while (parent)
+ {
+ auto parent_name = parent->m_name;
+ if (parent_name == "/")
+ {
+ path = "/" + path;
+ }
+ else
+ {
+ path = parent_name + "/" + path;
+ }
+
+ parent = parent->m_parent;
+ }
+
+ return path;
+ }
+
+ auto dentry::add_child(kstd::shared_ptr<dentry> const & child) -> void
+ {
+ m_children.push_back(child);
+ }
+
+ auto dentry::find_child(std::string_view name) const -> kstd::shared_ptr<dentry>
+ {
+ 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<uint32_t>(flag);
+ }
+
+ auto dentry::unset_flag(dentry_flags flag) -> void
+ {
+ m_flags &= ~static_cast<uint32_t>(flag);
+ }
+
+ auto dentry::has_flag(dentry_flags flag) const -> bool
+ {
+ return (m_flags & static_cast<uint32_t>(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
new file mode 100644
index 0000000..b7690f5
--- /dev/null
+++ b/kernel/src/filesystem/dentry.tests.cpp
@@ -0,0 +1,150 @@
+#include <kernel/filesystem/dentry.hpp>
+
+#include <kernel/test_support/cpu.hpp>
+#include <kernel/test_support/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Dentry construction", "[filesystem][dentry]")
+{
+ GIVEN("A parent dentry and inode")
+ {
+ auto inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(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<kernel::tests::filesystem::inode>();
+ auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode, "parent");
+
+ WHEN("adding child dentries")
+ {
+ auto child1 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child1");
+ auto child2 = kstd::make_shared<kernel::filesystem::dentry>(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<kernel::tests::filesystem::inode>();
+ 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<kernel::tests::filesystem::inode>();
+ auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode, "/");
+
+ auto home_inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto home_dentry = kstd::make_shared<kernel::filesystem::dentry>(root_dentry, home_inode, "home");
+ root_dentry->add_child(home_dentry);
+
+ auto user_inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto user_dentry = kstd::make_shared<kernel::filesystem::dentry>(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
new file mode 100644
index 0000000..ce887ff
--- /dev/null
+++ b/kernel/src/filesystem/devfs/filesystem.cpp
@@ -0,0 +1,81 @@
+#include <kernel/filesystem/devfs/filesystem.hpp>
+
+#include "kernel/filesystem/filesystem.hpp"
+#include <kernel/devices/storage/management.hpp>
+#include <kernel/filesystem/devfs/inode.hpp>
+#include <kernel/filesystem/device_inode.hpp>
+#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/type.hpp>
+
+#include <kapi/devices/device.hpp>
+
+#include <kstd/memory>
+
+#include <algorithm>
+#include <string_view>
+
+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<kernel::filesystem::filesystem> override
+ {
+ return kstd::make_shared<filesystem>();
+ }
+ };
+
+ [[gnu::used]]
+ constexpr auto registration = type_registration<type>{};
+
+ auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> const &) -> operation_result
+ {
+ m_root_inode = kstd::make_shared<inode>();
+ build_device_inode_table();
+
+ return operation_result::success;
+ }
+
+ auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<kernel::filesystem::inode>
+ {
+ 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<device_inode *>(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_inode>(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
new file mode 100644
index 0000000..36cb411
--- /dev/null
+++ b/kernel/src/filesystem/devfs/filesystem.tests.cpp
@@ -0,0 +1,72 @@
+#include <kernel/filesystem/devfs/filesystem.hpp>
+
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+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
new file mode 100644
index 0000000..7bbfbbe
--- /dev/null
+++ b/kernel/src/filesystem/devfs/inode.cpp
@@ -0,0 +1,21 @@
+#include <kernel/filesystem/devfs/inode.hpp>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..ae26e74
--- /dev/null
+++ b/kernel/src/filesystem/devfs/inode.tests.cpp
@@ -0,0 +1,55 @@
+#include <kernel/filesystem/devfs/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstdint>
+
+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<uint8_t> 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<uint8_t> 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
new file mode 100644
index 0000000..81a784c
--- /dev/null
+++ b/kernel/src/filesystem/device_inode.cpp
@@ -0,0 +1,57 @@
+#include <kernel/filesystem/device_inode.hpp>
+
+#include <kernel/devices/block_device_utils.hpp>
+
+#include <kapi/devices/device.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+
+#include <cstddef>
+
+namespace kernel::filesystem
+{
+ device_inode::device_inode(kstd::shared_ptr<kapi::devices::device> 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<kapi::devices::device> 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
new file mode 100644
index 0000000..025a22a
--- /dev/null
+++ b/kernel/src/filesystem/device_inode.tests.cpp
@@ -0,0 +1,109 @@
+#include <kernel/filesystem/device_inode.hpp>
+
+#include <kernel/test_support/cpu.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+#include <kernel/test_support/devices/character_device.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+SCENARIO("Device inode construction", "[filesystem][device_inode]")
+{
+ GIVEN("a block device")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>(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<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512);
+ auto inode = kernel::filesystem::device_inode{device};
+
+ WHEN("writing to the device inode")
+ {
+ kstd::vector<uint8_t> write_buffer(1024);
+ for (size_t i = 0; i < write_buffer.size(); ++i)
+ {
+ write_buffer[i] = static_cast<uint8_t>(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<uint8_t> 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<kernel::tests::devices::character_device>(0, 0, "test_character_device");
+ auto inode = kernel::filesystem::device_inode{device};
+
+ WHEN("reading from the device inode")
+ {
+ kstd::vector<uint8_t> 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<uint8_t> 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
new file mode 100644
index 0000000..3180a19
--- /dev/null
+++ b/kernel/src/filesystem/ext2/filesystem.cpp
@@ -0,0 +1,247 @@
+#include <kernel/filesystem/ext2/filesystem.hpp>
+
+#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/superblock.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/type.hpp>
+
+#include <kstd/memory>
+#include <kstd/unikstd.h>
+#include <kstd/vector>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+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<kernel::filesystem::filesystem> override
+ {
+ return kstd::make_shared<filesystem>();
+ }
+ };
+
+ [[gnu::used]]
+ constexpr auto registration = type_registration<type>{};
+
+ auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> 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<block_group_descriptor>(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<kernel::filesystem::inode> const & parent, std::string_view name) const
+ -> kstd::shared_ptr<kernel::filesystem::inode>
+ {
+ if (!parent || !parent->is_directory())
+ {
+ return nullptr;
+ }
+
+ auto * ext2_parent = static_cast<inode *>(parent.get());
+ if (!ext2_parent)
+ {
+ return nullptr;
+ }
+
+ auto const & inode_data = ext2_parent->data();
+ kstd::vector<uint8_t> 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<linked_directory_entry const *>(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<linked_directory_entry const *>(buffer.data() + bytes_read);
+ }
+ }
+
+ return nullptr;
+ }
+
+ auto filesystem::read_inode(uint32_t inode_number) const -> kstd::shared_ptr<inode>
+ {
+ 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<size_t>(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<inode>(this, new_inode_data);
+ }
+
+ auto filesystem::indirect_levels() const -> std::array<indirect_level, 3>
+ {
+ 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
new file mode 100644
index 0000000..8341070
--- /dev/null
+++ b/kernel/src/filesystem/ext2/filesystem.tests.cpp
@@ -0,0 +1,139 @@
+#include <kernel/filesystem/ext2/filesystem.hpp>
+
+#include <kernel/devices/storage/management.hpp>
+#include <kernel/filesystem/device_inode.hpp>
+#include <kernel/filesystem/ext2/inode.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+#include <kernel/test_support/filesystem/ext2.hpp>
+#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <cstdint>
+#include <filesystem>
+
+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<kernel::filesystem::device_inode>(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<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 2 * block_size);
+ REQUIRE(device != nullptr);
+
+ auto dev_inode = kstd::make_shared<kernel::filesystem::device_inode>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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<uint32_t>(block_size / sizeof(uint32_t));
+ auto const singly_start = static_cast<uint32_t>(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
new file mode 100644
index 0000000..35a32ee
--- /dev/null
+++ b/kernel/src/filesystem/ext2/inode.cpp
@@ -0,0 +1,111 @@
+#include <kernel/filesystem/ext2/inode.hpp>
+
+#include <kernel/filesystem/ext2/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/cstring>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+
+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<uint8_t const *>(m_data.block.data());
+ kstd::libc::memcpy(static_cast<uint8_t *>(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<uint8_t *>(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<uint8_t *>(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<uint64_t>(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
new file mode 100644
index 0000000..4aecc04
--- /dev/null
+++ b/kernel/src/filesystem/ext2/inode.tests.cpp
@@ -0,0 +1,376 @@
+#include <kernel/filesystem/ext2/inode.hpp>
+
+#include <kernel/devices/storage/management.hpp>
+#include <kernel/filesystem/device_inode.hpp>
+#include <kernel/filesystem/ext2/filesystem.hpp>
+#include <kernel/filesystem/ext2/superblock.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/test_support/cpu.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+#include <kernel/test_support/filesystem/ext2.hpp>
+#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <cstddef>
+#include <filesystem>
+#include <string_view>
+
+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<kernel::filesystem::device_inode>(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<std::byte>(6);
+ auto const bytes_read = file->read(buffer.data(), 0, buffer.size());
+
+ REQUIRE(bytes_read == 6);
+
+ auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read};
+ REQUIRE(text == "info_1");
+ }
+
+ THEN("reading with an offset returns the expected byte")
+ {
+ auto buffer = kstd::vector<std::byte>(1);
+ auto const bytes_read = file->read(buffer.data(), 5, buffer.size());
+
+ REQUIRE(bytes_read == 1);
+ REQUIRE(static_cast<char>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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<std::byte>(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<char const *>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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<std::byte>(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<char const *>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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<std::byte>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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<std::byte>(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<char const *>(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<std::byte>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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<kernel::tests::devices::block_device>(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<kernel::filesystem::device_inode>(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
new file mode 100644
index 0000000..24d0e22
--- /dev/null
+++ b/kernel/src/filesystem/filesystem.cpp
@@ -0,0 +1,60 @@
+#include <kernel/filesystem/filesystem.hpp>
+
+#include <kernel/filesystem/ext2/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+
+#include <array>
+
+namespace kernel::filesystem
+{
+ namespace
+ {
+ constexpr auto static filesystem_factories = std::array{
+ []() { return kstd::make_shared<ext2::filesystem>(); },
+ };
+ } // namespace
+
+ auto filesystem::probe_and_mount(kstd::shared_ptr<inode> const & backing_inode) -> kstd::shared_ptr<filesystem>
+ {
+ 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<inode> 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<inode> const &
+ {
+ return m_root_inode;
+ }
+
+ auto filesystem::backing_inode() const -> kstd::shared_ptr<inode> 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
new file mode 100644
index 0000000..c188917
--- /dev/null
+++ b/kernel/src/filesystem/inode.cpp
@@ -0,0 +1,24 @@
+#include <kernel/filesystem/inode.hpp>
+
+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
new file mode 100644
index 0000000..ead7479
--- /dev/null
+++ b/kernel/src/filesystem/mount.cpp
@@ -0,0 +1,90 @@
+#include <kernel/filesystem/mount.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/string>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kernel::filesystem
+{
+ mount::mount(kstd::shared_ptr<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry,
+ kstd::shared_ptr<filesystem> const & fs, kstd::shared_ptr<mount> const & parent_mount,
+ kstd::shared_ptr<mount> 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<dentry> const &
+ {
+ return m_mount_dentry;
+ }
+
+ auto mount::get_filesystem() const -> kstd::shared_ptr<filesystem> const &
+ {
+ return m_filesystem;
+ }
+
+ auto mount::root_dentry() const -> kstd::shared_ptr<dentry> 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<mount> const &
+ {
+ return m_parent_mount;
+ }
+
+ auto mount::source_mount() const -> kstd::shared_ptr<mount>
+ {
+ 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
new file mode 100644
index 0000000..c9ff82e
--- /dev/null
+++ b/kernel/src/filesystem/mount.tests.cpp
@@ -0,0 +1,94 @@
+#include <kernel/filesystem/mount.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/test_support/cpu.hpp>
+#include <kernel/test_support/filesystem/filesystem.hpp>
+#include <kernel/test_support/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <stdexcept>
+
+SCENARIO("Mount construction", "[filesystem][mount]")
+{
+ GIVEN("a filesystem and a root dentry")
+ {
+ auto fs = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(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<kernel::tests::filesystem::filesystem>();
+ auto root_inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(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
new file mode 100644
index 0000000..e4baac7
--- /dev/null
+++ b/kernel/src/filesystem/mount_table.cpp
@@ -0,0 +1,79 @@
+#include <kernel/filesystem/mount_table.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/mount.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <ranges>
+#include <string_view>
+
+namespace kernel::filesystem
+{
+ auto mount_table::has_child_mounts(kstd::shared_ptr<mount> 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<mount> 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<mount>
+ {
+ 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<kstd::shared_ptr<mount>>::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
new file mode 100644
index 0000000..19b47b2
--- /dev/null
+++ b/kernel/src/filesystem/mount_table.tests.cpp
@@ -0,0 +1,184 @@
+#include <kernel/filesystem/mount_table.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/mount.hpp>
+#include <kernel/test_support/filesystem/filesystem.hpp>
+#include <kernel/test_support/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <string_view>
+
+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<kernel::tests::filesystem::filesystem>();
+ auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount1 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry1, root_dentry1, fs1, nullptr, nullptr);
+
+ auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/mnt");
+ auto mount2 = kstd::make_shared<kernel::filesystem::mount>(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<kernel::tests::filesystem::filesystem>();
+ auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount1 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry1, root_dentry1, fs1, nullptr, nullptr);
+
+ auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount2 = kstd::make_shared<kernel::filesystem::mount>(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<kernel::tests::filesystem::filesystem>();
+ auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount1 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry1, root_dentry1, fs1, nullptr, nullptr);
+
+ auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(
+ mount_dentry1, kstd::make_shared<kernel::tests::filesystem::inode>(), "mnt");
+ auto mount2 = kstd::make_shared<kernel::filesystem::mount>(mount_dentry2, root_dentry2, fs2, mount1, nullptr);
+
+ auto fs3 = kstd::make_shared<kernel::tests::filesystem::filesystem>();
+ auto root_dentry3 = kstd::make_shared<kernel::filesystem::dentry>(
+ nullptr, kstd::make_shared<kernel::tests::filesystem::inode>(), "/");
+ auto mount_dentry3 = kstd::make_shared<kernel::filesystem::dentry>(
+ mount_dentry2, kstd::make_shared<kernel::tests::filesystem::inode>(), "submnt");
+ auto mount3 = kstd::make_shared<kernel::filesystem::mount>(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<kernel::tests::filesystem::filesystem>();
+ auto root_inode = kstd::make_shared<kernel::tests::filesystem::inode>();
+ auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode, "/");
+
+ auto source_mount = kstd::make_shared<kernel::filesystem::mount>(root_dentry, root_dentry, fs, nullptr, nullptr);
+ auto mount = kstd::make_shared<kernel::filesystem::mount>(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
new file mode 100644
index 0000000..a5567bf
--- /dev/null
+++ b/kernel/src/filesystem/open_file_descriptor.cpp
@@ -0,0 +1,46 @@
+#include <kernel/filesystem/open_file_descriptor.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+
+#include <kstd/memory>
+#include <kstd/os/error.hpp>
+
+#include <cstddef>
+
+namespace kernel::filesystem
+{
+ open_file_descriptor::open_file_descriptor(kstd::shared_ptr<dentry> 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<dentry> 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
new file mode 100644
index 0000000..8c24cf0
--- /dev/null
+++ b/kernel/src/filesystem/open_file_descriptor.tests.cpp
@@ -0,0 +1,113 @@
+#include <kernel/filesystem/open_file_descriptor.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/vfs.hpp>
+#include <kernel/test_support/filesystem/inode.hpp>
+#include <kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <string_view>
+
+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<kernel::tests::filesystem::inode>();
+ auto dentry = kstd::make_shared<kernel::filesystem::dentry>(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<kernel::tests::filesystem::inode>();
+ auto dentry = kstd::make_shared<kernel::filesystem::dentry>(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<kernel::filesystem::open_file_descriptor>(dentry);
+
+ THEN("the file can be read and the offset is updated")
+ {
+ kstd::vector<std::byte> 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<char *>(buffer.data()), static_cast<size_t>(bytes_read)};
+ REQUIRE(buffer_as_str == "info_1\n");
+ }
+
+ THEN("the file can be read multiple times")
+ {
+ kstd::vector<std::byte> 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<char *>(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
new file mode 100644
index 0000000..2afe3aa
--- /dev/null
+++ b/kernel/src/filesystem/open_file_table.cpp
@@ -0,0 +1,87 @@
+#include <kernel/filesystem/open_file_table.hpp>
+
+#include <kernel/filesystem/open_file_descriptor.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/unikstd.h>
+
+#include <algorithm>
+#include <cstddef>
+#include <optional>
+
+namespace
+{
+ constinit auto static global_open_file_table = std::optional<kernel::filesystem::open_file_table>{};
+} // 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<open_file_descriptor> 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<open_file_descriptor>
+ {
+ 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
new file mode 100644
index 0000000..3e91111
--- /dev/null
+++ b/kernel/src/filesystem/open_file_table.tests.cpp
@@ -0,0 +1,106 @@
+#include <kernel/filesystem/open_file_table.hpp>
+
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/open_file_descriptor.hpp>
+#include <kernel/test_support/filesystem/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+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<kernel::tests::filesystem::inode>();
+ auto dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode, "test_dentry");
+
+ auto file_descriptor_1 = kstd::make_shared<kernel::filesystem::open_file_descriptor>(dentry);
+ auto file_descriptor_2 = kstd::make_shared<kernel::filesystem::open_file_descriptor>(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<kernel::tests::filesystem::inode>();
+ auto dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode, "test_dentry");
+ auto file_descriptor = kstd::make_shared<kernel::filesystem::open_file_descriptor>(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
new file mode 100644
index 0000000..3c18b5c
--- /dev/null
+++ b/kernel/src/filesystem/path.tests.cpp
@@ -0,0 +1,69 @@
+#include <kernel/filesystem/path.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <string>
+#include <string_view>
+#include <vector>
+
+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<std::string_view> 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
new file mode 100644
index 0000000..7fe5c1e
--- /dev/null
+++ b/kernel/src/filesystem/rootfs/filesystem.cpp
@@ -0,0 +1,47 @@
+#include <kernel/filesystem/rootfs/filesystem.hpp>
+
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/inode.hpp>
+#include <kernel/filesystem/rootfs/inode.hpp>
+#include <kernel/filesystem/type.hpp>
+
+#include <kstd/memory>
+
+#include <string_view>
+
+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<kernel::filesystem::filesystem> override
+ {
+ return kstd::make_shared<filesystem>();
+ }
+ };
+
+ [[gnu::used]]
+ constexpr auto registration = type_registration<type>{};
+
+ auto filesystem::mount(kstd::shared_ptr<kernel::filesystem::inode> const &) -> operation_result
+ {
+ m_root_inode = kstd::make_shared<inode>();
+ return operation_result::success;
+ }
+
+ auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view) const
+ -> kstd::shared_ptr<kernel::filesystem::inode>
+ {
+ return nullptr;
+ }
+} // namespace kernel::filesystem::rootfs
diff --git a/kernel/src/filesystem/rootfs/filesystem.tests.cpp b/kernel/src/filesystem/rootfs/filesystem.tests.cpp
new file mode 100644
index 0000000..ae320e9
--- /dev/null
+++ b/kernel/src/filesystem/rootfs/filesystem.tests.cpp
@@ -0,0 +1,38 @@
+#include <kernel/filesystem/rootfs/filesystem.hpp>
+
+#include <kernel/filesystem/filesystem.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+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
new file mode 100644
index 0000000..f64fb87
--- /dev/null
+++ b/kernel/src/filesystem/rootfs/inode.cpp
@@ -0,0 +1,23 @@
+#include <kernel/filesystem/inode.hpp>
+
+#include <kernel/filesystem/rootfs/inode.hpp>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..f4b634f
--- /dev/null
+++ b/kernel/src/filesystem/rootfs/inode.tests.cpp
@@ -0,0 +1,37 @@
+#include <kernel/filesystem/rootfs/inode.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+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<char> 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<char> 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
new file mode 100644
index 0000000..d917c81
--- /dev/null
+++ b/kernel/src/filesystem/type_registry.cpp
@@ -0,0 +1,72 @@
+#include <kernel/filesystem/type_registry.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <algorithm>
+#include <cstddef>
+#include <optional>
+#include <ranges>
+#include <span>
+
+namespace kernel::filesystem
+{
+
+ extern "C"
+ {
+ extern type_registry::pointer const __start_fs_types;
+ extern type_registry::pointer const __stop_fs_types;
+ }
+
+ namespace
+ {
+ auto constinit instance = std::optional<type_registry>{};
+ }
+
+ 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<pointer const>
+ {
+ 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
new file mode 100644
index 0000000..8382579
--- /dev/null
+++ b/kernel/src/filesystem/type_registry.tests.cpp
@@ -0,0 +1,77 @@
+#include <kernel/filesystem/type_registry.hpp>
+
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/type.hpp>
+
+#include <kstd/memory>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+#include <string_view>
+
+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<kernel::filesystem::filesystem> 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
new file mode 100644
index 0000000..e5dff8c
--- /dev/null
+++ b/kernel/src/filesystem/vfs.cpp
@@ -0,0 +1,299 @@
+#include <kernel/filesystem/vfs.hpp>
+
+#include <kernel/filesystem/constants.hpp>
+#include <kernel/filesystem/dentry.hpp>
+#include <kernel/filesystem/devfs/filesystem.hpp>
+#include <kernel/filesystem/filesystem.hpp>
+#include <kernel/filesystem/mount.hpp>
+#include <kernel/filesystem/mount_table.hpp>
+#include <kernel/filesystem/path.hpp>
+#include <kernel/filesystem/rootfs/filesystem.hpp>
+
+#include <kapi/system.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstdint>
+#include <optional>
+#include <ranges>
+#include <string_view>
+#include <utility>
+
+namespace
+{
+ constinit auto static active_vfs = std::optional<kernel::filesystem::vfs>{};
+} // 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<rootfs::filesystem>();
+ root_fs->mount(nullptr);
+
+ auto root_fs_root_dentry = kstd::make_shared<dentry>(nullptr, root_fs->root_inode(), "/");
+ auto root_mount = kstd::make_shared<mount>(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<devfs::filesystem>();
+ 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<dentry>
+ {
+ 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<dentry> const & mount_point_dentry,
+ kstd::shared_ptr<mount> const & parent_mount, kstd::shared_ptr<filesystem> const & fs,
+ kstd::shared_ptr<mount> const & source_mount) -> void
+ {
+ auto new_fs_root =
+ kstd::make_shared<dentry>(mount_point_dentry->parent(), fs->root_inode(), mount_point_dentry->name());
+ auto new_mount = kstd::make_shared<mount>(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<devfs::filesystem> 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<dentry>(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<dentry>, kstd::shared_ptr<mount>>
+ {
+ 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<dentry>(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<uint8_t> 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<char const *>(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<dentry>
+ {
+ return resolve_path_internal(path).first;
+ }
+
+ auto vfs::find_mount(std::string_view path) const -> kstd::shared_ptr<mount>
+ {
+ 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
new file mode 100644
index 0000000..f1d0df0
--- /dev/null
+++ b/kernel/src/filesystem/vfs.tests.cpp
@@ -0,0 +1,568 @@
+#include <kernel/filesystem/vfs.hpp>
+
+#include <kernel/filesystem/open_file_descriptor.hpp>
+#include <kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp>
+
+#include <kstd/memory>
+#include <kstd/vector>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <stdexcept>
+#include <string_view>
+
+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<kernel::filesystem::open_file_descriptor>(dentry);
+
+ kstd::vector<std::byte> buffer(7);
+ auto bytes_read = sheep_1_ofd->read(buffer.data(), buffer.size());
+ std::string_view buffer_as_str{reinterpret_cast<char *>(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<kernel::filesystem::open_file_descriptor>(sheep_1);
+ auto goat_1_ofd = kstd::make_shared<kernel::filesystem::open_file_descriptor>(goat_1);
+
+ kstd::vector<std::byte> sheep_buffer(7);
+ auto bytes_read = sheep_1_ofd->read(sheep_buffer.data(), sheep_buffer.size());
+ std::string_view buffer_as_str{reinterpret_cast<char *>(sheep_buffer.data()), bytes_read};
+ REQUIRE(buffer_as_str == "sheep_1");
+
+ kstd::vector<std::byte> goat_buffer(6);
+ bytes_read = goat_1_ofd->read(goat_buffer.data(), goat_buffer.size());
+ buffer_as_str = std::string_view{reinterpret_cast<char *>(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
index 01bcbb0..8dc1349 100644
--- a/kernel/src/main.cpp
+++ b/kernel/src/main.cpp
@@ -1,20 +1,152 @@
-#include "kapi/cio.hpp"
-#include "kapi/memory.hpp"
-#include "kapi/system.hpp"
+#include "kernel/filesystem/type_registry.hpp"
+#include <kernel/devices/storage/management.hpp>
+#include <kernel/filesystem/open_file_table.hpp>
+#include <kernel/filesystem/vfs.hpp>
+#include <kernel/memory.hpp>
-#include "kernel/memory.hpp"
+#include <kapi/boot_modules.hpp>
+#include <kapi/cio.hpp>
+#include <kapi/cpu.hpp>
+#include <kapi/devices.hpp>
+#include <kapi/filesystem.hpp>
+#include <kapi/interrupts.hpp>
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
+#include <kstd/format>
#include <kstd/print>
+#include <kstd/units>
+#include <kstd/vector>
+
+#include <cstddef>
+#include <string_view>
+
+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<std::byte> 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<char *>(buffer_1.data()), static_cast<size_t>(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<std::byte> buffer_2{10};
+ bytes_read = kapi::filesystem::read(fd_2, buffer_2.data(), buffer_2.size());
+ buffer_as_str = std::string_view{reinterpret_cast<char *>(buffer_2.data()), static_cast<size_t>(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<std::byte> 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<std::byte> 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);
- kstd::println("[OS] Memory subsystem initialized.");
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
index 0f614f0..6a85c0e 100644
--- a/kernel/src/memory.cpp
+++ b/kernel/src/memory.cpp
@@ -1,16 +1,15 @@
-#include "kernel/memory.hpp"
+#include <kernel/memory.hpp>
-#include "kapi/memory.hpp"
-#include "kapi/system.hpp"
+#include <kernel/memory/block_list_allocator.hpp>
+#include <kernel/memory/heap_allocator.hpp>
-#include "kernel/memory/block_list_allocator.hpp"
-#include "kernel/memory/heap_allocator.hpp"
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
#include <kstd/print>
+#include <kstd/units>
#include <atomic>
-#include <cstddef>
-#include <new>
#include <optional>
namespace kernel::memory
@@ -22,7 +21,7 @@ namespace kernel::memory
{
null_allocator static instance;
- [[nodiscard]] auto allocate(std::size_t, std::align_val_t) noexcept -> void * override
+ [[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;
diff --git a/kernel/src/memory/bitmap_allocator.cpp b/kernel/src/memory/bitmap_allocator.cpp
index c010f77..240e2af 100644
--- a/kernel/src/memory/bitmap_allocator.cpp
+++ b/kernel/src/memory/bitmap_allocator.cpp
@@ -1,11 +1,12 @@
-#include "kernel/memory/bitmap_allocator.hpp"
+#include <kernel/memory/bitmap_allocator.hpp>
-#include "kapi/memory.hpp"
+#include <kapi/memory.hpp>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
+#include <ranges>
#include <span>
#include <utility>
@@ -17,7 +18,9 @@ namespace kernel::memory
, m_frame_count{frame_count}
, m_last_index{}
{
- std::ranges::fill(m_bitmap, ~0uz);
+ 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
@@ -78,22 +81,25 @@ namespace kernel::memory
auto bitmap_frame_allocator::test(std::size_t index) const noexcept -> bool
{
- auto entry_entry = index / 64;
- auto entry_offset = index % 64;
+ 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
{
- auto entry_entry = index / 64;
- auto entry_offset = index % 64;
+ 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
{
- auto entry_entry = index / 64;
- auto entry_offset = index % 64;
+ 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);
}
diff --git a/kernel/src/memory/bitmap_allocator.tests.cpp b/kernel/src/memory/bitmap_allocator.tests.cpp
new file mode 100644
index 0000000..05d11a3
--- /dev/null
+++ b/kernel/src/memory/bitmap_allocator.tests.cpp
@@ -0,0 +1,288 @@
+#include <kernel/memory/bitmap_allocator.hpp>
+
+#include <kapi/memory.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers.hpp>
+#include <catch2/matchers/catch_matchers_range_equals.hpp>
+
+#include <cstdint>
+#include <limits>
+#include <ranges>
+#include <span>
+#include <vector>
+
+constexpr auto all_bits_set = std::numeric_limits<std::uint64_t>::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
index 5d870d8..6e68ada 100644
--- a/kernel/src/memory/block_list_allocator.cpp
+++ b/kernel/src/memory/block_list_allocator.cpp
@@ -1,24 +1,26 @@
-#include "kernel/memory/block_list_allocator.hpp"
+#include <kernel/memory/block_list_allocator.hpp>
-#include "kapi/memory.hpp"
-#include "kapi/system.hpp"
+#include <kernel/memory/heap_allocator.hpp>
-#include "kernel/memory/heap_allocator.hpp"
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
#include <kstd/mutex>
+#include <kstd/units>
#include <bit>
#include <cstddef>
#include <cstdint>
#include <memory>
-#include <new>
+
+using namespace kstd::units_literals;
namespace kernel::memory
{
namespace
{
- [[nodiscard]] constexpr auto align_up(std::byte * pointer, std::align_val_t alignment) noexcept -> std::byte *
+ [[nodiscard]] constexpr auto align_up(std::byte * pointer, kstd::units::bytes alignment) noexcept -> std::byte *
{
auto const remainder = std::bit_cast<std::uintptr_t>(pointer) % static_cast<std::size_t>(alignment);
return remainder == 0 ? pointer : pointer + static_cast<std::size_t>(alignment) - remainder;
@@ -33,7 +35,7 @@ namespace kernel::memory
, m_lock{}
{}
- auto block_list_allocator::allocate(std::size_t size, std::align_val_t alignment) noexcept -> void *
+ auto block_list_allocator::allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void *
{
kstd::lock_guard guard{m_lock};
@@ -47,13 +49,13 @@ namespace kernel::memory
auto const raw_block = reinterpret_cast<std::byte *>(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<std::size_t>(aligned_payload - unaligned_payload);
+ auto const required_padding = static_cast<kstd::units::bytes>(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<std::size_t>(payload_header - raw_block);
+ auto const front_padding = static_cast<kstd::units::bytes>(payload_header - raw_block);
auto payload_block = current;
@@ -72,9 +74,10 @@ namespace kernel::memory
payload_block->prev = current;
}
- auto const payload_size =
- aligned_payload - reinterpret_cast<std::byte *>(payload_block) - allocated_metadata_size + size;
- split(payload_block, payload_size, 0uz);
+ auto const header_size =
+ static_cast<kstd::units::bytes>(aligned_payload - reinterpret_cast<std::byte *>(payload_block));
+ auto const payload_size = header_size - allocated_metadata_size + size;
+ split(payload_block, payload_size, 0_B);
payload_block->free = false;
@@ -87,7 +90,7 @@ namespace kernel::memory
current = current->next;
}
- auto const search_size = size + static_cast<std::size_t>(alignment);
+ auto const search_size = size + alignment;
if (attempt == 0uz && !expand(search_size))
{
return nullptr;
@@ -114,10 +117,10 @@ namespace kernel::memory
coalesce(block);
}
- auto block_list_allocator::expand(std::size_t size) noexcept -> bool
+ 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) / kapi::memory::frame::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;
@@ -187,7 +190,8 @@ namespace kernel::memory
}
}
- auto block_list_allocator::split(block_header * block, std::size_t size, std::size_t padding) noexcept -> void
+ 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;
diff --git a/kernel/src/memory/block_list_allocator.tests.cpp b/kernel/src/memory/block_list_allocator.tests.cpp
new file mode 100644
index 0000000..c5f84c5
--- /dev/null
+++ b/kernel/src/memory/block_list_allocator.tests.cpp
@@ -0,0 +1,85 @@
+#include <kernel/memory/block_list_allocator.hpp>
+
+#include <kernel/test_support/memory.hpp>
+
+#include <kapi/memory.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+
+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<std::byte *>(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
new file mode 100644
index 0000000..ba23dbd
--- /dev/null
+++ b/kernel/src/memory/mmio_allocator.cpp
@@ -0,0 +1,111 @@
+#include <kernel/memory/mmio_allocator.hpp>
+
+#include <kapi/memory.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/allocator>
+
+#include <cstddef>
+#include <memory>
+#include <utility>
+
+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<kstd::allocator<node>>;
+
+ 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<kstd::allocator<node>>;
+
+ 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
index 57e31e6..5673d68 100644
--- a/kernel/src/memory/operators.cpp
+++ b/kernel/src/memory/operators.cpp
@@ -1,6 +1,8 @@
-#include "kapi/system.hpp"
+#include <kernel/memory.hpp>
-#include "kernel/memory.hpp"
+#include <kapi/system.hpp>
+
+#include <kstd/units>
#include <cstddef>
#include <new>
@@ -8,7 +10,8 @@
[[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(size, alignment);
+ return allocator.allocate(static_cast<kstd::units::bytes>(size),
+ static_cast<kstd::units::bytes>(static_cast<std::size_t>(alignment)));
}
[[nodiscard]] auto operator new(std::size_t size, std::align_val_t alignment) -> void *
@@ -73,9 +76,9 @@ auto operator delete(void * pointer, std::align_val_t) noexcept -> void
::operator delete(pointer);
}
-auto operator delete(void * pointer, std::size_t, std::align_val_t) noexcept -> void
+auto operator delete(void * pointer, std::size_t size, std::align_val_t) noexcept -> void
{
- ::operator delete(pointer);
+ ::operator delete(pointer, size);
}
auto operator delete[](void * pointer) noexcept -> void
@@ -83,9 +86,9 @@ auto operator delete[](void * pointer) noexcept -> void
::operator delete(pointer);
}
-auto operator delete[](void * pointer, std::size_t) noexcept -> void
+auto operator delete[](void * pointer, std::size_t size) noexcept -> void
{
- ::operator delete(pointer);
+ ::operator delete(pointer, size);
}
auto operator delete[](void * pointer, std::align_val_t) noexcept -> void
@@ -93,7 +96,7 @@ auto operator delete[](void * pointer, std::align_val_t) noexcept -> void
::operator delete(pointer);
}
-auto operator delete[](void * pointer, std::size_t, std::align_val_t) noexcept -> void
+auto operator delete[](void * pointer, std::size_t size, std::align_val_t) noexcept -> void
{
- ::operator delete(pointer);
+ ::operator delete(pointer, size);
}
diff --git a/kernel/src/test_support/devices/block_device.cpp b/kernel/src/test_support/devices/block_device.cpp
new file mode 100644
index 0000000..9a9e544
--- /dev/null
+++ b/kernel/src/test_support/devices/block_device.cpp
@@ -0,0 +1,61 @@
+#include <kernel/test_support/devices/block_device.hpp>
+
+#include <kernel/devices/block_device.hpp>
+
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <string.h>
+
+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<uint8_t *>(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<uint8_t const *>(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
new file mode 100644
index 0000000..3806654
--- /dev/null
+++ b/kernel/src/test_support/devices/character_device.cpp
@@ -0,0 +1,19 @@
+#include <kernel/test_support/devices/character_device.hpp>
+
+#include <kapi/devices.hpp>
+
+#include <kstd/string>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..52b6efe
--- /dev/null
+++ b/kernel/src/test_support/filesystem/ext2.cpp
@@ -0,0 +1,68 @@
+#include <kernel/test_support/filesystem/ext2.hpp>
+
+#include <kernel/filesystem/ext2/block_group_descriptor.hpp>
+#include <kernel/filesystem/ext2/filesystem.hpp>
+#include <kernel/filesystem/ext2/inode.hpp>
+#include <kernel/filesystem/ext2/superblock.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+
+#include <cstdint>
+#include <cstring>
+
+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<size_t>(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
new file mode 100644
index 0000000..ec70607
--- /dev/null
+++ b/kernel/src/test_support/filesystem/filesystem.cpp
@@ -0,0 +1,17 @@
+#include <kernel/test_support/filesystem/filesystem.hpp>
+
+#include <kernel/filesystem/inode.hpp>
+#include <kernel/test_support/filesystem/inode.hpp>
+
+#include <kstd/memory>
+
+#include <string_view>
+
+namespace kernel::tests::filesystem
+{
+ auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view) const
+ -> kstd::shared_ptr<kernel::filesystem::inode>
+ {
+ return kstd::make_shared<inode>();
+ }
+} // 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
new file mode 100644
index 0000000..0c8d956
--- /dev/null
+++ b/kernel/src/test_support/filesystem/inode.cpp
@@ -0,0 +1,23 @@
+#include <kernel/test_support/filesystem/inode.hpp>
+
+#include <kernel/filesystem/inode.hpp>
+
+#include <cstddef>
+
+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
new file mode 100644
index 0000000..aabaace
--- /dev/null
+++ b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp
@@ -0,0 +1,139 @@
+#include <kernel/test_support/filesystem/storage_boot_module_fixture.hpp>
+
+#include <kernel/devices/storage/management.hpp>
+#include <kernel/test_support/boot_modules.hpp>
+#include <kernel/test_support/devices/storage/management.hpp>
+
+#include <kapi/boot_module/boot_module.hpp>
+#include <kapi/boot_modules.hpp>
+#include <kapi/memory.hpp>
+
+#include <cstddef>
+#include <fcntl.h>
+#include <filesystem>
+#include <format>
+#include <stdexcept>
+#include <string>
+#include <unistd.h>
+#include <utility>
+#include <vector>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+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<std::size_t>(statistics.st_size);
+
+ mapping = static_cast<std::byte *>(::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<unsigned char>(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<std::string> const & module_names,
+ std::vector<std::filesystem::path> 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
new file mode 100644
index 0000000..02ccfec
--- /dev/null
+++ b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp
@@ -0,0 +1,31 @@
+#include <kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp>
+
+#include <kernel/filesystem/vfs.hpp>
+#include <kernel/test_support/filesystem/vfs.hpp>
+
+#include <cstddef>
+#include <filesystem>
+#include <string>
+#include <vector>
+
+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<std::string> const & module_names, std::vector<std::filesystem::path> 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
new file mode 120000
index 0000000..718a227
--- /dev/null
+++ b/kernel/src/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/src/test_support/filesystem/test_assets/ext2_1KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img
new file mode 100644
index 0000000..a5202ca
--- /dev/null
+++ b/kernel/src/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/src/test_support/filesystem/test_assets/ext2_2KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img
new file mode 100644
index 0000000..7f297f0
--- /dev/null
+++ b/kernel/src/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/src/test_support/filesystem/test_assets/ext2_4KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img
new file mode 100644
index 0000000..c3f6daf
--- /dev/null
+++ b/kernel/src/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/src/test_support/kapi/cio.cpp b/kernel/src/test_support/kapi/cio.cpp
new file mode 100644
index 0000000..98bc99d
--- /dev/null
+++ b/kernel/src/test_support/kapi/cio.cpp
@@ -0,0 +1,54 @@
+#include <kernel/test_support/cio.hpp>
+
+#include <kernel/test_support/log_buffer.hpp>
+
+#include <kapi/cio.hpp>
+
+#include <atomic>
+#include <optional>
+#include <stdexcept>
+
+namespace
+{
+
+ auto constinit is_initialized = std::atomic_flag{};
+ auto constinit device = std::optional<kernel::tests::cio::output_device>{};
+
+} // 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
new file mode 100644
index 0000000..5d95633
--- /dev/null
+++ b/kernel/src/test_support/kapi/cpu.cpp
@@ -0,0 +1,52 @@
+#include <kernel/test_support/cpu.hpp>
+
+#include <kapi/cpu.hpp>
+
+#include <atomic>
+#include <stdexcept>
+
+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
new file mode 100644
index 0000000..0077266
--- /dev/null
+++ b/kernel/src/test_support/kapi/interrupts.cpp
@@ -0,0 +1,11 @@
+#include <kapi/interrupts.hpp>
+
+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
new file mode 100644
index 0000000..7fc95cb
--- /dev/null
+++ b/kernel/src/test_support/kapi/memory.cpp
@@ -0,0 +1,74 @@
+#include <kapi/memory.hpp>
+
+#include <kernel/test_support/bump_frame_allocator.hpp>
+#include <kernel/test_support/page_mapper.hpp>
+
+#include <kapi/memory.hpp>
+
+#include <kstd/units>
+
+#include <optional>
+
+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<kernel::tests::bump_frame_allocator>{};
+ auto constinit test_mapper = std::optional<kernel::tests::page_mapper>{};
+
+ auto constinit old_allocator = std::optional<kapi::memory::frame_allocator *>{};
+ auto constinit old_mapper = std::optional<kapi::memory::page_mapper *>{};
+
+ 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
new file mode 100644
index 0000000..04d875b
--- /dev/null
+++ b/kernel/src/test_support/log_buffer.cpp
@@ -0,0 +1,33 @@
+#include <kernel/test_support/log_buffer.hpp>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+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<std::string> 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
new file mode 100644
index 0000000..45fb4bc
--- /dev/null
+++ b/kernel/src/test_support/output_device.cpp
@@ -0,0 +1,28 @@
+#include <kernel/test_support/cio.hpp>
+#include <kernel/test_support/log_buffer.hpp>
+
+#include <kapi/cio.hpp>
+
+#include <iostream>
+#include <string>
+#include <string_view>
+
+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
new file mode 100644
index 0000000..3d50ff1
--- /dev/null
+++ b/kernel/src/test_support/page_mapper.cpp
@@ -0,0 +1,70 @@
+#include <kernel/test_support/page_mapper.hpp>
+
+#include <kapi/memory.hpp>
+
+#include <kstd/units>
+
+#include <cstddef>
+#include <format>
+#include <stdexcept>
+
+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<std::byte *>(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
new file mode 100644
index 0000000..074e6b1
--- /dev/null
+++ b/kernel/src/test_support/simulated_memory.cpp
@@ -0,0 +1,106 @@
+#include <kernel/test_support/simulated_memory.hpp>
+
+#include <kapi/memory.hpp>
+
+#include <kstd/units>
+
+#include <cerrno>
+#include <cstddef>
+#include <cstring>
+#include <format>
+#include <stdexcept>
+#include <unistd.h>
+
+#include <sys/mman.h>
+#include <sys/types.h>
+
+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<off_t>(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<std::byte *>(physical_storage);
+ m_virtual_base = static_cast<std::byte *>(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<std::byte *>(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
new file mode 100644
index 0000000..6bb7537
--- /dev/null
+++ b/kernel/src/test_support/state_reset_listener.cpp
@@ -0,0 +1,48 @@
+#include <kernel/filesystem/open_file_table.hpp>
+#include <kernel/test_support/boot_modules.hpp>
+#include <kernel/test_support/cio.hpp>
+#include <kernel/test_support/cpu.hpp>
+#include <kernel/test_support/devices/storage/management.hpp>
+#include <kernel/test_support/filesystem/open_file_table.hpp>
+#include <kernel/test_support/filesystem/vfs.hpp>
+#include <kernel/test_support/memory.hpp>
+
+#include <kapi/cio.hpp>
+#include <kapi/cpu.hpp>
+#include <kapi/memory.hpp>
+
+#include <catch2/catch_test_case_info.hpp>
+#include <catch2/interfaces/catch_interfaces_reporter.hpp>
+#include <catch2/reporters/catch_reporter_event_listener.hpp>
+#include <catch2/reporters/catch_reporter_registrars.hpp>
+
+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/libs/CMakeLists.txt b/libs/CMakeLists.txt
index 58d9796..2b61992 100644
--- a/libs/CMakeLists.txt
+++ b/libs/CMakeLists.txt
@@ -1,3 +1,4 @@
+add_subdirectory("acpi" EXCLUDE_FROM_ALL SYSTEM)
add_subdirectory("elf" EXCLUDE_FROM_ALL SYSTEM)
add_subdirectory("kstd" EXCLUDE_FROM_ALL SYSTEM)
-add_subdirectory("multiboot2" EXCLUDE_FROM_ALL SYSTEM) \ No newline at end of file
+add_subdirectory("multiboot2" EXCLUDE_FROM_ALL SYSTEM)
diff --git a/libs/acpi/CMakeLists.txt b/libs/acpi/CMakeLists.txt
new file mode 100644
index 0000000..1d03bb5
--- /dev/null
+++ b/libs/acpi/CMakeLists.txt
@@ -0,0 +1,118 @@
+cmake_minimum_required(VERSION "3.27.0")
+
+project("acpi"
+ DESCRIPTION "An ACPI parsing library"
+ VERSION "0.0.1"
+ LANGUAGES ASM CXX
+)
+
+include("CTest")
+
+#[============================================================================[
+# Library
+#]============================================================================]
+
+add_library("acpi" STATIC)
+add_library("acpi::lib" ALIAS "acpi")
+
+target_sources("acpi" PRIVATE
+ "acpi/common/checksum.cpp"
+ "acpi/common/table_header.cpp"
+ "acpi/data/madt.cpp"
+ "acpi/data/rsdt.cpp"
+ "acpi/data/xsdt.cpp"
+ "acpi/pointers.cpp"
+)
+
+file(GLOB_RECURSE ACPI_HEADERS
+ CONFIGURE_DEPENDS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ "acpi/*.hpp"
+)
+
+target_sources("acpi" PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS "acpi"
+ FILES
+ ${ACPI_HEADERS}
+)
+
+target_include_directories("acpi" PUBLIC
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+)
+
+target_link_libraries("acpi" PUBLIC
+ "kstd::lib"
+)
+
+set_target_properties("acpi" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
+)
+
+#[============================================================================[
+# Tests
+#]============================================================================]
+
+if(BUILD_TESTING)
+ find_package("Catch2")
+ include("Catch")
+
+ find_program(IASL_EXE NAMES "iasl" REQUIRED)
+
+ set(TEST_TABLES
+ "basic_madt"
+ "basic_rsdt"
+ "basic_rsdp"
+ "basic_xsdt"
+ "table_header"
+ )
+
+ foreach(TABLE IN LISTS TEST_TABLES)
+ add_custom_command(OUTPUT "test_data/${TABLE}.aml"
+ COMMAND ${CMAKE_COMMAND}
+ "-DIASL_EXE=${IASL_EXE}"
+ "-DIASL_OUTPUT=test_data/${TABLE}.aml"
+ "-DIASL_INPUT=${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl"
+ "-P"
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Scripts/IaslCompile.cmake"
+ DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_data/${TABLE}.asl"
+ COMMENT "Compiling test_data/${TABLE}.asl"
+ VERBATIM
+ )
+ list(APPEND GENERATED_TABLE_BLOBS "${CMAKE_CURRENT_BINARY_DIR}/test_data/${TABLE}.aml")
+ endforeach()
+
+ set_source_files_properties("acpi/test_data/tables.S" PROPERTIES OBJECT_DEPENDS "${GENERATED_TABLE_BLOBS}")
+
+ if(COMMAND "enable_coverage")
+ enable_coverage("acpi")
+ endif()
+
+ add_executable("acpi_tests")
+ add_executable("acpi::tests" ALIAS "acpi_tests")
+
+ target_sources("acpi_tests" PRIVATE
+ "acpi/common/table_header.test.cpp"
+ "acpi/data/madt.test.cpp"
+ "acpi/data/rsdt.test.cpp"
+ "acpi/data/xsdt.test.cpp"
+ "acpi/pointers.test.cpp"
+
+ "acpi/test_data/tables.S"
+ )
+
+ target_include_directories("acpi_tests" PRIVATE
+ "${CMAKE_CURRENT_BINARY_DIR}/test_data"
+ )
+
+ target_link_libraries("acpi_tests" PRIVATE
+ "Catch2::Catch2WithMain"
+ "acpi::lib"
+ )
+
+ set_target_properties("acpi_tests" PROPERTIES
+ EXCLUDE_FROM_ALL NO
+ )
+
+ catch_discover_tests("acpi::tests" ${CATCH_TEST_ARGS})
+endif()
diff --git a/libs/acpi/acpi/acpi.hpp b/libs/acpi/acpi/acpi.hpp
new file mode 100644
index 0000000..0da44a8
--- /dev/null
+++ b/libs/acpi/acpi/acpi.hpp
@@ -0,0 +1,12 @@
+#ifndef ACPI_ACPI_HPP
+#define ACPI_ACPI_HPP
+
+#include <acpi/common/checksum.hpp> // IWYU pragma: export
+#include <acpi/common/table_signature.hpp> // IWYU pragma: export
+#include <acpi/common/table_type.hpp> // IWYU pragma: export
+#include <acpi/data/madt.hpp> // IWYU pragma: export
+#include <acpi/data/rsdt.hpp> // IWYU pragma: export
+#include <acpi/data/xsdt.hpp> // IWYU pragma: export
+#include <acpi/pointers.hpp> // IWYU pragma: export
+
+#endif
diff --git a/libs/acpi/acpi/common/basic_table.hpp b/libs/acpi/acpi/common/basic_table.hpp
new file mode 100644
index 0000000..f5b5b27
--- /dev/null
+++ b/libs/acpi/acpi/common/basic_table.hpp
@@ -0,0 +1,40 @@
+#ifndef ACPI_COMMON_BASIC_TABLE_HPP
+#define ACPI_COMMON_BASIC_TABLE_HPP
+
+#include <acpi/common/checksum.hpp>
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+
+#include <cstddef>
+#include <span>
+
+namespace acpi
+{
+
+ template<typename TableType>
+ struct basic_table : table_header
+ {
+ [[nodiscard]] auto validate_checksum() const noexcept -> bool
+ {
+ return acpi::validate_checksum(as_span());
+ }
+
+ [[nodiscard]] auto as_span() const noexcept -> std::span<std::byte const>
+ {
+ return {reinterpret_cast<std::byte const *>(this), length().value};
+ }
+
+ [[nodiscard]] auto validate() const noexcept -> bool
+ {
+ return signature() == table_signature_v<TableType> && validate_checksum();
+ }
+
+ private:
+ friend TableType;
+
+ constexpr basic_table() noexcept = default;
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/common/checksum.cpp b/libs/acpi/acpi/common/checksum.cpp
new file mode 100644
index 0000000..09425dd
--- /dev/null
+++ b/libs/acpi/acpi/common/checksum.cpp
@@ -0,0 +1,19 @@
+#include <acpi/common/checksum.hpp>
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ auto validate_checksum(std::span<std::byte const> data) -> bool
+ {
+ auto sum = std::ranges::fold_left(data, std::uint8_t{}, [](auto acc, auto byte) {
+ return static_cast<std::uint8_t>(acc + static_cast<std::uint8_t>(byte));
+ });
+ return sum == 0;
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/common/checksum.hpp b/libs/acpi/acpi/common/checksum.hpp
new file mode 100644
index 0000000..a92c242
--- /dev/null
+++ b/libs/acpi/acpi/common/checksum.hpp
@@ -0,0 +1,20 @@
+#ifndef ACPI_CHECKSUM_HPP
+#define ACPI_CHECKSUM_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <cstddef>
+#include <span>
+
+namespace acpi
+{
+
+ //! Validate and ACPI entity checksum.
+ //!
+ //! @param data The data to validate the checksum of.
+ //! @return true iff. the checksum is valid, false otherwise.
+ auto validate_checksum(std::span<std::byte const> data) -> bool;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/common/table_header.cpp b/libs/acpi/acpi/common/table_header.cpp
new file mode 100644
index 0000000..6a7a4dc
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.cpp
@@ -0,0 +1,130 @@
+#include <acpi/common/table_header.hpp>
+
+#include <kstd/cstring>
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+
+namespace acpi
+{
+
+ struct common_table_header_data
+ {
+ std::array<char, 4> signature;
+ std::uint32_t length;
+ std::uint8_t revision;
+ std::uint8_t checksum;
+ std::array<char, 6> oem_id;
+ std::array<char, 8> oem_table_id;
+ std::uint32_t oem_revision;
+ std::array<char, 4> creator_id;
+ std::uint32_t creator_revision;
+ };
+
+ static_assert(sizeof(common_table_header_data) == common_table_header_size);
+
+ auto table_header::creator_revision() const noexcept -> decltype(common_table_header_data::creator_revision)
+ {
+ using type = decltype(common_table_header_data::creator_revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, creator_revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::creator_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::creator_id);
+ constexpr auto offset = offsetof(common_table_header_data, creator_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::length() const noexcept -> kstd::units::bytes
+ {
+ using type = decltype(common_table_header_data::length);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, length);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto raw_value = type{};
+
+ kstd::libc::memcpy(&raw_value, data.data(), size);
+
+ return kstd::units::bytes{raw_value};
+ }
+
+ auto table_header::oem_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::oem_id);
+ constexpr auto offset = offsetof(common_table_header_data, oem_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::oem_table_id() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::oem_table_id);
+ constexpr auto offset = offsetof(common_table_header_data, oem_table_id);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+ auto table_header::oem_revision() const noexcept -> decltype(common_table_header_data::oem_revision)
+ {
+ using type = decltype(common_table_header_data::oem_revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, oem_revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::revision() const noexcept -> decltype(common_table_header_data::revision)
+ {
+ using type = decltype(common_table_header_data::revision);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(common_table_header_data, revision);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto table_header::signature() const noexcept -> std::string_view
+ {
+ constexpr auto size = sizeof(common_table_header_data::signature);
+ constexpr auto offset = offsetof(common_table_header_data, signature);
+
+ auto const base = reinterpret_cast<char const *>(m_data.data());
+
+ return std::string_view{base + offset, size};
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/common/table_header.hpp b/libs/acpi/acpi/common/table_header.hpp
new file mode 100644
index 0000000..471fed8
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.hpp
@@ -0,0 +1,56 @@
+#ifndef ACPI_COMMON_TABLE_HEADER_HPP
+#define ACPI_COMMON_TABLE_HEADER_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ //! The size of the common table header as defined in the ACPI specification.
+ constexpr auto common_table_header_size = 36;
+
+ //! The common header for all ACPI tables, except the FACS.
+ //!
+ //! Multibyte fields in the header are not guaranteed to be naturally aligned by the firmware. Therefore, no direct
+ //! access to multibyte fields is provided. Instead, the provided member functions handle alignment of the multibyte
+ //! values and thus provide a safe interface to the header fields.
+ struct table_header
+ {
+ //! Get the revision of the utility used to create this table.
+ [[nodiscard]] auto creator_revision() const noexcept -> std::uint32_t;
+
+ //! Get the vendor ID of the utility used to create this table.
+ [[nodiscard]] auto creator_id() const noexcept -> std::string_view;
+
+ //! Get the length of the entire table, including this header.
+ [[nodiscard]] auto length() const noexcept -> kstd::units::bytes;
+
+ //! Get the ID of the OEM.
+ [[nodiscard]] auto oem_id() const noexcept -> std::string_view;
+
+ //! Get the OEMs revision number of this table.
+ [[nodiscard]] auto oem_revision() const noexcept -> std::uint32_t;
+
+ //! Get the OEMs ID of this table.
+ [[nodiscard]] auto oem_table_id() const noexcept -> std::string_view;
+
+ //! Get the revision number of the structure of this table.
+ [[nodiscard]] auto revision() const noexcept -> std::uint8_t;
+
+ //! Get the signature of this table.
+ [[nodiscard]] auto signature() const noexcept -> std::string_view;
+
+ private:
+ std::array<std::byte, common_table_header_size> m_data;
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/common/table_header.test.cpp b/libs/acpi/acpi/common/table_header.test.cpp
new file mode 100644
index 0000000..ddc879e
--- /dev/null
+++ b/libs/acpi/acpi/common/table_header.test.cpp
@@ -0,0 +1,60 @@
+#include <acpi/common/table_header.hpp>
+
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Common table header parsing", "[common_table_header]")
+{
+ GIVEN("A valid compiled table header")
+ {
+ auto data = acpi::test_data::tables::table_header();
+
+ WHEN("parsing the header")
+ {
+ auto header = reinterpret_cast<acpi::table_header const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(header->signature() == "TEST");
+ }
+
+ THEN("the revision is correct")
+ {
+ REQUIRE(header->revision() == 1);
+ }
+
+ THEN("the length is correct")
+ {
+ REQUIRE(header->length() == kstd::type_size<acpi::table_header>);
+ }
+
+ THEN("the oem id is correct")
+ {
+ REQUIRE(header->oem_id() == "FEMO ");
+ }
+
+ THEN("the oem table id is correct")
+ {
+ REQUIRE(header->oem_table_id() == "HDRTEST ");
+ }
+
+ THEN("the oem revision is correct")
+ {
+ REQUIRE(header->oem_revision() == 1);
+ }
+
+ THEN("the creator id is correct")
+ {
+ REQUIRE(header->creator_id() == "INTL");
+ }
+
+ THEN("the creator revision is non-zero")
+ {
+ REQUIRE(header->creator_revision() != 0);
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/common/table_signature.hpp b/libs/acpi/acpi/common/table_signature.hpp
new file mode 100644
index 0000000..f0c2d7a
--- /dev/null
+++ b/libs/acpi/acpi/common/table_signature.hpp
@@ -0,0 +1,17 @@
+#ifndef ACPI_COMMON_TABLE_SIGNATURE_HPP
+#define ACPI_COMMON_TABLE_SIGNATURE_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+namespace acpi
+{
+
+ template<typename TableType>
+ struct table_signature;
+
+ template<typename TableType>
+ constexpr auto table_signature_v = table_signature<TableType>::value;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/common/table_type.hpp b/libs/acpi/acpi/common/table_type.hpp
new file mode 100644
index 0000000..bc427c7
--- /dev/null
+++ b/libs/acpi/acpi/common/table_type.hpp
@@ -0,0 +1,17 @@
+#ifndef ACPI_COMMON_TABLE_TYPE_HPP
+#define ACPI_COMMON_TABLE_TYPE_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+namespace acpi
+{
+
+ template<char const *>
+ struct table_type;
+
+ template<char const * Signature>
+ using table_type_t = typename table_type<Signature>::type;
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/common/vla_table.hpp b/libs/acpi/acpi/common/vla_table.hpp
new file mode 100644
index 0000000..d3f33a7
--- /dev/null
+++ b/libs/acpi/acpi/common/vla_table.hpp
@@ -0,0 +1,120 @@
+#ifndef ACPI_COMMON_VLA_TABLE_HPP
+#define ACPI_COMMON_VLA_TABLE_HPP
+
+#include <acpi/common/basic_table.hpp>
+
+#include <cstddef>
+#include <iterator>
+
+namespace acpi
+{
+
+ template<typename EntryType>
+ struct vla_table_iterator
+ {
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = EntryType;
+ using pointer = value_type *;
+ using reference = value_type &;
+ using difference_type = std::ptrdiff_t;
+
+ constexpr vla_table_iterator() noexcept = default;
+
+ constexpr vla_table_iterator(pointer entry, pointer limit) noexcept
+ : m_entry{entry}
+ , m_limit{limit}
+ {}
+
+ constexpr auto operator++() noexcept -> vla_table_iterator &
+ {
+ auto decayed = std::bit_cast<std::byte *>(m_entry);
+ decayed += m_entry->length();
+ m_entry = std::bit_cast<pointer>(decayed);
+ return *this;
+ }
+
+ constexpr auto operator++(int) noexcept -> vla_table_iterator
+ {
+ auto copy = *this;
+ ++*this;
+ return copy;
+ }
+
+ constexpr auto operator*() const noexcept -> reference
+ {
+ return *m_entry;
+ }
+
+ constexpr auto operator->() const noexcept -> pointer
+ {
+ return m_entry;
+ }
+
+ constexpr auto operator==(vla_table_iterator const & other) const noexcept -> bool
+ {
+ return m_entry == other.m_entry || (is_end() && other.is_end());
+ }
+
+ private:
+ [[nodiscard]] constexpr auto is_end() const noexcept -> bool
+ {
+ return m_entry == m_limit;
+ }
+
+ pointer m_entry{};
+ pointer m_limit{};
+ };
+
+ template<typename EntryType, typename TableType>
+ struct vla_table : basic_table<TableType>
+ {
+ using entry_type = EntryType;
+ using iterator = vla_table_iterator<EntryType>;
+ using const_iterator = vla_table_iterator<EntryType const>;
+
+ [[nodiscard]] auto begin() noexcept -> iterator
+ {
+ auto base = std::bit_cast<std::byte *>(this);
+ base += sizeof(TableType);
+ auto limit = std::bit_cast<std::byte *>(this);
+ limit += this->length().value;
+ return iterator{std::bit_cast<entry_type *>(base), std::bit_cast<entry_type *>(limit)};
+ }
+
+ [[nodiscard]] auto end() noexcept -> iterator
+ {
+ return iterator{};
+ }
+
+ [[nodiscard]] auto begin() const noexcept -> const_iterator
+ {
+ auto base = std::bit_cast<std::byte *>(this);
+ base += sizeof(TableType);
+ auto limit = std::bit_cast<std::byte *>(this);
+ limit += this->length().value;
+ return const_iterator{std::bit_cast<entry_type const *>(base), std::bit_cast<entry_type const *>(limit)};
+ }
+
+ [[nodiscard]] auto end() const noexcept -> const_iterator
+ {
+ return const_iterator{};
+ }
+
+ [[nodiscard]] auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ [[nodiscard]] auto cend() const noexcept -> const_iterator
+ {
+ return end();
+ }
+
+ private:
+ friend TableType;
+
+ constexpr vla_table() noexcept = default;
+ };
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/madt.cpp b/libs/acpi/acpi/data/madt.cpp
new file mode 100644
index 0000000..1a8b6d3
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.cpp
@@ -0,0 +1,112 @@
+#include <acpi/data/madt.hpp>
+
+#include <kstd/cstring>
+
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct madt_data
+ {
+ std::uint32_t local_interrupt_controller_address;
+ std::uint32_t flags;
+ };
+
+ static_assert(sizeof(madt_data) == 8);
+
+ auto madt::local_interrupt_controller_address() const noexcept -> std::uintptr_t
+ {
+ using type = decltype(madt_data::local_interrupt_controller_address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(madt_data, local_interrupt_controller_address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ auto madt::flags() const noexcept -> std::uint32_t
+ {
+ using type = decltype(madt_data::flags);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(madt_data, flags);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return value;
+ }
+
+ struct madt_entry_data
+ {
+ std::uint8_t type;
+ std::uint8_t length;
+ };
+
+ static_assert(sizeof(madt_entry_data) == 2);
+
+ auto madt_entry::type() const noexcept -> enum type
+ {
+ constexpr auto field_offset = offsetof(madt_entry_data, type);
+
+ return std::bit_cast<enum type>(*(m_data.data() + field_offset));
+ }
+
+ auto madt_entry::length() const noexcept -> std::size_t
+ {
+ constexpr auto field_offset = offsetof(madt_entry_data, length);
+
+ return static_cast<std::size_t>(*(m_data.data() + field_offset));
+ }
+
+ struct [[gnu::packed]] processor_local_apic_entry_data
+ {
+ std::uint8_t apic_id;
+ std::uint8_t processor_id;
+ std::uint32_t flags;
+ };
+
+ static_assert(sizeof(processor_local_apic_entry_data) == 6);
+
+ auto processor_local_apic_entry::id() const noexcept -> std::uint8_t
+ {
+ constexpr auto field_offset = offsetof(processor_local_apic_entry_data, apic_id);
+
+ return static_cast<std::uint8_t>(*(m_data.data() + field_offset));
+ }
+
+ auto processor_local_apic_entry::flags() const noexcept -> enum flags
+ {
+ using type = decltype(processor_local_apic_entry_data::flags);
+ static_assert(sizeof(type) == sizeof(enum flags));
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(processor_local_apic_entry_data, flags);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return static_cast<enum flags>(value);
+ }
+
+ auto processor_local_apic_entry::processor_id() const noexcept -> std::uint32_t
+ {
+ constexpr auto field_offset = offsetof(processor_local_apic_entry_data, processor_id);
+
+ return static_cast<std::uint32_t>(*(m_data.data() + field_offset));
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/data/madt.hpp b/libs/acpi/acpi/data/madt.hpp
new file mode 100644
index 0000000..b76daa4
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.hpp
@@ -0,0 +1,117 @@
+#ifndef ACPI_ACPI_MADT_HPP
+#define ACPI_ACPI_MADT_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <kstd/ext/bitfield_enum>
+#include <kstd/os/error.hpp>
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <ranges>
+#include <type_traits>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct madt>
+ {
+ constexpr char static const value[] = "APIC"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct madt>>
+ {
+ using type = struct madt;
+ };
+
+ struct madt_entry
+ {
+ //! The type of an MADT entry.
+ enum struct type : std::uint8_t
+ {
+ processor_local_apic,
+ io_apic,
+ io_apic_interrupt_source_override,
+ io_apic_nmi_source,
+ local_apic_nmi_interrupts,
+ local_apic_address_override,
+ processor_local_x2_apic,
+ };
+
+ //! Get the type of this entry.
+ [[nodiscard]] auto type() const noexcept -> enum type;
+
+ //! Get the length of this entry.
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ //! Cast this entry to the given concrete entry type.
+ //!
+ //! @warning This function will terminate execution if the desired target type does not match up with this entry's
+ //! actual type.
+ template<typename EntryType>
+ [[nodiscard]] auto as() const -> EntryType const &
+ {
+ if (type() != EntryType::this_type)
+ {
+ kstd::os::panic("Invalid cast");
+ }
+ return reinterpret_cast<EntryType const &>(*this);
+ }
+
+ private:
+ std::array<std::byte, 2> m_data{};
+ };
+
+ //! The Multiple APIC Description Table (MADT)
+ struct madt : vla_table<madt_entry, madt>
+ {
+ //! Get the physical address of the local interrupt controllers described by this entry.
+ [[nodiscard]] auto local_interrupt_controller_address() const noexcept -> std::uintptr_t;
+
+ [[nodiscard]] auto flags() const noexcept -> std::uint32_t;
+
+ template<typename EntryType>
+ [[nodiscard]] auto only() const noexcept
+ {
+ return *this | std::views::filter([](auto const & e) { return e.type() == EntryType::this_type; }) |
+ std::views::transform([](auto const & e) { return e.template as<EntryType>(); });
+ }
+
+ private:
+ std::array<std::byte, 8> m_data{};
+ };
+
+ struct processor_local_apic_entry : madt_entry
+ {
+ constexpr auto static this_type = type::processor_local_apic;
+
+ enum struct flags : std::uint32_t
+ {
+ processor_enabled = 1,
+ online_capable = 2,
+ };
+
+ [[nodiscard]] auto id() const noexcept -> std::uint8_t;
+ [[nodiscard]] auto flags() const noexcept -> flags;
+ [[nodiscard]] auto processor_id() const noexcept -> std::uint32_t;
+
+ private:
+ std::array<std::byte, 6> m_data{};
+ };
+
+} // namespace acpi
+
+template<>
+struct kstd::ext::is_bitfield_enum<enum acpi::processor_local_apic_entry::flags> : std::true_type
+{
+};
+
+#endif
diff --git a/libs/acpi/acpi/data/madt.test.cpp b/libs/acpi/acpi/data/madt.test.cpp
new file mode 100644
index 0000000..1b95a74
--- /dev/null
+++ b/libs/acpi/acpi/data/madt.test.cpp
@@ -0,0 +1,57 @@
+#include <acpi/data/madt.hpp>
+
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+
+SCENARIO("MADT parsing", "[madt]")
+{
+ GIVEN("The basic compiled MADT containing a single LAPIC entry and the default x86 LAPIC address")
+ {
+ auto data = acpi::test_data::tables::basic_madt();
+
+ WHEN("parsing the table")
+ {
+ auto madt = reinterpret_cast<acpi::madt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(madt->signature() == "APIC");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(madt->validate());
+ }
+
+ THEN("there is a single entry in the table")
+ {
+ REQUIRE(std::distance(madt->begin(), madt->end()) == 1);
+ }
+
+ THEN("the LAPIC address is 0xfee00000")
+ {
+ REQUIRE(madt->local_interrupt_controller_address() == 0xfee0'0000);
+ }
+
+ THEN("the length is sizeof(madt) + sizeof(processor_local_apic)")
+ {
+ REQUIRE(madt->length().value == sizeof(acpi::madt) + sizeof(acpi::processor_local_apic_entry));
+ }
+
+ THEN("the first entry has type processor_local_apic")
+ {
+ REQUIRE(madt->cbegin()->type() == acpi::madt_entry::type::processor_local_apic);
+ }
+
+ THEN("`only` can be used to get a view of all processor_local_apic entries")
+ {
+ REQUIRE(std::ranges::distance(madt->only<acpi::processor_local_apic_entry>()) == 1);
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/data/rsdt.cpp b/libs/acpi/acpi/data/rsdt.cpp
new file mode 100644
index 0000000..80e209d
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.cpp
@@ -0,0 +1,39 @@
+#include <acpi/data/rsdt.hpp>
+
+#include <acpi/common/table_header.hpp>
+
+#include <kstd/cstring>
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct rsdt_entry_data
+ {
+ std::uint32_t address;
+ };
+
+ [[nodiscard]] auto rsdt_entry::address() const noexcept -> table_header *
+ {
+ using type = decltype(rsdt_entry_data::address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(rsdt_entry_data, address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return reinterpret_cast<table_header *>(static_cast<uintptr_t>(value));
+ }
+
+ [[nodiscard]] auto rsdt_entry::length() const noexcept -> std::size_t
+ {
+ return sizeof(m_data);
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/data/rsdt.hpp b/libs/acpi/acpi/data/rsdt.hpp
new file mode 100644
index 0000000..a661187
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.hpp
@@ -0,0 +1,42 @@
+#ifndef ACPI_DATA_RSDT_HPP
+#define ACPI_DATA_RSDT_HPP
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct rsdt>
+ {
+ constexpr char static const value[] = "RSDT"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct rsdt>>
+ {
+ using type = struct rsdt;
+ };
+
+ struct rsdt_entry
+ {
+ [[nodiscard]] auto address() const noexcept -> table_header *;
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ private:
+ std::array<std::byte, 4> m_data{};
+ };
+
+ struct rsdt : vla_table<rsdt_entry, rsdt>
+ {
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/rsdt.test.cpp b/libs/acpi/acpi/data/rsdt.test.cpp
new file mode 100644
index 0000000..47992ce
--- /dev/null
+++ b/libs/acpi/acpi/data/rsdt.test.cpp
@@ -0,0 +1,48 @@
+#include <acpi/data/rsdt.hpp>
+
+#include <acpi/acpi.hpp>
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+
+SCENARIO("RSDT parsing", "[rsdt]")
+{
+ GIVEN("The basic compiled RSDT containing 8 table pointers")
+ {
+ auto data = acpi::test_data::tables::basic_rsdt();
+
+ WHEN("parsing the table")
+ {
+ auto rsdt = reinterpret_cast<acpi::rsdt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(rsdt->signature() == "RSDT");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(rsdt->validate());
+ }
+
+ THEN("there are 8 entries in the table")
+ {
+ REQUIRE(std::distance(rsdt->begin(), rsdt->end()) == 8);
+ }
+
+ THEN("the first entry has address 0x10")
+ {
+ REQUIRE(rsdt->cbegin()->address() == reinterpret_cast<acpi::table_header *>(0x10));
+ }
+
+ THEN("the length is sizeof(rsdt) + 8 * sizeof(rsdt_entry)")
+ {
+ REQUIRE(rsdt->length().value == sizeof(acpi::rsdt) + 8 * sizeof(acpi::rsdt_entry));
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/data/xsdt.cpp b/libs/acpi/acpi/data/xsdt.cpp
new file mode 100644
index 0000000..b77aeab
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.cpp
@@ -0,0 +1,39 @@
+#include <acpi/data/xsdt.hpp>
+
+#include <acpi/common/table_header.hpp>
+
+#include <kstd/cstring>
+
+#include <cstddef>
+#include <cstdint>
+#include <span>
+
+namespace acpi
+{
+
+ struct xsdt_entry_data
+ {
+ std::uint64_t address;
+ };
+
+ [[nodiscard]] auto xsdt_entry::address() const noexcept -> table_header *
+ {
+ using type = decltype(xsdt_entry_data::address);
+
+ constexpr auto size = sizeof(type);
+ constexpr auto offset = offsetof(xsdt_entry_data, address);
+
+ auto const data = std::span<std::byte const, size>{m_data.data() + offset, size};
+ auto value = type{};
+
+ kstd::libc::memcpy(&value, data.data(), size);
+
+ return reinterpret_cast<table_header *>(static_cast<uintptr_t>(value));
+ }
+
+ [[nodiscard]] auto xsdt_entry::length() const noexcept -> std::size_t
+ {
+ return sizeof(m_data);
+ }
+
+} // namespace acpi \ No newline at end of file
diff --git a/libs/acpi/acpi/data/xsdt.hpp b/libs/acpi/acpi/data/xsdt.hpp
new file mode 100644
index 0000000..965f23c
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.hpp
@@ -0,0 +1,42 @@
+#ifndef ACPI_DATA_XSDT_HPP
+#define ACPI_DATA_XSDT_HPP
+
+#include <acpi/common/table_header.hpp>
+#include <acpi/common/table_signature.hpp>
+#include <acpi/common/table_type.hpp>
+#include <acpi/common/vla_table.hpp>
+
+#include <array>
+#include <cstddef>
+
+namespace acpi
+{
+
+ template<>
+ struct table_signature<struct xsdt>
+ {
+ constexpr char static const value[] = "XSDT"; // NOLINT
+ };
+
+ template<>
+ struct table_type<table_signature_v<struct xsdt>>
+ {
+ using type = struct xsdt;
+ };
+
+ struct xsdt_entry
+ {
+ [[nodiscard]] auto address() const noexcept -> table_header *;
+ [[nodiscard]] auto length() const noexcept -> std::size_t;
+
+ private:
+ std::array<std::byte, 8> m_data{};
+ };
+
+ struct xsdt : vla_table<xsdt_entry, xsdt>
+ {
+ };
+
+} // namespace acpi
+
+#endif \ No newline at end of file
diff --git a/libs/acpi/acpi/data/xsdt.test.cpp b/libs/acpi/acpi/data/xsdt.test.cpp
new file mode 100644
index 0000000..77a5340
--- /dev/null
+++ b/libs/acpi/acpi/data/xsdt.test.cpp
@@ -0,0 +1,48 @@
+#include <acpi/data/xsdt.hpp>
+
+#include <acpi/acpi.hpp>
+#include <acpi/test_data/tables.hpp>
+
+#include <kstd/units>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+
+SCENARIO("XSDT parsing", "[xsdt]")
+{
+ GIVEN("The basic compiled XSDT containing 8 table pointers")
+ {
+ auto data = acpi::test_data::tables::basic_xsdt();
+
+ WHEN("parsing the table")
+ {
+ auto xsdt = reinterpret_cast<acpi::xsdt const *>(data.data());
+
+ THEN("the signature is correct")
+ {
+ REQUIRE(xsdt->signature() == "XSDT");
+ }
+
+ THEN("validate returns true")
+ {
+ REQUIRE(xsdt->validate());
+ }
+
+ THEN("there are 8 entries in the table")
+ {
+ REQUIRE(std::distance(xsdt->begin(), xsdt->end()) == 8);
+ }
+
+ THEN("the first entry has address 0x10")
+ {
+ REQUIRE(xsdt->cbegin()->address() == reinterpret_cast<acpi::table_header *>(0x10));
+ }
+
+ THEN("the length is sizeof(xsdt) + 8 * sizeof(xsdt_entry)")
+ {
+ REQUIRE(xsdt->length().value == sizeof(acpi::xsdt) + 8 * sizeof(acpi::xsdt_entry));
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/pointers.cpp b/libs/acpi/acpi/pointers.cpp
new file mode 100644
index 0000000..2ac8d31
--- /dev/null
+++ b/libs/acpi/acpi/pointers.cpp
@@ -0,0 +1,56 @@
+#include <acpi/pointers.hpp>
+
+#include <acpi/common/checksum.hpp>
+
+#include <kstd/units>
+
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ auto rsdp::oem_id() const noexcept -> std::string_view
+ {
+ return {m_oem_id.data(), m_oem_id.size()};
+ }
+
+ auto rsdp::revision() const noexcept -> std::uint8_t
+ {
+ return m_revision;
+ }
+
+ auto rsdp::signature() const noexcept -> std::string_view
+ {
+ return {m_signature.data(), m_signature.size()};
+ }
+
+ auto rsdp::table_address() const noexcept -> std::uintptr_t
+ {
+ auto raw = std::bit_cast<std::uint32_t>(m_rsdt_address);
+ return static_cast<std::uintptr_t>(raw);
+ }
+
+ auto rsdp::validate() const noexcept -> bool
+ {
+ return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), sizeof(rsdp)});
+ }
+
+ auto xsdp::length() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{m_length};
+ }
+
+ auto xsdp::table_address() const noexcept -> std::uintptr_t
+ {
+ return std::bit_cast<std::uintptr_t>(m_xsdt_address);
+ }
+
+ auto xsdp::validate() const noexcept -> bool
+ {
+ return signature() == "RSD PTR " && validate_checksum({reinterpret_cast<std::byte const *>(this), m_length});
+ }
+
+} // namespace acpi
diff --git a/libs/acpi/acpi/pointers.hpp b/libs/acpi/acpi/pointers.hpp
new file mode 100644
index 0000000..5771e7d
--- /dev/null
+++ b/libs/acpi/acpi/pointers.hpp
@@ -0,0 +1,72 @@
+#ifndef ACPI_POINTERS_HPP
+#define ACPI_POINTERS_HPP
+
+// IWYU pragma: private, include <acpi/acpi.hpp>
+
+#include <kstd/units>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace acpi
+{
+
+ //! A pointer to the Root System Description Table.
+ struct [[gnu::packed]] rsdp
+ {
+ //! Get the ID of the OEM, usually a vendor name.
+ [[nodiscard]] auto oem_id() const noexcept -> std::string_view;
+
+ //! Get the revision of the ACPI Root System Description Table.
+ //!
+ //! @note Revisions greater or equal to two indicate that the system uses the Extended System Description Table, and
+ //! as such that table should be use to query information.
+ [[nodiscard]] auto revision() const noexcept -> std::uint8_t;
+
+ //! Get the signature of this pointer.
+ //!
+ //! A valid RSDP must always carry the signature "RSD PTR ".
+ [[nodiscard]] auto signature() const noexcept -> std::string_view;
+
+ //! Get the physical address of the pointed-to Root System Description Table.
+ [[nodiscard]] auto table_address() const noexcept -> std::uintptr_t;
+
+ //! Validate the checksum of this RSDP.
+ //!
+ //! @return @p true iff. the checksum of this RSDP is valid, @p false otherwise.
+ [[nodiscard]] auto validate() const noexcept -> bool;
+
+ private:
+ std::array<char, 8> m_signature;
+ [[maybe_unused]] std::uint8_t m_checksum;
+ std::array<char, 6> m_oem_id;
+ std::uint8_t m_revision;
+ std::array<std::byte, 4> m_rsdt_address;
+ };
+
+ //! A pointer to the Extended System Description Table.
+ struct [[gnu::packed]] xsdp : rsdp
+ {
+ //! Get the length of the data contained in this pointer.
+ [[nodiscard]] auto length() const noexcept -> kstd::units::bytes;
+
+ //! Get the physical address of the pointed-to Extended System Description Table.
+ [[nodiscard]] auto table_address() const noexcept -> std::uintptr_t;
+
+ //! Validate the checksum of this XSDP.
+ //!
+ //! @return @p true iff. the checksum of this RSDP is valid, @p false otherwise.
+ [[nodiscard]] auto validate() const noexcept -> bool;
+
+ private:
+ std::uint32_t m_length;
+ std::array<std::byte, 8> m_xsdt_address;
+ [[maybe_unused]] std::uint8_t m_extended_checksum;
+ [[maybe_unused]] std::array<std::byte, 3> m_reserved;
+ };
+
+} // namespace acpi
+
+#endif
diff --git a/libs/acpi/acpi/pointers.test.cpp b/libs/acpi/acpi/pointers.test.cpp
new file mode 100644
index 0000000..d7b700d
--- /dev/null
+++ b/libs/acpi/acpi/pointers.test.cpp
@@ -0,0 +1,30 @@
+#include <acpi/pointers.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <bit>
+#include <cstddef>
+
+SCENARIO("ACPI root pointer parsing", "[acpi]")
+{
+ GIVEN("A null-filled pointer")
+ {
+ auto data = std::array<std::byte, sizeof(acpi::rsdp)>{};
+
+ WHEN("parsing the data")
+ {
+ auto rsdp = std::bit_cast<acpi::rsdp>(data);
+
+ THEN("the signature is invalid")
+ {
+ REQUIRE(rsdp.signature() != "RSD PTR ");
+ }
+
+ THEN("validate returns false")
+ {
+ REQUIRE_FALSE(rsdp.validate());
+ }
+ }
+ }
+}
diff --git a/libs/acpi/acpi/test_data/tables.S b/libs/acpi/acpi/test_data/tables.S
new file mode 100644
index 0000000..641db6a
--- /dev/null
+++ b/libs/acpi/acpi/test_data/tables.S
@@ -0,0 +1,17 @@
+.section .rodata
+
+.balign 16
+
+#define TABLE(name, file) \
+ .global name##_start; \
+ .global name##_end; \
+ name##_start: .incbin file; \
+ name##_end:
+
+TABLE(basic_madt, "basic_madt.aml")
+TABLE(basic_rsdt, "basic_rsdt.aml")
+TABLE(basic_rsdp, "basic_rsdp.aml")
+TABLE(basic_xsdt, "basic_xsdt.aml")
+TABLE(table_header, "table_header.aml")
+
+#undef TABLE
diff --git a/libs/acpi/acpi/test_data/tables.hpp b/libs/acpi/acpi/test_data/tables.hpp
new file mode 100644
index 0000000..e91f1a5
--- /dev/null
+++ b/libs/acpi/acpi/test_data/tables.hpp
@@ -0,0 +1,28 @@
+#ifndef ACPI_TEST_DATA_TABLES_HPP
+#define ACPI_TEST_DATA_TABLES_HPP
+
+#include <cstddef>
+#include <span>
+
+#define TABLE(name) \
+ extern "C" std::byte const name##_start; \
+ extern "C" std::byte const name##_end; \
+ auto inline name()->std::span<std::byte const> \
+ { \
+ return {&name##_start, &name##_end}; \
+ }
+
+namespace acpi::test_data::tables
+{
+
+ TABLE(basic_madt);
+ TABLE(basic_rsdt);
+ TABLE(basic_rsdp);
+ TABLE(basic_xsdt);
+ TABLE(table_header);
+
+} // namespace acpi::test_data::tables
+
+#undef TABLE
+
+#endif
diff --git a/libs/acpi/cmake/Scripts/IaslCompile.cmake b/libs/acpi/cmake/Scripts/IaslCompile.cmake
new file mode 100644
index 0000000..ff73b34
--- /dev/null
+++ b/libs/acpi/cmake/Scripts/IaslCompile.cmake
@@ -0,0 +1,16 @@
+execute_process(
+ COMMAND
+ "${IASL_EXE}"
+ "-vs"
+ "-p"
+ "${IASL_OUTPUT}"
+ "${IASL_INPUT}"
+ OUTPUT_VARIABLE IASL_OUT
+ ERROR_VARIABLE IASL_ERR
+ RESULT_VARIABLE IASL_RES
+)
+
+if(NOT IASL_RES EQUAL 0)
+ message(STATUS "${IASL_OUT}")
+ message(FATAL_ERROR "${IASL_ERR}")
+endif()
diff --git a/libs/acpi/test_data/basic_madt.asl b/libs/acpi/test_data/basic_madt.asl
new file mode 100644
index 0000000..cd6958a
--- /dev/null
+++ b/libs/acpi/test_data/basic_madt.asl
@@ -0,0 +1,29 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [APIC] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "APIC" [Multiple APIC Description Table (MADT)]
+[0004] Table Length : 00000000
+[0001] Revision : 07
+[0001] Checksum : 00
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "Template"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20230628
+
+[0004] Local Apic Address : FEE00000
+[0004] Flags (decoded below) : 00000001
+ PC-AT Compatibility : 1
+
+[0001] Subtable Type : 00 [Processor Local APIC]
+[0001] Length : 08
+[0001] Processor ID : 00
+[0001] Local Apic ID : 00
+[0004] Flags (decoded below) : 00000001
+ Processor Enabled : 1
+ Runtime Online Capable : 0
diff --git a/libs/acpi/test_data/basic_rsdp.asl b/libs/acpi/test_data/basic_rsdp.asl
new file mode 100644
index 0000000..8274c0f
--- /dev/null
+++ b/libs/acpi/test_data/basic_rsdp.asl
@@ -0,0 +1,16 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [RSDP] ACPI Table (AML byte code table)
+ */
+[0008] Signature : "RSD PTR "
+[0001] Checksum : 43
+[0006] Oem ID : "INTEL "
+[0001] Revision : 02
+[0004] RSDT Address : 00000000
+[0004] Length : 00000024
+[0008] XSDT Address : 0000000000000000
+[0001] Extended Checksum : DC
+[0003] Reserved : 000000
diff --git a/libs/acpi/test_data/basic_rsdt.asl b/libs/acpi/test_data/basic_rsdt.asl
new file mode 100644
index 0000000..6cf4c7a
--- /dev/null
+++ b/libs/acpi/test_data/basic_rsdt.asl
@@ -0,0 +1,26 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [RSDT] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "RSDT" [Root System Description Table]
+[0004] Table Length : 00000000
+[0001] Revision : 01
+[0001] Checksum : 00
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "TEMPLATE"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20100528
+
+[0004] ACPI Table Address 0 : 00000010
+[0004] ACPI Table Address 1 : 00000020
+[0004] ACPI Table Address 2 : 00000030
+[0004] ACPI Table Address 3 : 00000040
+[0004] ACPI Table Address 4 : 00000050
+[0004] ACPI Table Address 5 : 00000060
+[0004] ACPI Table Address 6 : 00000070
+[0004] ACPI Table Address 7 : 00000080
diff --git a/libs/acpi/test_data/basic_xsdt.asl b/libs/acpi/test_data/basic_xsdt.asl
new file mode 100644
index 0000000..d8589f9
--- /dev/null
+++ b/libs/acpi/test_data/basic_xsdt.asl
@@ -0,0 +1,26 @@
+/*
+ * Intel ACPI Component Architecture
+ * iASL Compiler/Disassembler version 20251212 (64-bit version)
+ * Copyright (c) 2000 - 2025 Intel Corporation
+ *
+ * Template for [XSDT] ACPI Table (static data table)
+ * Format: [ByteLength] FieldName : HexFieldValue
+ */
+[0004] Signature : "XSDT" [Extended System Description Table]
+[0004] Table Length : 00000064
+[0001] Revision : 01
+[0001] Checksum : 8B
+[0006] Oem ID : "INTEL "
+[0008] Oem Table ID : "TEMPLATE"
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 20100528
+
+[0008] ACPI Table Address 0 : 0000000000000010
+[0008] ACPI Table Address 1 : 0000000000000020
+[0008] ACPI Table Address 2 : 0000000000000030
+[0008] ACPI Table Address 3 : 0000000000000040
+[0008] ACPI Table Address 4 : 0000000000000050
+[0008] ACPI Table Address 5 : 0000000000000060
+[0008] ACPI Table Address 6 : 0000000000000070
+[0008] ACPI Table Address 7 : 0000000000000080
diff --git a/libs/acpi/test_data/table_header.asl b/libs/acpi/test_data/table_header.asl
new file mode 100644
index 0000000..8cddc03
--- /dev/null
+++ b/libs/acpi/test_data/table_header.asl
@@ -0,0 +1,9 @@
+[0004] Signature : "TEST"
+[0004] Table Length : 00000000
+[0001] Revision : 01
+[0001] Checksum : 00
+[0006] Oem ID : "FEMO "
+[0008] Oem Table ID : "HDRTEST "
+[0004] Oem Revision : 00000001
+[0004] Asl Compiler ID : "INTL"
+[0004] Asl Compiler Revision : 00000000
diff --git a/libs/elf/CMakeLists.txt b/libs/elf/CMakeLists.txt
index 66e59ee..1841132 100644
--- a/libs/elf/CMakeLists.txt
+++ b/libs/elf/CMakeLists.txt
@@ -1,17 +1,35 @@
+cmake_minimum_required(VERSION "3.27.0")
+
+project("elf"
+ DESCRIPTION "ELF file format library"
+ VERSION "0.0.1"
+ LANGUAGES CXX
+)
+
+include("CTest")
+
+#[============================================================================[
+# Library
+#]============================================================================]
+
add_library("elf" INTERFACE)
-add_library("libs::elf" ALIAS "elf")
+add_library("elf::lib" ALIAS "elf")
+
+file(GLOB_RECURSE ELF_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "elf/**.hpp"
+)
target_sources("elf" INTERFACE
FILE_SET HEADERS
- BASE_DIRS "include"
+ BASE_DIRS "elf"
FILES
- "include/elf/format.hpp"
- "include/elf/section_header.hpp"
-
+ ${ELF_HEADERS}
)
target_include_directories("elf" INTERFACE
- "include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
)
set_target_properties("elf" PROPERTIES
diff --git a/libs/elf/include/elf/format.hpp b/libs/elf/elf/format.hpp
index bc85101..bc85101 100644
--- a/libs/elf/include/elf/format.hpp
+++ b/libs/elf/elf/format.hpp
diff --git a/libs/elf/include/elf/section_header.hpp b/libs/elf/elf/section_header.hpp
index 2b907cb..b1305ec 100644
--- a/libs/elf/include/elf/section_header.hpp
+++ b/libs/elf/elf/section_header.hpp
@@ -1,7 +1,7 @@
#ifndef ELF_SECTION_HEADER_HPP
#define ELF_SECTION_HEADER_HPP
-#include "format.hpp"
+#include <elf/format.hpp>
#include <cstddef>
#include <cstdint>
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt
index 1f140f6..0f64761 100644
--- a/libs/kstd/CMakeLists.txt
+++ b/libs/kstd/CMakeLists.txt
@@ -1,49 +1,100 @@
-add_library("kstd" STATIC)
-add_library("libs::kstd" ALIAS "kstd")
+cmake_minimum_required(VERSION "3.27.0")
-set(KSTD_LIBC_SYMBOLS
- "abort"
- "strlen"
- "memcmp"
+project("kstd"
+ DESCRIPTION "A kernel STL implementation"
+ VERSION "0.0.1"
+ LANGUAGES CXX
)
-target_sources("kstd" PRIVATE
- "src/libc/stdlib.cpp"
- "src/libc/string.cpp"
+include("CTest")
+
+#[============================================================================[
+# Library
+#]============================================================================]
+
+add_library("kstd" STATIC)
+add_library("kstd::lib" ALIAS "kstd")
- "src/os/error.cpp"
+target_sources("kstd" PRIVATE
+ "kstd/os/error.cpp"
+ "kstd/mutex.cpp"
+ "kstd/vformat.cpp"
+)
- "src/mutex.cpp"
+file(GLOB_RECURSE KSTD_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "kstd/*"
)
+list(FILTER KSTD_HEADERS EXCLUDE REGEX ".*\.cpp")
+
target_sources("kstd" PUBLIC
FILE_SET HEADERS
- BASE_DIRS "include"
+ BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}"
FILES
- "include/kstd/bits/format_context.hpp"
- "include/kstd/bits/format_specs.hpp"
- "include/kstd/bits/format_string.hpp"
- "include/kstd/bits/formatter.hpp"
- "include/kstd/bits/shared_ptr.hpp"
- "include/kstd/bits/unique_ptr.hpp"
-
- "include/kstd/ext/bitfield_enum"
-
- "include/kstd/os/error.hpp"
- "include/kstd/os/print.hpp"
-
- "include/kstd/asm_ptr"
- "include/kstd/format"
- "include/kstd/memory"
- "include/kstd/mutex"
- "include/kstd/stack"
- "include/kstd/vector"
+ ${KSTD_HEADERS}
)
target_include_directories("kstd" PUBLIC
- "include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+)
+
+set_target_properties("kstd" PROPERTIES
+ VERIFY_INTERFACE_HEADER_SETS YES
)
-list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=")
+if(NOT BUILD_TESTING)
+ target_sources("kstd" PRIVATE
+ "kstd/libc/stdlib.cpp"
+ "kstd/libc/string.cpp"
+ )
+
+ set(KSTD_LIBC_SYMBOLS
+ "abort"
+ "strlen"
+ "memcmp"
+ "memcpy"
+ )
+
+ list(TRANSFORM KSTD_LIBC_SYMBOLS PREPEND "-Wl,--undefined=")
+
+ target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS})
+endif()
+
+#[============================================================================[
+# Tests
+#]============================================================================]
+
+if(BUILD_TESTING)
+ find_package("Catch2")
+ include("Catch")
+
+ add_executable("kstd_tests")
+ add_executable("kstd::tests" ALIAS "kstd_tests")
+
+ target_sources("kstd_tests" PRIVATE
+ "kstd/flat_map.test.cpp"
+ "kstd/format.test.cpp"
+ "kstd/vector.test.cpp"
+ "kstd/bits/observer_ptr.test.cpp"
+ "kstd/test_support/os_panic.test.cpp"
+ "kstd/string.test.cpp"
+ )
+
+ target_link_libraries("kstd_tests" PRIVATE
+ "Catch2::Catch2WithMain"
+ "kstd::lib"
+ )
+
+ set_target_properties("kstd_tests" PROPERTIES
+ EXCLUDE_FROM_ALL NO
+ )
+
+ if(COMMAND "enable_coverage")
+ enable_coverage("kstd")
+ enable_coverage("kstd_tests")
+ endif()
-target_link_options("kstd" INTERFACE ${KSTD_LIBC_SYMBOLS}) \ No newline at end of file
+ catch_discover_tests("kstd::tests" ${CATCH_TEST_ARGS})
+endif() \ No newline at end of file
diff --git a/libs/kstd/gdb/__init__.py b/libs/kstd/gdb/__init__.py
new file mode 100644
index 0000000..c5d1e53
--- /dev/null
+++ b/libs/kstd/gdb/__init__.py
@@ -0,0 +1,23 @@
+import gdb.printing
+
+from .vector import KstdVectorPrinter
+from .string import KstdStringPrinter
+from .smart_pointers import (
+ KstdUniquePtrPrinter,
+ KstdSharedPtrPrinter,
+ KstdObserverPtrPrinter,
+)
+
+
+def build_pretty_printers():
+ pp = gdb.printing.RegexpCollectionPrettyPrinter("kstd")
+ pp.add_printer("vector", "^kstd::vector<.*>$", KstdVectorPrinter)
+ pp.add_printer("string", "^kstd::string$", KstdStringPrinter)
+ pp.add_printer("unique_ptr", "^kstd::unique_ptr<.*>$", KstdUniquePtrPrinter)
+ pp.add_printer("shared_ptr", "^kstd::shared_ptr<.*>$", KstdSharedPtrPrinter)
+ pp.add_printer("observer_ptr", "^kstd::observer_ptr<.*>$", KstdObserverPtrPrinter)
+ return pp
+
+
+def register_printers(objfile):
+ gdb.printing.register_pretty_printer(objfile, build_pretty_printers(), replace=True)
diff --git a/libs/kstd/gdb/smart_pointers.py b/libs/kstd/gdb/smart_pointers.py
new file mode 100644
index 0000000..f6e8a45
--- /dev/null
+++ b/libs/kstd/gdb/smart_pointers.py
@@ -0,0 +1,68 @@
+import gdb
+
+
+class KstdUniquePtrPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+
+ def to_string(self):
+ pointer = self.__val["pointer"]
+ if int(pointer) == 0:
+ return f"kstd::unique_ptr<{self.__type}> (empty)"
+ return f"kstd::unique_ptr<{self.__type}>"
+
+ def children(self):
+ pointer = self.__val["pointer"]
+ if int(pointer) != 0:
+ yield ("get()", pointer.dereference())
+
+ def display_hint(self):
+ return None
+
+
+class KstdSharedPtrPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+
+ def to_string(self):
+ pointer = self.__val["pointer"]
+ control_block = self.__val["control"]
+
+ if int(pointer) == 0 or int(control_block) == 0:
+ return f"shared_ptr<{self.__type}> (empty)"
+
+ strong_refs = int(control_block["shared_count"]["_M_i"])
+ weak_refs = int(control_block["weak_count"]["_M_i"])
+
+ return (
+ f"shared_ptr<{self.__type}> use count {strong_refs}, weak count {weak_refs}"
+ )
+
+ def children(self):
+ pointer = self.__val["pointer"]
+ control_block = self.__val["control"]
+
+ if int(pointer) != 0:
+ yield ("get()", pointer.dereference())
+
+ def display_hint(self):
+ return None
+
+
+class KstdObserverPtrPrinter(gdb.ValuePrinter):
+ def __init__(self, val):
+ self.__val = val
+ self.__type = val.type.template_argument(0)
+ self.__pointer = val["m_ptr"]
+
+ def to_string(self):
+ return f"{(self.__pointer)}"
+
+ def children(self):
+ if int(self.__pointer) != 0:
+ yield ("get()", self.__pointer.dereference())
+
+ def display_hint(self):
+ return None
diff --git a/libs/kstd/gdb/string.py b/libs/kstd/gdb/string.py
new file mode 100644
index 0000000..73c22d6
--- /dev/null
+++ b/libs/kstd/gdb/string.py
@@ -0,0 +1,27 @@
+import gdb
+
+
+class KstdStringPrinter(gdb.ValuePrinter):
+
+ def __init__(self, val):
+ self.__val = val
+
+ def to_string(self):
+ storage = self.__val["m_storage"]
+ storage_size = int(storage["m_size"])
+
+ if storage_size <= 0:
+ return '""'
+
+ data_pointer = storage["m_data"]
+ string_length = storage_size - 1
+
+ try:
+ if hasattr(data_pointer, "lazy_string"):
+ return data_pointer.lazy_string(encoding="utf-8", length=string_length)
+ return data_pointer.string(encoding="utf-8", length=string_length)
+ except gdb.error:
+ return "<unreadable memory>"
+
+ def display_hint(self):
+ return "string"
diff --git a/libs/kstd/gdb/units.py b/libs/kstd/gdb/units.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/kstd/gdb/units.py
diff --git a/libs/kstd/gdb/vector.py b/libs/kstd/gdb/vector.py
new file mode 100644
index 0000000..f11e064
--- /dev/null
+++ b/libs/kstd/gdb/vector.py
@@ -0,0 +1,41 @@
+import gdb
+
+
+class KstdVectorPrinter(gdb.ValuePrinter):
+ class Iterator:
+ def __init__(self, begin: gdb.Value, end: gdb.Value):
+ self._item = begin
+ self._end = end
+ self._count = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ count = self._count
+ self._count = count + 1
+
+ if self._item == self._end:
+ raise StopIteration
+
+ element = self._item.dereference()
+ self._item = self._item + 1
+ return (f"[{count}]", element)
+
+ def __init__(self, val: gdb.Value):
+ self.__val = val
+ self.__size = int(val["m_size"])
+ self.__capacity = int(val["m_capacity"])
+ self.__data = val["m_data"]
+
+ def to_string(self):
+ return f"vector of length {self.__size}, capacity {self.__capacity}"
+
+ def children(self):
+ return self.Iterator(self.__data, self.__data + self.__size)
+
+ def display_hint(self):
+ return "array"
+
+ def num_children(self):
+ return self.__size
diff --git a/libs/kstd/include/kstd/bits/format_context.hpp b/libs/kstd/include/kstd/bits/format_context.hpp
deleted file mode 100644
index b5c7d21..0000000
--- a/libs/kstd/include/kstd/bits/format_context.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP
-#define KSTD_BITS_FORMAT_CONTEXT_HPP
-
-// IWYU pragma: private, include <kstd/format>
-
-#include <string_view>
-
-namespace kstd
-{
-
- struct format_context
- {
- using writer_function = void(void *, std::string_view);
-
- writer_function * writer;
- void * user_data;
-
- constexpr auto push(std::string_view string) -> void
- {
- writer(user_data, string);
- }
-
- constexpr auto push(char character) -> void
- {
- writer(user_data, std::string_view(&character, 1));
- }
- };
-
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format_specs.hpp b/libs/kstd/include/kstd/bits/format_specs.hpp
deleted file mode 100644
index 092a875..0000000
--- a/libs/kstd/include/kstd/bits/format_specs.hpp
+++ /dev/null
@@ -1,104 +0,0 @@
-#ifndef KSTD_BITS_FORMAT_SPECS_HPP
-#define KSTD_BITS_FORMAT_SPECS_HPP
-
-// IWYU pragma: private
-
-#include <cstddef>
-#include <iterator>
-#include <string_view>
-
-namespace kstd::bits
-{
-
- struct format_specs
- {
- std::size_t width{};
- char fill{' '};
- char type{};
- bool align_left{};
- bool sign_plus{};
- bool sign_space{};
- bool alternative_form{};
- bool zero_pad{};
- };
-
- constexpr auto parse_specs(std::string_view string, format_specs & specs) -> std::string_view
- {
- auto current = string.begin();
- auto end = string.end();
-
- if (current == end || *current != ':')
- {
- return {current, end};
- }
-
- std::advance(current, 1);
-
- if (current != end && std::next(current) != end && (*std::next(current) == '<' || *std::next(current) == '>'))
- {
- specs.fill = *current;
- specs.align_left = *std::next(current) == '<';
- std::advance(current, 2);
- }
- else if (current != end)
- {
- if (*current == '<')
- {
- specs.align_left = true;
- std::advance(current, 1);
- }
- else if (*current == '>')
- {
- specs.align_left = false;
- std::advance(current, 1);
- }
- }
-
- if (current != end)
- {
- if (*current == '+')
- {
- specs.sign_plus = true;
- std::advance(current, 1);
- }
- else if (*current == ' ')
- {
- specs.sign_space = true;
- std::advance(current, 1);
- }
- else if (*current == '-')
- {
- std::advance(current, 1);
- }
- }
-
- if (current != end && *current == '#')
- {
- specs.alternative_form = true;
- std::advance(current, 1);
- }
-
- if (current != end && *current == '0')
- {
- specs.zero_pad = true;
- std::advance(current, 1);
- }
-
- while (current != end && *current >= '0' && *current <= '9')
- {
- specs.width = specs.width * 10 + (*current - '0');
- std::advance(current, 1);
- }
-
- if (current != end && *current != '}')
- {
- specs.type = *current;
- std::advance(current, 1);
- }
-
- return {current, end};
- }
-
-} // namespace kstd::bits
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/format_string.hpp b/libs/kstd/include/kstd/bits/format_string.hpp
deleted file mode 100644
index 3a15bf0..0000000
--- a/libs/kstd/include/kstd/bits/format_string.hpp
+++ /dev/null
@@ -1,131 +0,0 @@
-#ifndef KSTD_BITS_FORMAT_STRING_HPP
-#define KSTD_BITS_FORMAT_STRING_HPP
-
-// IWYU pragma: private, include <kstd/format>
-
-#include <algorithm>
-#include <cstddef>
-#include <iterator>
-#include <string_view>
-
-namespace kstd
-{
-
- namespace bits
- {
- auto invalid_format_string(char const *) -> void;
-
- consteval auto validate_format_string(std::string_view string, std::size_t argument_count) -> void
- {
- auto next_automatic_index = 0uz;
- auto placeholder_count = 0uz;
- auto has_manual_index = false;
- auto has_automatic_index = false;
-
- auto current = string.begin();
- auto end = string.end();
-
- while (current != end)
- {
- if (*current == '{')
- {
- if (std::next(current) != end && *std::next(current) == '{')
- {
- std::advance(current, 2);
- continue;
- }
-
- std::advance(current, 1);
-
- auto index = 0uz;
- auto is_manual_index = false;
-
- if (current != end && *current >= '0' && *current <= '9')
- {
- is_manual_index = true;
- while (current != end && *current >= '0' && *current <= '9')
- {
- index = index * 10 + (*current - '0');
- std::advance(current, 1);
- }
- }
-
- if (is_manual_index)
- {
- placeholder_count = std::max(placeholder_count, index + 1);
- if (has_automatic_index)
- {
- invalid_format_string("Cannot mix automatic and manual indexing.");
- }
- has_manual_index = true;
- if (index >= argument_count)
- {
- invalid_format_string("Argument index out of range");
- }
- }
- else
- {
- if (has_manual_index)
- {
- invalid_format_string("Cannot mix automatic and manual indexing.");
- }
- has_automatic_index = true;
- ++placeholder_count;
- if (next_automatic_index >= argument_count)
- {
- invalid_format_string("Not enough arguments provided for format string.");
- }
- index = next_automatic_index++;
- }
-
- while (current != end && *current != '}')
- {
- std::advance(current, 1);
- }
-
- if (current == end)
- {
- invalid_format_string("Unexpected end of format string.");
- }
- std::advance(current, 1);
- }
- else if (*current == '}')
- {
- if (std::next(current) != end && *std::next(current) == '}')
- {
- std::advance(current, 2);
- continue;
- }
- invalid_format_string("Unexpected '}' in format string.");
- }
- else
- {
- std::advance(current, 1);
- }
- }
- if (argument_count < placeholder_count)
- {
- invalid_format_string("Not enough arguments provided for format string.");
- }
- else if (argument_count > placeholder_count)
- {
- invalid_format_string("Too many arguments provided for format string.");
- }
- }
- } // namespace bits
-
- template<typename... Args>
- struct format_string
- {
- std::string_view str;
-
- consteval format_string(char const * format)
- : str{format}
- {
- bits::validate_format_string(str, sizeof...(Args));
- }
- };
-
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/formatter.hpp b/libs/kstd/include/kstd/bits/formatter.hpp
deleted file mode 100644
index eadc0ef..0000000
--- a/libs/kstd/include/kstd/bits/formatter.hpp
+++ /dev/null
@@ -1,300 +0,0 @@
-#ifndef KSTD_BITS_FORMATTER_HPP
-#define KSTD_BITS_FORMATTER_HPP
-
-// IWYU pragma: private, include <kstd/format>
-
-#include <kstd/bits/format_context.hpp>
-#include <kstd/bits/format_specs.hpp>
-
-#include <array>
-#include <bit>
-#include <concepts>
-#include <cstddef>
-#include <cstdint>
-#include <iterator>
-#include <string_view>
-#include <type_traits>
-#include <utility>
-
-namespace kstd
-{
-
- template<typename T>
- struct formatter;
-
- template<std::integral T>
- struct formatter<T>
- {
- bits::format_specs specs{};
-
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- return bits::parse_specs(context, specs);
- }
-
- auto format(T value, format_context & context) const -> void
- {
- enum struct base
- {
- bin = 2,
- oct = 8,
- dec = 10,
- hex = 16,
- };
-
- constexpr auto static maximum_digits = 80;
-
- using unsigned_T = std::make_unsigned_t<T>;
- auto absolute_value = static_cast<unsigned_T>(value);
- auto is_negative = false;
-
- if constexpr (std::is_signed_v<T>)
- {
- if (value < 0)
- {
- is_negative = true;
- absolute_value = 0 - value;
- }
- }
-
- auto const base = [type = specs.type] -> auto {
- switch (type)
- {
- case 'x':
- case 'X':
- case 'p':
- return base::hex;
- case 'b':
- case 'B':
- return base::bin;
- case 'o':
- return base::oct;
- default:
- return base::dec;
- }
- }();
-
- auto buffer = std::array<char, maximum_digits>{};
- auto digits = (specs.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
- auto current = buffer.rbegin();
-
- if (absolute_value == 0)
- {
- *current = '0';
- std::advance(current, 1);
- }
- else
- {
- while (absolute_value != 0)
- {
- *current = digits[absolute_value % std::to_underlying(base)];
- std::advance(current, 1);
- absolute_value /= std::to_underlying(base);
- }
- }
-
- auto prefix = std::array<char, 2>{'0', '\0'};
- auto prefix_length = 0uz;
- if (specs.alternative_form && value != 0)
- {
- switch (base)
- {
- case base::bin:
- prefix[1] = specs.type == 'B' ? 'B' : 'b';
- prefix_length = 2;
- break;
- case base::oct:
- prefix_length = 1;
- break;
- case base::hex:
- prefix[1] = specs.type == 'X' ? 'X' : 'x';
- prefix_length = 2;
- break;
- default:
- break;
- }
- }
-
- auto sign_character = '\0';
- if (is_negative)
- {
- sign_character = '-';
- }
- else if (specs.sign_plus)
- {
- sign_character = '+';
- }
- else if (specs.sign_space)
- {
- sign_character = ' ';
- }
-
- auto const content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current));
- auto const total_length = content_length + prefix_length + (sign_character != '\0');
- auto const padding_length = (specs.width > total_length) ? (specs.width - total_length) : 0;
-
- if (!specs.align_left && !specs.zero_pad)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
-
- if (sign_character)
- {
- context.push(sign_character);
- }
-
- if (prefix_length)
- {
- context.push({prefix.data(), prefix_length});
- }
-
- if (!specs.align_left && specs.zero_pad)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push('0');
- }
- }
-
- context.push({current.base(), content_length});
-
- if (specs.align_left)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
- }
- };
-
- template<typename T>
- struct formatter<T const *> : formatter<std::uintptr_t>
- {
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- auto result = formatter<std::uintptr_t>::parse(context);
- if (!this->specs.type)
- {
- this->specs.type = 'p';
- this->specs.alternative_form = true;
- }
- return result;
- }
-
- auto format(T const * pointer, format_context & context) const -> void
- {
- formatter<std::uintptr_t>::format(std::bit_cast<std::uintptr_t>(pointer), context);
- }
- };
-
- template<typename T>
- struct formatter<T *> : formatter<T const *>
- {
- };
-
- template<>
- struct formatter<std::string_view>
- {
- bits::format_specs specs{};
-
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- return bits::parse_specs(context, specs);
- }
-
- auto format(std::string_view string, format_context & context) const -> void
- {
- auto const content_length = string.size();
- auto const padding_length = (specs.width > content_length) ? (specs.width - content_length) : 0;
-
- if (!specs.align_left)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
-
- context.push(string);
-
- if (specs.align_left)
- {
- for (auto i = 0uz; i < padding_length; ++i)
- {
- context.push(specs.fill);
- }
- }
- }
- };
-
- template<>
- struct formatter<char const *>
- {
- bits::format_specs specs{};
-
- constexpr auto parse(std::string_view context) -> std::string_view
- {
- return bits::parse_specs(context, specs);
- }
-
- auto format(char const * string, format_context & context) const -> void
- {
- if (string)
- {
- formatter<std::string_view>{specs}.format(string, context);
- }
- else
- {
- formatter<std::string_view>{specs}.format("(null)", context);
- }
- }
- };
-
- template<>
- struct formatter<char *> : formatter<char const *>
- {
- };
-
- struct format_arg
- {
- using formatting_function = std::string_view(void const *, std::string_view, format_context &);
-
- void const * value;
- formatting_function * format;
- };
-
- struct format_args
- {
- constexpr format_args(format_arg const * args, std::size_t number_of_args)
- : m_args(args)
- , m_number_of_args(number_of_args)
- {}
-
- [[nodiscard]] constexpr auto get(std::size_t index) const -> format_arg
- {
- if (index >= m_number_of_args)
- return {.value = nullptr, .format = nullptr};
- return m_args[index];
- }
-
- private:
- format_arg const * m_args;
- std::size_t m_number_of_args;
- };
-
- template<typename T>
- auto format_dispatcher(void const * value, std::string_view format_spec, format_context & context) -> std::string_view
- {
- auto formatter_for_T = formatter<T>{};
- auto const remainder = formatter_for_T.parse(format_spec);
- formatter_for_T.format(*static_cast<T const *>(value), context);
- return remainder;
- }
-
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/shared_ptr.hpp b/libs/kstd/include/kstd/bits/shared_ptr.hpp
deleted file mode 100644
index cfe5d18..0000000
--- a/libs/kstd/include/kstd/bits/shared_ptr.hpp
+++ /dev/null
@@ -1,258 +0,0 @@
-#ifndef KSTD_BITS_SHARED_PTR_HPP
-#define KSTD_BITS_SHARED_PTR_HPP
-
-#include <atomic>
-#include <cstddef>
-#include <utility>
-
-// IWYU pragma: private, include <kstd/memory>
-
-namespace kstd
-{
- /**
- * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several
- * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of
- * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining
- * shared_ptr owning the object is assigned another pointer via operator= or reset(). A
- * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used
- * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(),
- * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count
- * reaches zero.
- *
- * @tparam T The type of the managed object.
- */
- template<typename T>
- struct shared_ptr
- {
- /**
- * @brief Constructor.
- *
- * @param pointer A pointer to an object to manage (default is nullptr).
- */
- explicit shared_ptr(T * pointer = nullptr)
- : pointer(pointer)
- , ref_count(new std::atomic<std::size_t>(pointer != nullptr ? 1 : 0))
- {
- // Nothing to do.
- }
-
- /**
- * @brief Copy constructor.
- *
- * @param other The shared_ptr to copy from.
- */
- shared_ptr(shared_ptr const & other)
- : pointer(other.pointer)
- , ref_count(other.ref_count)
- {
- if (pointer != nullptr)
- {
- ++(*ref_count);
- }
- }
-
- /**
- * @brief Move constructor.
- *
- * @param other The shared_ptr to move from.
- */
- shared_ptr(shared_ptr && other) noexcept
- : pointer(other.pointer)
- , ref_count(other.ref_count)
- {
- other.pointer = nullptr;
- other.ref_count = nullptr;
- }
-
- /**
- * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the
- * object managed by r. If r manages no object, *this manages no object too. Equivalent to
- * shared_ptr<T>(r).swap(*this).
- *
- * @param other Another smart pointer to share the ownership with.
- * @return Reference to this shared pointer.
- */
- auto operator=(shared_ptr const & other) -> shared_ptr &
- {
- if (this != &other)
- {
- cleanup();
- pointer = other.pointer;
- ref_count = other.ref_count;
-
- if (pointer != nullptr)
- {
- ++(*ref_count);
- }
- }
-
- return *this;
- }
-
- /**
- * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of
- * the previous state of r, and r is empty. Equivalent to shared_ptr<T>(std::move(r)).swap(*this).
- *
- * @param other Another smart pointer to acquire the ownership from.
- * @return Reference to this shared pointer.
- */
- auto operator=(shared_ptr && other) noexcept -> shared_ptr &
- {
- if (this != &other)
- {
- cleanup();
- pointer = other.pointer;
- ref_count = other.ref_count;
- other.pointer = nullptr;
- other.ref_count = nullptr;
- }
-
- return *this;
- }
-
- /**
- * @brief Destructor. Cleans up resources if necessary.
- */
- ~shared_ptr()
- {
- cleanup();
- }
-
- /**
- * @brief Replaces the managed object.
- *
- * @param ptr Pointer to a new object to manage (default = nullptr).
- */
- void reset(T * ptr = nullptr)
- {
- cleanup();
- pointer = ptr;
- ref_count = new std::atomic<std::size_t>(ptr != nullptr ? 1 : 0);
- }
-
- /**
- * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not
- * adjusted.
- *
- * @param other The shared_ptr to swap with.
- */
- void swap(shared_ptr & other)
- {
- std::swap(pointer, other.pointer);
- std::swap(ref_count, other.ref_count);
- }
-
- /**
- * @brief Dereference operator. If get() is a null pointer, the behavior is undefined.
- *
- * @return Returns the object owned by *this, equivalent to *get().
- */
- auto operator*() const -> T &
- {
- return *pointer;
- }
-
- /**
- * @brief Member access operator.
- *
- * @return Returns a pointer to the object owned by *this, i.e. get().
- */
- auto operator->() const -> T *
- {
- return pointer;
- }
-
- /**
- * @brief Returns a pointer to the managed object or nullptr if no object is owned.
- *
- * @return Pointer to the managed object or nullptr if no object is owned.
- */
- auto get() const -> T *
- {
- return pointer;
- }
-
- /**
- * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If
- * there is no managed object, ​0​ is returned.
- *
- * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty
- * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1,
- * there are no other owners.
- *
- * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed
- * object.
- */
- [[nodiscard]] auto use_count() const -> std::size_t
- {
- if (pointer != nullptr)
- {
- return *ref_count;
- }
-
- return 0;
- }
-
- /**
- * @brief Checks whether *this owns an object, i.e. whether get() != nullptr.
- *
- * @return true if *this owns an object, false otherwise.
- */
- explicit operator bool() const
- {
- return pointer != nullptr;
- }
-
- /**
- * @brief Defaulted three-way comparator operator.
- */
- auto operator<=>(shared_ptr const & other) const = default;
-
- private:
- /**
- * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object.
- */
- auto cleanup() -> void
- {
- if (pointer != nullptr && ref_count != nullptr && --(*ref_count) == 0)
- {
- delete pointer;
- delete ref_count;
- }
- }
-
- T * pointer; ///< The managed object.
- std::atomic<std::size_t> * ref_count; ///< Reference count.
- };
-
- /**
- * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls
- * lhs.swap(rhs).
- *
- * @tparam T Type of the managed object.
- * @param lhs, rhs Smart pointers whose contents to swap.
- */
- template<typename T>
- auto swap(shared_ptr<T> & lhs, shared_ptr<T> & rhs) -> void
- {
- lhs.swap(rhs);
- }
-
- /**
- * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The
- * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is
- * not an array type. The function is equivalent to: shared_ptr<T>(new T(std::forward<Args>(args)...)).
- *
- * @tparam T Type of the managed object.
- * @tparam Args Argument types for T's constructor.
- * @param args List of arguments with which an instance of T will be constructed.
- * @returns Shared_pointer of an instance of type T.
- */
- template<typename T, typename... Args>
- auto make_shared(Args &&... args) -> shared_ptr<T>
- {
- return shared_ptr<T>(new T(std::forward<Args>(args)...));
- }
-} // namespace kstd
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/format b/libs/kstd/include/kstd/format
deleted file mode 100644
index 0a1bcd1..0000000
--- a/libs/kstd/include/kstd/format
+++ /dev/null
@@ -1,8 +0,0 @@
-#ifndef KSTD_FORMAT_HPP
-#define KSTD_FORMAT_HPP
-
-#include "bits/format_context.hpp" // IWYU pragma: export
-#include "bits/format_string.hpp" // IWYU pragma: export
-#include "bits/formatter.hpp" // IWYU pragma: export
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/memory b/libs/kstd/include/kstd/memory
deleted file mode 100644
index cab2fba..0000000
--- a/libs/kstd/include/kstd/memory
+++ /dev/null
@@ -1,7 +0,0 @@
-#ifndef KSTD_SHARED_POINTER_HPP
-#define KSTD_SHARED_POINTER_HPP
-
-#include "kstd/bits/shared_ptr.hpp" // IWYU pragma: export
-#include "kstd/bits/unique_ptr.hpp" // IWYU pragma: export
-
-#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector
deleted file mode 100644
index 7568cb6..0000000
--- a/libs/kstd/include/kstd/vector
+++ /dev/null
@@ -1,569 +0,0 @@
-#ifndef KSTD_VECTOR_HPP
-#define KSTD_VECTOR_HPP
-
-#include <kstd/os/error.hpp>
-
-#include <algorithm>
-#include <cstddef>
-#include <initializer_list>
-
-namespace kstd
-{
- /**
- * @brief Custom vector implementation mirroring the std::vector to allow for the usage of STL functionality with our
- * custom memory management.
- *
- * @tparam T Element the vector instance should contain.
- */
- template<typename T>
- struct vector
- {
- using value_type = T; ///< Type of the elements contained in the container.
- using size_type = std::size_t; ///< Type of the size in the container.
- using reference = value_type &; ///< Type of reference to the elements.
- using const_reference = value_type const &; ///< Type of constant reference to the elements.
- using pointer = value_type *; ///< Type of pointer to the elements.
- using const_pointer = value_type const *; ///< Type of constant pointer to the elements.
-
- /**
- * @brief Default Constructor.
- */
- vector() = default;
-
- /**
- * @brief Constructs data with the given amount of elements containing the given value or alternatively the default
- * constructed value.
- *
- * @param n Amount of elements we want to create and set the given value for.
- * @param initial Inital value of all elements in the underlying data array.
- */
- explicit vector(size_type n, value_type initial = value_type{})
- : _size(n)
- , _capacity(n)
- , _data(new value_type[_capacity]{})
- {
- std::ranges::fill(*this, initial);
- }
-
- /**
- * @brief Constructs data by copying all element from the given exclusive range.
- *
- * @tparam InputIterator Template that should have atleast input iterator characteristics.
- * @param first Input iterator to the first element in the range we want to copy from.
- * @param last Input iterator to one past the last element in the range we want to copy from.
- */
- template<typename InputIterator>
- explicit vector(InputIterator first, InputIterator last)
- : _size(std::distance(first, last))
- , _capacity(std::distance(first, last))
- , _data(new value_type[_capacity]{})
- {
- std::ranges::copy(first, last, _data);
- }
-
- /**
- * @brief Construct data by copying all elements from the initializer list.
- *
- * @param initializer_list List we want to copy all elements from.
- */
- explicit vector(std::initializer_list<value_type> initializer_list)
- : _size(initializer_list.size())
- , _capacity(initializer_list.size())
- , _data(new value_type[_capacity]{})
- {
- std::ranges::copy(initializer_list, _data);
- }
-
- /**
- * @brief Copy constructor.
- *
- * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all
- * elements from it.
- *
- * @param other Other instance of vector we want to copy the data from.
- */
- vector(vector<value_type> const & other)
- : _size(other._size)
- , _capacity(other._capacity)
- , _data(new value_type[_capacity]{})
- {
- std::ranges::copy(other, _data);
- }
-
- /**
- * @brief Copy assignment operator.
- *
- * @note Allocates underlying data container with the same capacity as vector we are copying from and copies all
- * elements from it.
- *
- * @param other Other instance of vector we want to copy the data from.
- * @return Newly created copy.
- */
- auto operator=(vector const & other) -> vector<value_type> &
- {
- delete[] _data;
- _size = other._size;
- _capacity = other._capacity;
- _data = new value_type[_capacity]{};
- std::ranges::copy(other, _data);
- return *this;
- }
-
- /**
- * @brief Destructor.
- */
- ~vector()
- {
- delete[] _data;
- }
-
- /**
- * @brief Amount of elements currently contained in this vector, will fill up until we have reached the capacity. If
- * that is the case the capacity is increased automatically.
- *
- * @return Current amount of elements.
- */
- [[nodiscard]] auto size() const -> size_type
- {
- return _size;
- }
-
- /**
- * @brief Amount of space the vector currently has, can be different than the size, because we allocate more than we
- * exactly require to decrease the amount of allocations and deallocation to improve speed.
- *
- * @return Current amount of space the vector has for elements.
- */
- [[nodiscard]] auto capacity() const -> size_type
- {
- return _capacity;
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Does not do any bounds checks use at() for that.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto operator[](size_type index) -> reference
- {
- return _data[index];
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Does not do any bounds checks use at() for that.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto operator[](size_type index) const -> const_reference
- {
- return _data[index];
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- auto at(size_type index) -> reference
- {
- throw_if_out_of_range(index);
- return this->operator[](index);
- }
-
- /**
- * @brief Array indexing operator. Allowing to access element at the given index.
- *
- * @note Ensures we do not access element outside of the bounds of the array, if we do further execution is halted.
- *
- * @param index Index we want to access elements at.
- * @return Reference to the underlying element.
- */
- [[nodiscard]] auto at(size_type index) const -> const_reference
- {
- throw_if_out_of_range(index);
- return this->operator[](index);
- }
-
- /**
- * @brief Appends the given element value to the end of the container. The element is assigned through the
- * assignment operator of the template type. The value is forwarded to the constructor as
- * std::forward<U>(value), meaning it is either moved (rvalue) or copied (lvalue).
- *
- * @note If after the operation the new size() is greater than old capacity() a reallocation takes place,
- * in which case all iterators (including the end() iterator) and all references to the elements are invalidated.
- * Otherwise only the end() iterator is invalidated. Uses a forward reference for the actual value passed, which
- * allows the template method to be used by both lvalue and rvalues and compile a different implementation.
- *
- * @param value The value of the element to append.
- */
- template<class U>
- auto push_back(U && value) -> void
- {
- increase_capacity_if_full();
- _data[_size] = std::forward<U>(value);
- (void)_size++;
- }
-
- /**
- * @brief Appends a new element to the end of the container. The element is constructed through a constructor of the
- * template type. The arguments args... are forwarded to the constructor as std::forward<Args>(args)....
- *
- * If after the operation the new size() is greater than old capacity() a reallocation takes place, in which case
- * all iterators (including the end() iterator) and all references to the elements are invalidated. Otherwise only
- * the end() iterator is invalidated. Uses a forward reference for the actual value passed, which
- * allows the template method to be used by both lvalue and rvalues and compile a different implementation.
- *
- * @tparam Args
- * @param args Arguments to forward to the constructor of the element
- * @return value_type&
- */
- template<class... Args>
- auto emplace_back(Args &&... args) -> value_type &
- {
- increase_capacity_if_full();
- _data[_size] = value_type{std::forward<Args>(args)...};
- auto const index = _size++;
- return _data[index];
- }
-
- /**
- * @brief Removes the last element of the container. Calling pop_back on an empty container results in halting the
- * further execution. Iterators and references to the last element are invalidated. The end()
- * iterator is also invalidated.
- */
- auto pop_back() -> void
- {
- throw_if_empty();
- (void)_size--;
- }
-
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- auto begin() noexcept -> pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- [[nodiscard]] auto begin() const noexcept -> const_pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns an iterator to the first element of the vector.
- * If the vector is empty, the returned iterator will be equal to end().
- *
- * @return Iterator to the first element.
- */
- [[nodiscard]] auto cbegin() const noexcept -> const_pointer
- {
- return begin();
- }
-
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- auto rbegin() noexcept -> pointer
- {
- return _data + _size - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- [[nodiscard]] auto rbegin() const noexcept -> const_pointer
- {
- return _data + _size - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the first element of the reversed vector. It corresponds to the last element
- * of the non-reversed vector. If the vector is empty, the returned iterator will be equal to rend().
- *
- * @return Reverse iterator to the first element.
- */
- [[nodiscard]] auto crbegin() const noexcept -> const_pointer
- {
- return rbegin();
- }
-
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- auto end() noexcept -> pointer
- {
- return _data + _size;
- }
-
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- [[nodiscard]] auto end() const noexcept -> const_pointer
- {
- return _data + _size;
- }
-
- /**
- * @brief Returns an iterator to the element following the last element of the vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Iterator to the element following the last element.
- */
- [[nodiscard]] auto cend() const noexcept -> const_pointer
- {
- return end();
- }
-
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- auto rend() noexcept -> pointer
- {
- return _data + size() - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- [[nodiscard]] auto rend() const noexcept -> const_pointer
- {
- return _data + size() - 1;
- }
-
- /**
- * @brief Returns a reverse iterator to the element following the last element of the reversed vector. It
- * corresponds to the element preceding the first element of the non-reversed vector. This element acts as a
- * placeholder, attempting to access it results in undefined behavior.
- *
- * @return Reverse iterator to the element following the last element.
- */
- [[nodiscard]] auto crend() const noexcept -> const_pointer
- {
- return rbegin();
- }
-
- /**
- * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range
- * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable
- * in that case).
- *
- * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal
- * to the address of the first element.
- */
- auto data() -> pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns a pointer to the underlying array serving as element storage. The pointer is such that range
- * [data(), data() + size()) is always a valid range, even if the container is empty (data() is not dereferenceable
- * in that case).
- *
- * @return Pointer to the underlying element storage. For non-empty containers, the returned pointer compares equal
- * to the address of the first element.
- */
- [[nodiscard]] auto data() const -> const_pointer
- {
- return _data;
- }
-
- /**
- * @brief Returns a reference to the first element in the container. Calling front on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the first element.
- */
- auto front() -> reference
- {
- throw_if_empty();
- return *begin();
- }
-
- /**
- * @brief Returns a reference to the first element in the container. Calling front on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the first element.
- */
- [[nodiscard]] auto front() const -> const_reference
- {
- throw_if_empty();
- return *begin();
- }
-
- /**
- * @brief Returns a reference to the last element in the container. Calling back on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the last element.
- */
- auto back() -> reference
- {
- throw_if_empty();
- return *rbegin();
- }
-
- /**
- * @brief Returns a reference to the last element in the container. Calling back on an empty container causes
- * undefined behavior.
- *
- * @return Reference to the last element.
- */
- [[nodiscard]] auto back() const -> const_reference
- {
- throw_if_empty();
- return *rbegin();
- }
-
- /**
- * @brief Increase the capacity of the vector (the total number of elements that the vector can hold without
- * requiring reallocation) to a value that's greater or equal to new_cap. If new_cap is greater than the current
- * capacity(), new storage is allocated, otherwise the function does nothing.
- *
- * reserve() does not change the size of the vector.
- *
- * If new_cap is greater than capacity(), all iterators (including the end() iterator) and all references to the
- * elements are invalidated. Otherwise, no iterators or references are invalidated.
- *
- * After a call to reserve(), insertions will not trigger reallocation unless the insertion would make the size of
- * the vector greater than the value of capacity().
- *
- * @note Correctly using reserve() can prevent unnecessary reallocations, but inappropriate uses of reserve() (for
- * instance, calling it before every push_back() call) may actually increase the number of reallocations (by causing
- * the capacity to grow linearly rather than exponentially) and result in increased computational complexity and
- * decreased performance. For example, a function that receives an arbitrary vector by reference and appends
- * elements to it should usually not call reserve() on the vector, since it does not know of the vector's usage
- * characteristics.
- *
- * When inserting a range, the range version of insert() is generally preferable as it preserves the correct
- * capacity growth behavior, unlike reserve() followed by a series of push_back()s.
- *
- * reserve() cannot be used to reduce the capacity of the container; to that end shrink_to_fit() is provided.
- *
- * @param new_capacity New capacity of the vector, in number of elements
- */
- auto reserve(size_type new_capacity) -> void
- {
- if (new_capacity <= _capacity)
- {
- return;
- }
-
- _capacity = new_capacity;
- auto temp = new value_type[_capacity]{};
- std::ranges::copy(begin(), end(), temp);
- delete[] _data;
- _data = temp;
- }
-
- /**
- * @brief Requests the removal of unused capacity. Meaning it requests to reduce capacity() to size().
- *
- * If reallocation occurs, all iterators (including the end() iterator) and all references to the elements are
- * invalidated. If no reallocation occurs, no iterators or references are invalidated.
- */
- auto shrink_to_fit() -> void
- {
- if (_size == _capacity)
- {
- return;
- }
-
- _capacity = _size;
- auto temp = new value_type[_capacity]{};
- std::ranges::copy(begin(), end(), temp);
- delete[] _data;
- _data = temp;
- }
-
- /**
- * @brief Wheter there are currently any items this container or not.
- *
- * @return True if there are no elements, false if there are.
- */
- [[nodiscard]] auto empty() const -> bool
- {
- return _size <= 0;
- }
-
- private:
- /**
- * @brief Halts the execution of the application if the data container is currently empty.
- */
- auto throw_if_empty() const -> void
- {
- if (empty())
- {
- os::panic("[Vector] Attempted to access element of currently empty vector");
- }
- }
-
- auto throw_if_out_of_range(size_type index) const -> void
- {
- if (index >= _size)
- {
- os::panic("[Vector] Attempted to read element at invalid index");
- }
- }
-
- /**
- * @brief Increases the internal capacity to 1 if it was previously 0 and to * 2 after that, meaning exponential
- * growth. This is done to decrease the amount of single allocations done and because a power of 2 in memory size is
- * normally perferable for the cache.
- */
- auto increase_capacity_if_full() -> void
- {
- if (_size == _capacity)
- {
- reserve(_capacity == 0U ? 1U : _capacity * 2U);
- }
- }
-
- size_type _size = {}; ///< Amount of elements in the underlying data container
- size_type _capacity = {}; ///< Amount of space for elements in the underlying data container
- value_type * _data = {}; ///< Pointer to the first element in the underlying data container
- };
-
-} // namespace kstd
-
-#endif
diff --git a/libs/kstd/kstd/allocator b/libs/kstd/kstd/allocator
new file mode 100644
index 0000000..0de0e10
--- /dev/null
+++ b/libs/kstd/kstd/allocator
@@ -0,0 +1,64 @@
+#ifndef KSTD_ALLOCATOR_HPP
+#define KSTD_ALLOCATOR_HPP
+
+#include <cstddef>
+#include <new>
+#include <type_traits>
+
+#if __has_builtin(__builtin_operator_new) >= 201'802L
+#define KSTD_OPERATOR_NEW __builtin_operator_new
+#define KSTD_OPERATOR_DELETE __builtin_operator_delete
+#else
+#define KSTD_OPERATOR_NEW ::operator new
+#define KSTD_OPERATOR_DELETE ::operator delete
+#endif
+
+namespace kstd
+{
+
+ template<typename T>
+ struct allocator
+ {
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using propagate_on_container_move_assignment = std::true_type;
+ using is_always_equal = std::true_type;
+
+ constexpr allocator() noexcept = default;
+
+ template<typename U>
+ constexpr allocator(allocator<U> const &) noexcept
+ {}
+
+ constexpr auto allocate(std::size_t n) -> T *
+ {
+ if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ {
+ return static_cast<T *>(KSTD_OPERATOR_NEW(n * sizeof(T), std::align_val_t{alignof(T)}));
+ }
+ return static_cast<T *>(KSTD_OPERATOR_NEW(n * sizeof(T)));
+ }
+
+ constexpr void deallocate(T * p, std::size_t n) noexcept
+ {
+ if (alignof(T) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
+ {
+ KSTD_OPERATOR_DELETE(p, n * sizeof(T), std::align_val_t{alignof(T)});
+ }
+ KSTD_OPERATOR_DELETE(p, n * sizeof(T));
+ }
+ };
+
+ template<typename T, typename U>
+ constexpr auto operator==(allocator<T> const &, allocator<U> const &) noexcept -> bool
+ {
+ return true;
+ }
+
+} // namespace kstd
+
+#undef KSTD_OPERATOR_NEW
+#undef KSTD_OPERATOR_DELETE
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/asm_ptr b/libs/kstd/kstd/asm_ptr
index c06a8b5..c06a8b5 100644
--- a/libs/kstd/include/kstd/asm_ptr
+++ b/libs/kstd/kstd/asm_ptr
diff --git a/libs/kstd/kstd/bits/concepts.hpp b/libs/kstd/kstd/bits/concepts.hpp
new file mode 100644
index 0000000..74c25cb
--- /dev/null
+++ b/libs/kstd/kstd/bits/concepts.hpp
@@ -0,0 +1,15 @@
+#ifndef KSTD_BITS_CONCEPTS_HPP
+#define KSTD_BITS_CONCEPTS_HPP
+
+#include <concepts>
+#include <ranges>
+namespace kstd::bits
+{
+
+ template<typename RangeType, typename ValueType>
+ concept container_compatible_range =
+ std::ranges::input_range<RangeType> && std::convertible_to<std::ranges::range_reference_t<RangeType>, ValueType>;
+
+}
+
+#endif
diff --git a/libs/kstd/kstd/bits/flat_map.hpp b/libs/kstd/kstd/bits/flat_map.hpp
new file mode 100644
index 0000000..fe46203
--- /dev/null
+++ b/libs/kstd/kstd/bits/flat_map.hpp
@@ -0,0 +1,186 @@
+#ifndef KSTD_BITS_FLAT_MAP_HPP
+#define KSTD_BITS_FLAT_MAP_HPP
+
+#include <concepts>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+namespace kstd::bits
+{
+
+ template<typename KeyType, typename MappedType>
+ struct flat_map_reference
+ {
+ using key_type = KeyType;
+ using mapped_type = MappedType;
+
+ constexpr flat_map_reference(key_type const & key, mapped_type & mapped)
+ : first{key}
+ , second{mapped}
+ {}
+
+ constexpr auto operator=(flat_map_reference const & other) const -> flat_map_reference const &
+ {
+ second = other.second;
+ return *this;
+ }
+
+ constexpr auto operator=(flat_map_reference && other) const -> flat_map_reference const &
+ {
+ second = std::move(other.second);
+ return *this;
+ }
+
+ template<typename TupleLikeType>
+ requires(std::tuple_size_v<std::remove_cvref_t<TupleLikeType>> == 2)
+ constexpr auto operator=(TupleLikeType && tuple) const -> flat_map_reference const &
+ {
+ second = std::forward<TupleLikeType>(tuple).second;
+ return *this;
+ }
+
+ template<std::size_t Index>
+ requires(Index >= 0 && Index <= 1)
+ [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto)
+ {
+ if constexpr (Index == 0)
+ {
+ return (first);
+ }
+ else
+ {
+ return (second);
+ }
+ }
+
+ key_type const & first; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
+ mapped_type & second; // NOLINT(cppcoreguidelines-avoid-const-or-ref-data-members)
+ };
+
+ template<typename Reference>
+ struct flat_map_pointer
+ {
+ Reference reference;
+
+ [[nodiscard]] constexpr auto operator->() noexcept -> Reference *
+ {
+ return std::addressof(reference);
+ }
+
+ [[nodiscard]] constexpr auto operator->() const noexcept -> Reference const *
+ {
+ return std::addressof(reference);
+ }
+ };
+
+ template<typename KeyType, typename MappedType, typename KeyIterator, typename MappedIterator>
+ struct flat_map_iterator
+ {
+ using iterator_category = std::random_access_iterator_tag;
+ using value_type = std::pair<KeyType, MappedType>;
+ using difference_type = std::ptrdiff_t;
+ using reference = flat_map_reference<KeyType, MappedType>;
+ using pointer = flat_map_pointer<reference>;
+
+ constexpr flat_map_iterator() = default;
+
+ constexpr flat_map_iterator(KeyIterator key_iterator, MappedIterator mapped_iterator)
+ : m_key_iterator{key_iterator}
+ , m_mapped_iterator{mapped_iterator}
+ {}
+
+ template<typename OtherMappedType, typename OtherKeyIterator, typename OtherMappedIterator>
+ requires(std::convertible_to<OtherKeyIterator, KeyIterator> &&
+ std::convertible_to<OtherMappedIterator, MappedIterator>)
+ constexpr flat_map_iterator(
+ flat_map_iterator<KeyType, OtherMappedType, OtherKeyIterator, OtherMappedIterator> const & other) noexcept
+ : m_key_iterator{other.m_key_iterator}
+ , m_mapped_iterator{other.m_mapped_iterator}
+ {}
+
+ [[nodiscard]] auto key_iterator() const noexcept -> KeyIterator
+ {
+ return m_key_iterator;
+ }
+
+ [[nodiscard]] constexpr auto operator*() const noexcept -> reference
+ {
+ return {*m_key_iterator, *m_mapped_iterator};
+ }
+
+ [[nodiscard]] constexpr auto operator->() const noexcept -> pointer
+ {
+ return {
+ {*m_key_iterator, *m_mapped_iterator}
+ };
+ }
+
+ constexpr auto operator++() noexcept -> flat_map_iterator &
+ {
+ ++m_key_iterator;
+ ++m_mapped_iterator;
+ return *this;
+ }
+
+ constexpr auto operator++(int) noexcept -> flat_map_iterator
+ {
+ auto copy = *this;
+ ++(*this);
+ return copy;
+ }
+
+ constexpr auto operator--() noexcept -> flat_map_iterator &
+ {
+ --m_key_iterator;
+ --m_mapped_iterator;
+ return *this;
+ }
+
+ constexpr auto operator--(int) noexcept -> flat_map_iterator
+ {
+ auto copy = *this;
+ --(*this);
+ return copy;
+ }
+
+ [[nodiscard]] constexpr auto operator+(difference_type offset) const noexcept -> flat_map_iterator
+ {
+ return {m_key_iterator + offset, m_mapped_iterator + offset};
+ }
+
+ [[nodiscard]] constexpr auto operator-(flat_map_iterator const & other) const noexcept -> difference_type
+ {
+ return m_key_iterator - other.m_key_iterator;
+ }
+
+ [[nodiscard]] constexpr auto operator<=>(flat_map_iterator const & other) const noexcept = default;
+
+ private:
+ KeyIterator m_key_iterator{};
+ MappedIterator m_mapped_iterator{};
+ };
+
+} // namespace kstd::bits
+
+template<typename K, typename M>
+struct std::tuple_size<kstd::bits::flat_map_reference<K, M>> : std::integral_constant<std::size_t, 2>
+{
+};
+
+template<typename K, typename M>
+struct std::tuple_element<0, kstd::bits::flat_map_reference<K, M>>
+{
+ using type = K const &;
+};
+
+template<typename K, typename M>
+struct std::tuple_element<1, kstd::bits::flat_map_reference<K, M>>
+{
+ using type = M &;
+};
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/arg.hpp b/libs/kstd/kstd/bits/format/arg.hpp
new file mode 100644
index 0000000..e65b26f
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/arg.hpp
@@ -0,0 +1,75 @@
+#ifndef KSTD_BITS_FORMAT_ARG_HPP
+#define KSTD_BITS_FORMAT_ARG_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/fwd.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ enum struct arg_type : std::uint8_t
+ {
+ none,
+ boolean,
+ character,
+ integer,
+ unsigned_integer,
+ string_view,
+ c_string,
+ pointer,
+ user_defined,
+ };
+ } // namespace bits::format
+
+ struct format_arg
+ {
+ bits::format::arg_type type{};
+ union
+ {
+ bool boolean;
+ char character;
+ std::int64_t integer;
+ std::uint64_t unsigned_integer;
+ std::string_view string_view;
+ char const * c_string;
+ void const * pointer;
+ struct
+ {
+ void const * pointer;
+ auto (*format)(void const * value, format_parse_context & parse_context, format_context & context) -> void;
+ } user_defined;
+ } value{};
+ };
+
+ namespace bits::format
+ {
+ constexpr auto extrat_dynamic_width(format_arg const & arg) -> std::size_t
+ {
+ if (arg.type == arg_type::unsigned_integer)
+ {
+ return static_cast<std::size_t>(arg.value.unsigned_integer);
+ }
+ else if (arg.type == arg_type::integer)
+ {
+ if (arg.value.integer < 0)
+ {
+ error("Dynamic width cannont be negative.");
+ }
+ return static_cast<std::size_t>(arg.value.integer);
+ }
+ error("Dynamic width argument is not an integral value.");
+ return 0;
+ }
+ } // namespace bits::format
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/args.hpp b/libs/kstd/kstd/bits/format/args.hpp
new file mode 100644
index 0000000..e8e3114
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/args.hpp
@@ -0,0 +1,160 @@
+#ifndef KSTD_BITS_FORMAT_ARGS_HPP
+#define KSTD_BITS_FORMAT_ARGS_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/arg.hpp>
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/fwd.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <array>
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <span>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+
+ template<typename ValueType>
+ auto format_trampoline(void const * value_pointer, format_parse_context & parse_context, format_context & context)
+ -> void
+ {
+ auto typed_value_pointer = static_cast<ValueType const *>(value_pointer);
+ auto fmt = formatter<std::remove_cvref_t<ValueType>>{};
+ auto const it = fmt.parse(parse_context);
+ parse_context.advance_to(it);
+ fmt.format(*typed_value_pointer, context);
+ }
+
+ template<typename ValueType>
+ constexpr auto determine_arg_type() -> arg_type
+ {
+ using decay_type = std::remove_cvref_t<ValueType>;
+ if constexpr (std::same_as<decay_type, bool>)
+ {
+ return arg_type::boolean;
+ }
+ else if constexpr (std::same_as<decay_type, char>)
+ {
+ return arg_type::character;
+ }
+ else if constexpr (std::integral<decay_type> && std::is_signed_v<decay_type>)
+ {
+ return arg_type::integer;
+ }
+ else if constexpr (std::integral<decay_type> && std::is_unsigned_v<decay_type>)
+ {
+ return arg_type::unsigned_integer;
+ }
+ else if constexpr (std::same_as<decay_type, std::string_view>)
+ {
+ return arg_type::string_view;
+ }
+ else if constexpr (std::same_as<std::decay_t<decay_type>, char *> ||
+ std::same_as<std::decay_t<decay_type>, char const *>)
+ {
+ return arg_type::c_string;
+ }
+ else if constexpr (std::is_pointer_v<decay_type> || std::same_as<decay_type, std::nullptr_t>)
+ {
+ if constexpr (std::same_as<decay_type, char *> || std::same_as<decay_type, char const *>)
+ {
+ return arg_type::user_defined;
+ }
+ else
+ {
+ return arg_type::pointer;
+ }
+ }
+ else
+ {
+ return arg_type::user_defined;
+ }
+ }
+
+ template<typename ValueType>
+ constexpr auto make_single_arg(ValueType const & value) -> format_arg
+ {
+ auto result = format_arg{};
+ constexpr auto type = determine_arg_type<ValueType>();
+ result.type = type;
+
+ if constexpr (type == arg_type::boolean)
+ {
+ result.value.boolean = value;
+ }
+ else if constexpr (type == arg_type::character)
+ {
+ result.value.character = value;
+ }
+ else if constexpr (type == arg_type::integer)
+ {
+ result.value.integer = static_cast<std::int64_t>(value);
+ }
+ else if constexpr (type == arg_type::unsigned_integer)
+ {
+ result.value.unsigned_integer = static_cast<std::uint64_t>(value);
+ }
+ else if constexpr (type == arg_type::string_view)
+ {
+ result.value.string_view = value;
+ }
+ else if constexpr (type == arg_type::c_string)
+ {
+ result.value.c_string = value;
+ }
+ else if constexpr (type == arg_type::pointer)
+ {
+ if constexpr (std::same_as<std::remove_cvref_t<ValueType>, std::nullptr_t>)
+ {
+ result.value.pointer = nullptr;
+ }
+ else
+ {
+ result.value.pointer = static_cast<void const *>(value);
+ }
+ }
+ else
+ {
+ result.value.user_defined.pointer = &value;
+ result.value.user_defined.format = format_trampoline<ValueType>;
+ }
+ return result;
+ }
+
+ } // namespace bits::format
+
+ using format_args = std::span<format_arg const>;
+
+ template<std::size_t Count>
+ struct format_arg_store
+ {
+ std::array<format_arg, Count> args{};
+ };
+
+ template<typename... Arguments>
+ [[nodiscard]] auto make_format_args(Arguments const &... args) noexcept -> format_arg_store<sizeof...(Arguments)>
+ {
+ using namespace bits::format;
+
+ if constexpr (sizeof...(Arguments) == 0)
+ {
+ return format_arg_store<0>{};
+ }
+ else
+ {
+ return format_arg_store<sizeof...(Arguments)>{
+ std::array<format_arg, sizeof...(Arguments)>{make_single_arg(args)...}};
+ }
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/context.hpp b/libs/kstd/kstd/bits/format/context.hpp
new file mode 100644
index 0000000..c166ba9
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/context.hpp
@@ -0,0 +1,65 @@
+#ifndef KSTD_BITS_FORMAT_CONTEXT_HPP
+#define KSTD_BITS_FORMAT_CONTEXT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/arg.hpp>
+#include <kstd/bits/format/output_buffer.hpp>
+#include <kstd/os/error.hpp>
+
+#include <concepts>
+#include <cstddef>
+#include <span>
+#include <string_view>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ template<typename T>
+ constexpr auto inline is_width_v = std::integral<T> && //
+ !std::same_as<bool, T> && //
+ !std::same_as<char, T> && //
+ !std::same_as<wchar_t, T> && //
+ !std::same_as<char8_t, T> && //
+ !std::same_as<char16_t, T> && //
+ !std::same_as<char32_t, T>;
+ }
+
+ struct format_context
+ {
+ using format_args = std::span<format_arg const>;
+ format_args args{};
+
+ format_context(bits::format::output_buffer & buffer, format_args args)
+ : args{args}
+ , m_buffer{&buffer}
+ {}
+
+ [[nodiscard]] auto arg(std::size_t const id) const -> format_arg const &
+ {
+ if (id >= args.size())
+ {
+ kstd::os::panic("[kstd:format] argument index out of range!");
+ }
+ return args[id];
+ }
+
+ constexpr auto push(std::string_view string) -> void
+ {
+ m_buffer->push(string);
+ }
+
+ constexpr auto push(char character) -> void
+ {
+ m_buffer->push(character);
+ }
+
+ private:
+ bits::format::output_buffer * m_buffer;
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/error.hpp b/libs/kstd/kstd/bits/format/error.hpp
new file mode 100644
index 0000000..c0cb53d
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/error.hpp
@@ -0,0 +1,24 @@
+#ifndef KSTD_BITS_FORMAT_ERROR_HPP
+#define KSTD_BITS_FORMAT_ERROR_HPP
+
+#include <kstd/os/error.hpp>
+
+namespace kstd::bits::format
+{
+
+ constexpr auto error(char const * message) -> void
+ {
+ if consteval
+ {
+ extern void compile_time_format_error_triggered(char const *);
+ compile_time_format_error_triggered(message);
+ }
+ else
+ {
+ kstd::os::panic("Error while formatting a string.");
+ }
+ }
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter.hpp b/libs/kstd/kstd/bits/format/formatter.hpp
new file mode 100644
index 0000000..eb28829
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter.hpp
@@ -0,0 +1,92 @@
+#ifndef KSTD_BITS_FORMATTER_HPP
+#define KSTD_BITS_FORMATTER_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+
+namespace kstd
+{
+
+ template<typename>
+ struct formatter
+ {
+ formatter() = delete;
+ formatter(formatter const &) = delete;
+ auto operator=(formatter const &) -> formatter & = delete;
+ };
+
+ template<typename ValueType>
+ struct range_formatter
+ {
+ constexpr auto set_separator(std::string_view sep) -> void
+ {
+ m_separator = sep;
+ }
+
+ constexpr auto set_brackets(std::string_view opening, std::string_view closing)
+ {
+ m_prefix = opening;
+ m_suffix = closing;
+ }
+
+ constexpr auto parse(format_parse_context & context)
+ {
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it == 'n')
+ {
+ set_brackets("", "");
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == ':')
+ {
+ std::advance(it, 1);
+ context.advance_to(it);
+ it = m_inner_formatter.parse(context);
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Invalid formate specifier for range");
+ }
+
+ return it;
+ }
+
+ template<typename Range>
+ auto format(Range const & range, format_context & context)
+ {
+ context.push(m_prefix);
+
+ auto is_first = true;
+ std::ranges::for_each(range, [&](auto const & element) {
+ if (!is_first)
+ {
+ context.push(m_separator);
+ }
+ m_inner_formatter.format(element, context);
+ is_first = false;
+ });
+
+ context.push(m_suffix);
+ }
+
+ private:
+ kstd::formatter<ValueType> m_inner_formatter{};
+ std::string_view m_separator{", "};
+ std::string_view m_prefix{"["};
+ std::string_view m_suffix{"]"};
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/bool.hpp b/libs/kstd/kstd/bits/format/formatter/bool.hpp
new file mode 100644
index 0000000..cc8d190
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/bool.hpp
@@ -0,0 +1,83 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_BOOL_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <iterator>
+#include <string_view>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<bool>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 's')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for bool.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(bool value, format_context & context) const -> void
+ {
+ auto const text = value ? std::string_view{"true"} : std::string_view{"false"};
+ auto final_width = 0uz;
+
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ auto padding = bits::format::calculate_format_padding(final_width, text.size(), specifiers.align,
+ bits::format::alignment::left);
+
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+
+ context.push(text);
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/byte.hpp b/libs/kstd/kstd/bits/format/formatter/byte.hpp
new file mode 100644
index 0000000..cc8aece
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/byte.hpp
@@ -0,0 +1,23 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_BYTE_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/integral.hpp>
+
+#include <cstddef>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<std::byte> : formatter<int>
+ {
+ auto format(std::byte value, format_context & context) const -> void
+ {
+ formatter<int>::format(static_cast<int>(value), context);
+ }
+ };
+
+} // namespace kstd
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/char.hpp b/libs/kstd/kstd/bits/format/formatter/char.hpp
new file mode 100644
index 0000000..92489a1
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/char.hpp
@@ -0,0 +1,94 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_CHAR_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/integral.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <iterator>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<char> : formatter<unsigned char>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (specifiers.alternative_form || specifiers.zero_pad || specifiers.sign != bits::format::sign_mode::none)
+ {
+ bits::format::error("Invalid format specifiers for 'char'");
+ }
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 'c' || *it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for char.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(char value, format_context & context) const -> void
+ {
+ if (specifiers.type == '\0' || specifiers.type == 'c')
+ {
+ auto final_width = 0uz;
+
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ auto padding =
+ bits::format::calculate_format_padding(final_width, 1, specifiers.align, bits::format::alignment::left);
+
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+
+ context.push(value);
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ else
+ {
+ formatter<unsigned char>::format(static_cast<unsigned char>(value), context);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/cstring.hpp b/libs/kstd/kstd/bits/format/formatter/cstring.hpp
new file mode 100644
index 0000000..553c8ca
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/cstring.hpp
@@ -0,0 +1,29 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_CSTRING_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/string_view.hpp>
+
+#include <string_view>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<char const *> : formatter<std::string_view>
+ {
+ auto format(char const * string, format_context & context) const -> void
+ {
+ formatter<std::string_view>::format(string ? std::string_view{string} : "(null)", context);
+ }
+ };
+
+ template<>
+ struct formatter<char *> : formatter<char const *>
+ {
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/integral.hpp b/libs/kstd/kstd/bits/format/formatter/integral.hpp
new file mode 100644
index 0000000..d17dc95
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/integral.hpp
@@ -0,0 +1,204 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_INTEGRAL_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <array>
+#include <concepts>
+#include <cstddef>
+#include <iterator>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+
+ template<std::integral T>
+ struct formatter<T>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto static maximum_digits = 80;
+
+ enum struct base
+ {
+ bin = 2,
+ oct = 8,
+ dec = 10,
+ hex = 16,
+ };
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it != '}')
+ {
+ if (*it == 'b' || *it == 'B' || *it == 'd' || *it == 'o' || *it == 'x' || *it == 'X' || *it == 'p')
+ {
+ specifiers.type = *it;
+ std::advance(it, 1);
+ }
+ else
+ {
+ bits::format::error("Invalid type specifier for integral type.");
+ }
+ }
+
+ if (it != end && *it != '}')
+ {
+ bits::format::error("Missing terminating '}' in format string.");
+ }
+
+ return it;
+ }
+
+ auto format(T value, format_context & context) const -> void
+ {
+ auto final_width = 0uz;
+ if (specifiers.mode == bits::format::width_mode::static_value)
+ {
+ final_width = specifiers.width_value;
+ }
+ else if (specifiers.mode == bits::format::width_mode::dynamic_argument_id)
+ {
+ auto const & arg = context.arg(specifiers.width_value);
+ final_width = bits::format::extrat_dynamic_width(arg);
+ }
+
+ using unsigned_T = std::make_unsigned_t<T>;
+ auto absolute_value = static_cast<unsigned_T>(value);
+ auto is_negative = false;
+
+ if constexpr (std::is_signed_v<T>)
+ {
+ if (value < 0)
+ {
+ is_negative = true;
+ absolute_value = 0 - static_cast<unsigned_T>(value);
+ }
+ }
+
+ auto const base = [type = specifiers.type] -> auto {
+ switch (type)
+ {
+ case 'x':
+ case 'X':
+ case 'p':
+ return base::hex;
+ case 'b':
+ case 'B':
+ return base::bin;
+ case 'o':
+ return base::oct;
+ default:
+ return base::dec;
+ }
+ }();
+
+ auto buffer = std::array<char, maximum_digits>{};
+ auto digits = (specifiers.type == 'X') ? "0123456789ABCDEF" : "0123456789abcdef";
+ auto current = buffer.rbegin();
+
+ if (absolute_value == 0)
+ {
+ *current = '0';
+ std::advance(current, 1);
+ }
+ else
+ {
+ while (absolute_value != 0)
+ {
+ *current = digits[absolute_value % std::to_underlying(base)];
+ std::advance(current, 1);
+ absolute_value /= std::to_underlying(base);
+ }
+ }
+
+ auto content_length = static_cast<std::size_t>(std::distance(buffer.rbegin(), current));
+ auto prefix = std::array<char, 2>{'0', '\0'};
+ auto prefix_length = 0uz;
+ if (specifiers.alternative_form)
+ {
+ switch (base)
+ {
+ case base::bin:
+ prefix[1] = specifiers.type == 'B' ? 'B' : 'b';
+ prefix_length = 2;
+ break;
+ case base::oct:
+ prefix_length = 1;
+ break;
+ case base::hex:
+ prefix[1] = specifiers.type == 'X' ? 'X' : 'x';
+ prefix_length = 2;
+ break;
+ default:
+ break;
+ }
+ }
+
+ auto sign_character = '\0';
+ if (is_negative)
+ {
+ sign_character = '-';
+ }
+ else if (specifiers.sign == bits::format::sign_mode::plus)
+ {
+ sign_character = '+';
+ }
+ else if (specifiers.sign == bits::format::sign_mode::space)
+ {
+ sign_character = ' ';
+ }
+
+ auto const total_length = content_length + prefix_length + (sign_character != '\0');
+ auto const padding = bits::format::calculate_format_padding(final_width, total_length, specifiers.align,
+ bits::format::alignment::right);
+ auto const effective_zero_pad = specifiers.zero_pad && (specifiers.align == bits::format::alignment::none);
+
+ if (!effective_zero_pad)
+ {
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+
+ if (sign_character != '\0')
+ {
+ context.push(sign_character);
+ }
+ if (prefix_length > 0)
+ {
+ context.push(std::string_view{prefix.data(), prefix_length});
+ }
+
+ if (effective_zero_pad)
+ {
+ for (auto i = 0uz; i < padding.left; ++i)
+ {
+ context.push('0');
+ }
+ }
+
+ context.push(std::string_view{current.base(), content_length});
+
+ for (auto i = 0uz; i < padding.right; ++i)
+ {
+ context.push(specifiers.fill);
+ }
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/ordering.hpp b/libs/kstd/kstd/bits/format/formatter/ordering.hpp
new file mode 100644
index 0000000..7832226
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/ordering.hpp
@@ -0,0 +1,111 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_ORDERING_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <compare>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<std::strong_ordering>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+ return context.begin();
+ }
+
+ auto format(std::strong_ordering value, format_context & context) const -> void
+ {
+ if (value == std::strong_ordering::equal)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equal");
+ }
+ else if (value == std::strong_ordering::equivalent)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equivalent");
+ }
+ else if (value == std::strong_ordering::greater)
+ {
+ return context.push(specifiers.alternative_form ? ">" : "greater");
+ }
+ else if (value == std::strong_ordering::less)
+ {
+ return context.push(specifiers.alternative_form ? "<" : "less");
+ }
+ kstd::os::panic("[kstd:format] Invalid strong ordering value!");
+ }
+ };
+
+ template<>
+ struct formatter<std::weak_ordering>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+ return context.begin();
+ }
+
+ auto format(std::weak_ordering value, format_context & context) const -> void
+ {
+ if (value == std::weak_ordering::equivalent)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equivalent");
+ }
+ else if (value == std::weak_ordering::greater)
+ {
+ return context.push(specifiers.alternative_form ? ">" : "greater");
+ }
+ else if (value == std::weak_ordering::less)
+ {
+ return context.push(specifiers.alternative_form ? "<" : "less");
+ }
+ kstd::os::panic("[kstd:format] Invalid weak ordering value!");
+ }
+ };
+
+ template<>
+ struct formatter<std::partial_ordering>
+ {
+ bits::format::specifiers specifiers{};
+
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ specifiers = bits::format::parse_format_specifiers(context);
+ return context.begin();
+ }
+
+ auto format(std::partial_ordering value, format_context & context) const -> void
+ {
+ if (value == std::partial_ordering::equivalent)
+ {
+ return context.push(specifiers.alternative_form ? "==" : "equivalent");
+ }
+ else if (value == std::partial_ordering::greater)
+ {
+ return context.push(specifiers.alternative_form ? ">" : "greater");
+ }
+ else if (value == std::partial_ordering::less)
+ {
+ return context.push(specifiers.alternative_form ? "<" : "less");
+ }
+ else if (value == std::partial_ordering::unordered)
+ {
+ return context.push(specifiers.alternative_form ? "<=>" : "unordered");
+ }
+ kstd::os::panic("[kstd:format] Invalid partial ordering value!");
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/pointer.hpp b/libs/kstd/kstd/bits/format/formatter/pointer.hpp
new file mode 100644
index 0000000..15f9a5b
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/pointer.hpp
@@ -0,0 +1,42 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_POINTER_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/integral.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+#include <kstd/bits/format/specifiers.hpp>
+
+#include <bit>
+#include <cstdint>
+
+namespace kstd
+{
+
+ template<typename T>
+ struct formatter<T const *> : formatter<std::uintptr_t>
+ {
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ auto result = formatter<std::uintptr_t>::parse(context);
+ if (!this->specifiers.type)
+ {
+ this->specifiers.type = 'p';
+ this->specifiers.alternative_form = true;
+ }
+ return result;
+ }
+
+ auto format(T const * pointer, format_context & context) const -> void
+ {
+ formatter<std::uintptr_t>::format(std::bit_cast<std::uintptr_t>(pointer), context);
+ }
+ };
+
+ template<typename T>
+ struct formatter<T *> : formatter<T const *>
+ {
+ };
+
+} // namespace kstd
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/range.hpp b/libs/kstd/kstd/bits/format/formatter/range.hpp
new file mode 100644
index 0000000..05af06f
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/range.hpp
@@ -0,0 +1,32 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_RANGE_HPP
+
+#include <kstd/bits/format/formatter.hpp>
+
+#include <concepts>
+#include <ranges>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+ namespace bits::format
+ {
+ template<typename T>
+ concept iterable = requires(T const & t) {
+ t.begin();
+ t.end();
+ };
+
+ template<typename T>
+ concept formattable_range = iterable<T> && !std::same_as<std::remove_cvref_t<T>, std::string_view> &&
+ !std::same_as<std::decay_t<T>, char *> && !std::same_as<std::decay_t<T>, char const *>;
+ } // namespace bits::format
+
+ template<bits::format::formattable_range Range>
+ struct formatter<Range> : range_formatter<std::ranges::range_value_t<Range>>
+ {
+ };
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/formatter/string_view.hpp b/libs/kstd/kstd/bits/format/formatter/string_view.hpp
new file mode 100644
index 0000000..7d74579
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/formatter/string_view.hpp
@@ -0,0 +1,41 @@
+#ifndef KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP
+#define KSTD_BITS_FORMAT_FORMATTER_STRING_VIEW_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <string_view>
+
+namespace kstd
+{
+
+ template<>
+ struct formatter<std::string_view>
+ {
+ constexpr auto parse(format_parse_context & context) -> format_parse_context::iterator
+ {
+ auto it = context.begin();
+
+ if (it != context.end() && *it == 's')
+ {
+ ++it;
+ }
+
+ if (it != context.end() && *it != '}')
+ {
+ bits::format::error("Invalid specifier for string_view.");
+ }
+ return it;
+ }
+
+ auto format(std::string_view const & string, format_context & context) const -> void
+ {
+ context.push(string);
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/fwd.hpp b/libs/kstd/kstd/bits/format/fwd.hpp
new file mode 100644
index 0000000..6caedae
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/fwd.hpp
@@ -0,0 +1,23 @@
+#ifndef KSTD_BITS_FORMAT_FWD_HPP
+#define KSTD_BITS_FORMAT_FWD_HPP
+
+// IWYU pragma: private
+
+#include <cstddef>
+
+namespace kstd
+{
+
+ struct format_parse_context;
+ struct format_context;
+ struct format_arg;
+
+ template<typename>
+ struct formatter;
+
+ template<std::size_t>
+ struct format_arg_store;
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/output_buffer.hpp b/libs/kstd/kstd/bits/format/output_buffer.hpp
new file mode 100644
index 0000000..fd7a2b4
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/output_buffer.hpp
@@ -0,0 +1,32 @@
+#ifndef KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP
+#define KSTD_BITS_FORMAT_OUTPUT_BUFFER_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <string_view>
+
+namespace kstd::bits::format
+{
+
+ //! An abstract interface for formatted output buffers.
+ //!
+ //! This interface is intended to be use for functions dealing with string formatting, like the print and the format
+ //! family.
+ struct output_buffer
+ {
+ virtual ~output_buffer() = default;
+
+ //! Push a text segment into the buffer.
+ //!
+ //! @param text The text segment to push.
+ virtual auto push(std::string_view text) -> void = 0;
+
+ //! Push a single character into the buffer.
+ //!
+ //! @param character The character to push into the buffer.
+ virtual auto push(char character) -> void = 0;
+ };
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/parse_context.hpp b/libs/kstd/kstd/bits/format/parse_context.hpp
new file mode 100644
index 0000000..cab8d72
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/parse_context.hpp
@@ -0,0 +1,109 @@
+#ifndef KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP
+#define KSTD_BITS_FORMAT_PARSE_CONTEXT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/error.hpp>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kstd
+{
+
+ struct format_parse_context
+ {
+ using iterator = std::string_view::const_iterator;
+
+ constexpr format_parse_context(std::string_view format, std::size_t argument_count,
+ bool const * is_integral = nullptr)
+ : m_current{format.begin()}
+ , m_end{format.end()}
+ , m_argument_count{argument_count}
+ , m_is_integral{is_integral}
+ {}
+
+ [[nodiscard]] constexpr auto begin() const -> iterator
+ {
+ return m_current;
+ }
+
+ [[nodiscard]] constexpr auto end() const -> iterator
+ {
+ return m_end;
+ }
+
+ constexpr auto advance_to(iterator position) -> void
+ {
+ m_current = position;
+ }
+
+ constexpr auto next_arg_id() -> std::size_t
+ {
+ if (m_mode == index_mode::manual)
+ {
+ bits::format::error("Cannot mix automatic and manual indexing.");
+ }
+
+ m_mode = index_mode::automatic;
+
+ if (m_next_argument_id >= m_argument_count)
+ {
+ bits::format::error("Argument index out of bounds.");
+ }
+ return m_next_argument_id++;
+ }
+
+ constexpr auto check_arg_id(std::size_t index) -> void
+ {
+ if (m_mode == index_mode::automatic)
+ {
+ bits::format::error("Cannot mix automatic and manual indexing.");
+ }
+
+ m_mode = index_mode::manual;
+
+ if (index >= m_argument_count)
+ {
+ bits::format::error("Argument index out of bounds.");
+ }
+ }
+
+ constexpr auto check_dynamic_width_id(std::size_t id) -> void
+ {
+ check_arg_id(id);
+ if (m_is_integral && !m_is_integral[id])
+ {
+ bits::format::error("Dynamic width argument must be an integral object.");
+ }
+ }
+
+ constexpr auto next_dynamic_width_id() -> std::size_t
+ {
+ auto const id = next_arg_id();
+ if (m_is_integral && !m_is_integral[id])
+ {
+ bits::format::error("Dynamic width argument must be an integral object.");
+ }
+ return id;
+ }
+
+ private:
+ enum class index_mode
+ {
+ unknown,
+ automatic,
+ manual,
+ };
+
+ iterator m_current{};
+ iterator m_end{};
+ index_mode m_mode{};
+ std::size_t m_next_argument_id{};
+ std::size_t m_argument_count{};
+ bool const * m_is_integral{};
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/specifiers.hpp b/libs/kstd/kstd/bits/format/specifiers.hpp
new file mode 100644
index 0000000..211c95d
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/specifiers.hpp
@@ -0,0 +1,205 @@
+#ifndef KSTD_BITS_FORMAT_SPECS_HPP
+#define KSTD_BITS_FORMAT_SPECS_HPP
+
+// IWYU pragma: private
+
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <cstddef>
+#include <cstdint>
+#include <iterator>
+
+namespace kstd::bits::format
+{
+ enum struct alignment : std::uint8_t
+ {
+ none,
+ left,
+ right,
+ center,
+ };
+
+ enum struct sign_mode : std::uint8_t
+ {
+ none,
+ plus,
+ minus,
+ space,
+ };
+
+ enum struct width_mode : std::uint8_t
+ {
+ none,
+ static_value,
+ dynamic_argument_id
+ };
+
+ struct specifiers
+ {
+ char fill{' '};
+ alignment align{};
+ sign_mode sign{};
+ bool alternative_form{};
+ bool zero_pad{};
+
+ width_mode mode{};
+ std::size_t width_value{};
+ char type{};
+ };
+
+ struct padding
+ {
+ std::size_t left{};
+ std::size_t right{};
+ };
+
+ constexpr auto parse_format_specifiers(format_parse_context & context) -> specifiers
+ {
+ auto specs = specifiers{};
+ auto it = context.begin();
+ auto const end = context.end();
+
+ if (it != end && *it == '}')
+ {
+ return specs;
+ }
+
+ if (std::next(it) != end && ((*std::next(it)) == '<' || (*std::next(it)) == '>' || (*std::next(it)) == '^'))
+ {
+ specs.fill = *it;
+ switch (*std::next(it))
+ {
+ case '<':
+ specs.align = alignment::left;
+ break;
+ case '>':
+ specs.align = alignment::right;
+ break;
+ case '^':
+ default:
+ specs.align = alignment::center;
+ break;
+ }
+ std::advance(it, 2);
+ }
+ else if (*it == '<' || *it == '>' || *it == '^')
+ {
+ switch (*it)
+ {
+ case '<':
+ specs.align = alignment::left;
+ break;
+ case '>':
+ specs.align = alignment::right;
+ break;
+ case '^':
+ default:
+ specs.align = alignment::center;
+ break;
+ }
+ std::advance(it, 1);
+ }
+
+ if (it != end && (*it == '+' || *it == '-' || *it == ' '))
+ {
+ switch (*it)
+ {
+ case '+':
+ specs.sign = sign_mode::plus;
+ break;
+ case '-':
+ specs.sign = sign_mode::minus;
+ break;
+ case ' ':
+ default:
+ specs.sign = sign_mode::space;
+ break;
+ }
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '#')
+ {
+ specs.alternative_form = true;
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '0')
+ {
+ specs.zero_pad = true;
+ std::advance(it, 1);
+ }
+
+ if (it != end && *it == '{')
+ {
+ specs.mode = width_mode::dynamic_argument_id;
+ std::advance(it, 1);
+ auto argument_id = 0uz;
+
+ if (it != end && *it >= '0' && *it <= '9')
+ {
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ context.check_dynamic_width_id(argument_id);
+ }
+ else
+ {
+ argument_id = context.next_dynamic_width_id();
+ }
+
+ if (it == end || *it != '}')
+ {
+ error("Expected '}' for dynamic width.");
+ }
+ std::advance(it, 1);
+ specs.width_value = argument_id;
+ }
+ else if (it != end && *it >= '0' && *it <= '9')
+ {
+ specs.mode = width_mode::static_value;
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ specs.width_value = specs.width_value * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ }
+
+ context.advance_to(it);
+ return specs;
+ }
+
+ constexpr auto calculate_format_padding(std::size_t target_width, std::size_t content_length,
+ alignment requested_alignment, alignment default_alignment) -> padding
+ {
+ if (target_width <= content_length)
+ {
+ return {};
+ }
+
+ auto total_padding = target_width - content_length;
+ auto effective_alignment = (requested_alignment == alignment::none) ? default_alignment : requested_alignment;
+
+ switch (effective_alignment)
+ {
+ case alignment::center:
+ {
+ auto left = total_padding / 2;
+ auto right = total_padding - left;
+ return {left, right};
+ }
+ case alignment::left:
+ return {0, total_padding};
+ case alignment::right:
+ default:
+ return {total_padding, 0};
+ break;
+ }
+ }
+
+} // namespace kstd::bits::format
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/string.hpp b/libs/kstd/kstd/bits/format/string.hpp
new file mode 100644
index 0000000..e7e4088
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/string.hpp
@@ -0,0 +1,148 @@
+#ifndef KSTD_BITS_FORMAT_STRING_HPP
+#define KSTD_BITS_FORMAT_STRING_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/error.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/parse_context.hpp>
+
+#include <array>
+#include <cstddef>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ template<typename... Args>
+ constexpr auto validate_argument(std::size_t target_index, format_parse_context & context) -> void
+ {
+ auto found = false;
+ auto current_index = 0uz;
+
+ (..., [&] {
+ if (current_index == target_index && !found)
+ {
+ using decay_type = std::remove_cvref_t<Args>;
+ static_assert(std::is_default_constructible_v<formatter<decay_type>>, "Missing formatter specialization.");
+ auto fmt = formatter<decay_type>{};
+
+ auto it = fmt.parse(context);
+ context.advance_to(it);
+ found = true;
+ }
+ ++current_index;
+ }());
+
+ if (!found)
+ {
+ error("Argument index out of bounds.");
+ }
+ }
+
+ template<typename... Args>
+ constexpr auto validate_dynamic_width(std::size_t target_index) -> void
+ {
+ auto is_valid_integer = false;
+ auto current_index = 0uz;
+
+ (..., [&] {
+ if (current_index == target_index)
+ {
+ using decay_type = std::remove_cvref_t<Args>;
+ if constexpr (std::is_integral_v<decay_type>)
+ {
+ is_valid_integer = true;
+ }
+ }
+ ++current_index;
+ }());
+
+ if (!is_valid_integer)
+ {
+ error("Dynamic width argument must be an integral object.");
+ }
+ }
+ } // namespace bits::format
+
+ template<typename... Args>
+ struct format_string
+ {
+ template<std::size_t Size>
+ consteval format_string(char const (&str)[Size]) noexcept(false) // NOLINT
+ : str_view{str}
+ {
+ using namespace bits::format;
+
+ auto const is_width_compatible =
+ std::array<bool, (sizeof...(Args) > 0 ? sizeof...(Args) : 1)>{is_width_v<std::remove_cvref_t<Args>>...};
+ auto context = format_parse_context{str_view, sizeof...(Args), is_width_compatible.data()};
+ auto it = context.begin();
+
+ while (it != context.end())
+ {
+ if (*it == '{')
+ {
+ ++it;
+ if (it != context.end() && *it == '{')
+ {
+ ++it;
+ context.advance_to(it);
+ continue;
+ }
+
+ context.advance_to(it);
+ auto argument_id = 0uz;
+
+ if (it != context.end() && *it >= '0' && *it <= '9')
+ {
+ while (it != context.end() && *it >= '0' && *it <= '9')
+ {
+ argument_id = argument_id * 10 + static_cast<std::size_t>(*it - '0');
+ ++it;
+ }
+ context.check_arg_id(argument_id);
+ context.advance_to(it);
+ }
+ else
+ {
+ argument_id = context.next_arg_id();
+ }
+
+ if (it != context.end() && *it == ':')
+ {
+ ++it;
+ context.advance_to(it);
+ }
+
+ validate_argument<Args...>(argument_id, context);
+
+ it = context.begin();
+ if (it == context.end() || *it != '}')
+ {
+ bits::format::error("Missing closing '}' in format string.");
+ }
+ }
+ else if (*it == '}')
+ {
+ ++it;
+ if (it != context.end() && *it == '}')
+ {
+ bits::format::error("Unescaped '}' in format string.");
+ }
+ }
+ ++it;
+ context.advance_to(it);
+ }
+ }
+
+ std::string_view str_view;
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/format/vformat.hpp b/libs/kstd/kstd/bits/format/vformat.hpp
new file mode 100644
index 0000000..994fae5
--- /dev/null
+++ b/libs/kstd/kstd/bits/format/vformat.hpp
@@ -0,0 +1,99 @@
+#ifndef KSTD_BITS_FORMAT_VFORMAT_HPP
+#define KSTD_BITS_FORMAT_VFORMAT_HPP
+
+// IWYU pragma: private, include <kstd/format>
+
+#include <kstd/bits/format/args.hpp>
+#include <kstd/bits/format/output_buffer.hpp>
+#include <kstd/bits/format/string.hpp>
+#include <kstd/string>
+
+#include <algorithm>
+#include <iterator>
+#include <string_view>
+#include <type_traits>
+
+namespace kstd
+{
+
+ namespace bits::format
+ {
+ //! Format a string with the given arguments into the given buffer.
+ //!
+ //! External implementations of `vprint` may call this function.
+ //!
+ //! @param buffer The buffer to format into.
+ //! @param format The format string to use.
+ //! @param args The arguments for the format string.
+ auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void;
+
+ struct string_writer : output_buffer
+ {
+ auto push(std::string_view text) -> void override;
+ auto push(char character) -> void override;
+
+ auto release() -> string &&;
+
+ private:
+ string m_result{};
+ };
+
+ template<std::output_iterator<char> Output>
+ struct iterator_writer : output_buffer
+ {
+ explicit iterator_writer(Output iterator)
+ : m_output{iterator}
+ {}
+
+ [[nodiscard]] auto iterator() const -> Output
+ {
+ return m_output;
+ }
+
+ auto push(std::string_view text) -> void override
+ {
+ m_output = std::ranges::copy(text, m_output).out;
+ }
+
+ auto push(char character) -> void override
+ {
+ *m_output++ = character;
+ }
+
+ private:
+ Output m_output{};
+ };
+
+ } // namespace bits::format
+
+ //! Format a given string with the provided arguments.
+ //!
+ //! @param format The format string.
+ //! @param args The arguments for the format string.
+ //! @return A new string containing the result of the format operation.
+ template<typename... ArgumentTypes>
+ auto format(format_string<std::type_identity_t<ArgumentTypes>...> format, ArgumentTypes &&... args) -> string
+ {
+ auto buffer = bits::format::string_writer{};
+ bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args);
+ return buffer.release();
+ }
+
+ //! Format a given string with the provided arguments.
+ //!
+ //! @param iterator The iterator to write to.
+ //! @param format The format string.
+ //! @param args The arguments for the format string.
+ //! @return An iterator past the last element written.
+ template<std::output_iterator<char> Output, typename... ArgumentTypes>
+ auto format_to(Output iterator, format_string<std::type_identity_t<ArgumentTypes>...> format,
+ ArgumentTypes &&... args) -> Output
+ {
+ auto buffer = bits::format::iterator_writer{iterator};
+ bits::format::vformat_to(buffer, format.str_view, make_format_args(std::forward<ArgumentTypes>(args)...).args);
+ return buffer.iterator();
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/observer_ptr.hpp b/libs/kstd/kstd/bits/observer_ptr.hpp
new file mode 100644
index 0000000..2593d7a
--- /dev/null
+++ b/libs/kstd/kstd/bits/observer_ptr.hpp
@@ -0,0 +1,163 @@
+#ifndef KSTD_OBSERVER_PTR_HPP
+#define KSTD_OBSERVER_PTR_HPP
+
+// IWYU pragma: private, include <kstd/memory>
+
+#include <kstd/os/error.hpp>
+
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+
+ template<typename ElementType>
+ struct observer_ptr
+ {
+ //! The type of the element being pointed to.
+ using element_type = ElementType;
+
+ //! Construct an empty observer pointer.
+ constexpr observer_ptr() noexcept = default;
+
+ //! Construct an empty observer pointer from a null pointer.
+ constexpr observer_ptr(std::nullptr_t) noexcept {}
+
+ //! Construct an observer pointer from a raw pointer.
+ constexpr explicit observer_ptr(element_type * pointer)
+ : m_ptr{pointer}
+ {}
+
+ //! Construct an observer pointer from another observer pointer.
+ template<typename OtherElementType>
+ requires std::convertible_to<OtherElementType *, ElementType *>
+ constexpr observer_ptr(observer_ptr<OtherElementType> other) noexcept
+ : m_ptr{other.get()}
+ {}
+
+ //! Copy construct an observer pointer.
+ constexpr observer_ptr(observer_ptr const & other) noexcept = default;
+
+ //! Move construct an observer pointer.
+ constexpr observer_ptr(observer_ptr && other) noexcept = default;
+
+ //! Copy assign an observer pointer.
+ constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default;
+
+ //! Move assign an observer pointer.
+ constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default;
+
+ //! Stop watching the the watched object.
+ //!
+ //! @return The currently watched object, or nullptr if no object is being watched.
+ [[nodiscard]] constexpr auto release() noexcept -> element_type *
+ {
+ return std::exchange(m_ptr, nullptr);
+ }
+
+ //! Reset the observer pointer.
+ //!
+ //! @param pointer The new object to watch.
+ constexpr auto reset(element_type * pointer = nullptr) noexcept -> void
+ {
+ m_ptr = pointer;
+ }
+
+ //! Swap the observer pointer with another observer pointer.
+ //!
+ //! @param other The other observer pointer to swap with.
+ constexpr auto swap(observer_ptr & other) noexcept -> void
+ {
+ std::swap(m_ptr, other.m_ptr);
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return The currently watched object, or nullptr if no object is being watched.
+ [[nodiscard]] constexpr auto get() const noexcept -> element_type *
+ {
+ return m_ptr;
+ }
+
+ //! Check if the observer pointer is watching an object.
+ //!
+ //! @return True if the observer pointer is watching an object, false otherwise.
+ [[nodiscard]] constexpr explicit operator bool() const noexcept
+ {
+ return m_ptr != nullptr;
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return A reference to the currently watched object.
+ [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t<element_type>
+ {
+ throw_on_null();
+ return *m_ptr;
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return A pointer to the currently watched object.
+ [[nodiscard]] constexpr auto operator->() const -> element_type *
+ {
+ throw_on_null();
+ return m_ptr;
+ }
+
+ //! Convert the observer pointer to a raw pointer.
+ //!
+ //! @return A pointer to the currently watched object.
+ constexpr explicit operator element_type *() const noexcept
+ {
+ return m_ptr;
+ }
+
+ //! Compare the observer pointer with another observer pointer.
+ //!>
+ //! @param other The other observer pointer to compare with.
+ //! @return The result of the comparison.
+ constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default;
+
+ private:
+ //! Throw an exception if the observer pointer is null.
+ //!
+ //! @throws std::runtime_error if the observer pointer is null.
+ constexpr auto throw_on_null() const -> void
+ {
+ if (m_ptr == nullptr)
+ {
+ os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer");
+ }
+ }
+
+ //! The raw pointer to the watched object.
+ ElementType * m_ptr{};
+ };
+
+ //! Swap two observer pointers.
+ //!
+ //! @param lhs The first observer pointer to swap.
+ //! @param rhs The second observer pointer to swap.
+ template<typename ElementType>
+ constexpr auto swap(observer_ptr<ElementType> & lhs, observer_ptr<ElementType> & rhs) noexcept -> void
+ {
+ lhs.swap(rhs);
+ }
+
+ //! Create an observer pointer from a raw pointer.
+ //!
+ //! @param pointer The raw pointer to create an observer pointer from.
+ //! @return An observer pointer to the given raw pointer.
+ template<typename ElementType>
+ constexpr auto make_observer(ElementType * pointer) noexcept -> observer_ptr<ElementType>
+ {
+ return observer_ptr<ElementType>{pointer};
+ }
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/observer_ptr.test.cpp b/libs/kstd/kstd/bits/observer_ptr.test.cpp
new file mode 100644
index 0000000..1ba9c63
--- /dev/null
+++ b/libs/kstd/kstd/bits/observer_ptr.test.cpp
@@ -0,0 +1,360 @@
+#include <kstd/memory>
+#include <kstd/test_support/os_panic.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <compare>
+#include <type_traits>
+#include <utility>
+
+namespace
+{
+ struct Base
+ {
+ };
+
+ struct Derived : Base
+ {
+ };
+
+ struct Element
+ {
+ int value{};
+
+ constexpr auto operator<=>(Element const &) const noexcept = default;
+ };
+} // namespace
+
+SCENARIO("Observer Pointer initialization and construction", "[observer_ptr]")
+{
+ GIVEN("An empty context")
+ {
+ WHEN("constructing by default")
+ {
+ auto ptr = kstd::observer_ptr<int>{};
+
+ THEN("the observer pointer is null")
+ {
+ REQUIRE_FALSE(ptr);
+ }
+ }
+
+ WHEN("constructing from a nullptr")
+ {
+ auto ptr = kstd::observer_ptr<int>{nullptr};
+
+ THEN("the observer pointer is null")
+ {
+ REQUIRE_FALSE(ptr);
+ }
+ }
+
+ WHEN("constructing from a raw pointer")
+ {
+ auto value = 1;
+ auto ptr = kstd::observer_ptr{&value};
+
+ THEN("the observer pointer is not null")
+ {
+ REQUIRE(ptr);
+ }
+
+ THEN("the observer pointer points to the correct object")
+ {
+ REQUIRE(&*ptr == &value);
+ }
+ }
+
+ WHEN("copy constructing from an existing observer pointer")
+ {
+ auto value = 1;
+ auto ptr = kstd::observer_ptr{&value};
+ auto copy = ptr;
+
+ THEN("the new observer pointer points to the same object as the other observer pointer")
+ {
+ REQUIRE(&*copy == &value);
+ }
+ }
+
+ WHEN("copy constructing from an existing observer pointer with a compatible type")
+ {
+ auto value = Derived{};
+ auto ptr = kstd::observer_ptr<Derived>(&value);
+ kstd::observer_ptr<Base> copy = ptr;
+
+ THEN("the new observer pointer points to the same object as the other observer pointer")
+ {
+ REQUIRE(&*copy == &value);
+ }
+ }
+
+ WHEN("copy assigning from an existing observer pointer")
+ {
+ auto value = 1;
+ auto ptr = kstd::observer_ptr{&value};
+ auto copy = ptr;
+
+ THEN("the new observer pointer points to the same object as the other observer pointer")
+ {
+ REQUIRE(&*copy == &value);
+ }
+ }
+
+ WHEN("move constructing from an existing observer pointer")
+ {
+ auto value = 1;
+ auto ptr = kstd::observer_ptr{&value};
+ auto copy = std::move(ptr);
+
+ THEN("the new observer pointer points to the same object as the other observer pointer")
+ {
+ REQUIRE(&*copy == &value);
+ }
+ }
+
+ WHEN("move assigning from an existing observer pointer")
+ {
+ auto value = 1;
+ auto ptr = kstd::observer_ptr{&value};
+ auto copy = std::move(ptr);
+
+ THEN("the new observer pointer points to the same object as the other observer pointer")
+ {
+ REQUIRE(&*copy == &value);
+ }
+ }
+
+ WHEN("constructing an observer pointer using make_observer")
+ {
+ auto value = 1;
+ auto ptr = kstd::make_observer(&value);
+
+ THEN("the observer pointer points to the correct object")
+ {
+ REQUIRE(&*ptr == &value);
+ }
+
+ THEN("the observe pointer has the correct element type")
+ {
+ STATIC_REQUIRE(std::is_same_v<decltype(ptr), kstd::observer_ptr<int>>);
+ }
+ }
+ }
+}
+
+SCENARIO("Observer pointer modifiers", "[observer_ptr]")
+{
+ GIVEN("A non-null observer pointer")
+ {
+ auto value = 1;
+ auto ptr = kstd::observer_ptr{&value};
+
+ WHEN("releasing the observer pointer")
+ {
+ auto raw_ptr = ptr.release();
+
+ THEN("the observer pointer is null")
+ {
+ REQUIRE_FALSE(ptr);
+ }
+
+ THEN("the returned pointer points to the correct object")
+ {
+ REQUIRE(raw_ptr == &value);
+ }
+ }
+
+ WHEN("resetting the observer pointer to nullptr")
+ {
+ ptr.reset();
+
+ THEN("the observer pointer is null")
+ {
+ REQUIRE_FALSE(ptr);
+ }
+ }
+
+ WHEN("resetting the observer pointer to a new object")
+ {
+ auto other_value = 2;
+ ptr.reset(&other_value);
+
+ THEN("the observer pointer points to the new object")
+ {
+ REQUIRE(&*ptr == &other_value);
+ }
+ }
+
+ WHEN("swapping it with another observer pointer")
+ {
+ auto other_value = 2;
+ auto other_ptr = kstd::observer_ptr{&other_value};
+ ptr.swap(other_ptr);
+
+ THEN("the observer pointer points to the other object")
+ {
+ REQUIRE(&*ptr == &other_value);
+ }
+
+ THEN("the other observer pointer points to the original object")
+ {
+ REQUIRE(&*other_ptr == &value);
+ }
+ }
+
+ WHEN("using namespace-level swap to swap it with another observer pointer")
+ {
+ using std::swap;
+ auto other_value = 2;
+ auto other_ptr = kstd::observer_ptr{&other_value};
+ swap(ptr, other_ptr);
+
+ THEN("the observer pointer points to the other object")
+ {
+ REQUIRE(&*ptr == &other_value);
+ }
+
+ THEN("the other observer pointer points to the original object")
+ {
+ REQUIRE(&*other_ptr == &value);
+ }
+ }
+ }
+}
+
+SCENARIO("Observer pointer observers", "[observer_ptr]")
+{
+ GIVEN("A non-null observer pointer")
+ {
+ auto value = Element{1};
+ auto ptr = kstd::observer_ptr{&value};
+
+ WHEN("getting the raw pointer")
+ {
+ auto raw_ptr = ptr.get();
+
+ THEN("the raw pointer points to the correct object")
+ {
+ REQUIRE(raw_ptr == &value);
+ }
+ }
+
+ WHEN("dereferencing the observer pointer")
+ {
+ auto dereferenced = *ptr;
+
+ THEN("the dereferenced value is the correct value")
+ {
+ REQUIRE(dereferenced == value);
+ }
+ }
+
+ WHEN("writing through the observer pointer with the arrow operator")
+ {
+ ptr->value = 2;
+
+ THEN("the value is updated")
+ {
+ REQUIRE(value.value == 2);
+ }
+ }
+
+ WHEN("converting the observer pointer to a raw pointer")
+ {
+ auto raw_ptr = static_cast<Element *>(ptr);
+
+ THEN("the raw pointer points to the correct object")
+ {
+ REQUIRE(raw_ptr == &value);
+ }
+ }
+
+ WHEN("checking the observer pointer as a boolean")
+ {
+ THEN("it returns true")
+ {
+ REQUIRE(static_cast<bool>(ptr));
+ }
+ }
+ }
+
+ GIVEN("A null observer pointer")
+ {
+ auto ptr = kstd::observer_ptr<Element>{};
+
+ WHEN("checking the observer pointer as a boolean")
+ {
+ THEN("it returns false")
+ {
+ REQUIRE_FALSE(static_cast<bool>(ptr));
+ }
+ }
+
+ WHEN("dereferencing the observer pointer")
+ {
+ THEN("the observer pointer panics")
+ {
+ REQUIRE_THROWS_AS(*ptr, kstd::tests::os_panic);
+ }
+ }
+
+ WHEN("writing through the observer pointer with the arrow operator")
+ {
+ THEN("the observer pointer panics")
+ {
+ REQUIRE_THROWS_AS(ptr->value = 2, kstd::tests::os_panic);
+ }
+ }
+ }
+}
+
+SCENARIO("Observer pointer comparisons", "[observer_ptr]")
+{
+ GIVEN("Observer pointers to elements of an array")
+ {
+ auto arr = std::array{1, 2};
+ auto ptr1 = kstd::observer_ptr{&arr[0]};
+ auto ptr2 = kstd::observer_ptr{&arr[1]};
+
+ WHEN("comparing the same observer pointer")
+ {
+ THEN("they are equal")
+ {
+ REQUIRE(ptr1 == ptr1);
+ REQUIRE((ptr1 <=> ptr1) == std::strong_ordering::equal);
+ }
+ }
+
+ WHEN("comparing different observer pointers")
+ {
+ THEN("they are ordered correctly")
+ {
+ REQUIRE(ptr1 != ptr2);
+ REQUIRE(ptr1 < ptr2);
+ REQUIRE(ptr1 <= ptr2);
+ REQUIRE(ptr2 > ptr1);
+ REQUIRE(ptr2 >= ptr1);
+ REQUIRE((ptr1 <=> ptr2) == std::strong_ordering::less);
+ REQUIRE((ptr2 <=> ptr1) == std::strong_ordering::greater);
+ }
+ }
+ }
+
+ GIVEN("A null observer pointer")
+ {
+ auto ptr = kstd::observer_ptr<int>{};
+
+ WHEN("comparing with another null observer pointer")
+ {
+ auto other_ptr = kstd::observer_ptr<int>{};
+
+ THEN("they are equal")
+ {
+ REQUIRE(ptr == other_ptr);
+ REQUIRE((ptr <=> other_ptr) == std::strong_ordering::equal);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/print_sink.hpp b/libs/kstd/kstd/bits/print_sink.hpp
index 0e0955c..af765e0 100644
--- a/libs/kstd/include/kstd/bits/print_sink.hpp
+++ b/libs/kstd/kstd/bits/print_sink.hpp
@@ -3,8 +3,6 @@
// IWYU pragma: private, include <kstd/print>
-#include <kstd/format>
-
namespace kstd
{
diff --git a/libs/kstd/kstd/bits/shared_ptr.hpp b/libs/kstd/kstd/bits/shared_ptr.hpp
new file mode 100644
index 0000000..8930095
--- /dev/null
+++ b/libs/kstd/kstd/bits/shared_ptr.hpp
@@ -0,0 +1,648 @@
+#ifndef KSTD_BITS_SHARED_PTR_HPP
+#define KSTD_BITS_SHARED_PTR_HPP
+
+#include <atomic>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+// IWYU pragma: private, include <kstd/memory>
+
+namespace kstd
+{
+ /**
+ * @brief Control block for shared_ptr and weak_ptr. This control block contains the reference counts for shared
+ * ownership and weak ownership. The shared_count tracks the number of shared_ptr instances that own the managed
+ * object, while the weak_count tracks the number of weak_ptr instances that reference the managed object.
+ * The weak_count is needed to determine when it is safe to delete the shared_control_block itself
+ */
+ struct shared_control_block
+ {
+ std::atomic<std::size_t> shared_count;
+ std::atomic<std::size_t> weak_count;
+
+ explicit shared_control_block(std::size_t shared = 1, std::size_t weak = 0)
+ : shared_count(shared)
+ , weak_count(weak)
+ {}
+ };
+
+ template<typename T>
+ struct shared_ptr;
+
+ /**
+ * @brief weak_ptr is a smart pointer that holds a non-owning weak reference to an object that is managed by
+ * shared_ptr. It must be converted to shared_ptr in to be able to access the referenced object. A weak_ptr is created
+ * as a copy of a shared_ptr, or as a copy of another weak_ptr. A weak_ptr is typically used to break circular
+ * references between shared_ptr to avoid memory leaks. A weak_ptr does not contribute to the reference count of the
+ * object, and it does not manage the lifetime of the object. If the object managed by shared_ptr is destroyed, the
+ * weak_ptr becomes expired and cannot be used to access the object anymore.
+ */
+ template<typename T>
+ struct weak_ptr
+ {
+ template<typename U>
+ friend struct shared_ptr;
+
+ /**
+ * @brief Constructs a null weak_ptr.
+ */
+ weak_ptr() noexcept
+ : pointer(nullptr)
+ , control(nullptr)
+ {}
+
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ weak_ptr(shared_ptr<U> const & other)
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ if (control != nullptr)
+ {
+ ++(control->weak_count);
+ }
+ }
+
+ /**
+ * @brief Copy constructor. Constructs a weak_ptr which shares ownership of the object managed by other.
+ */
+ weak_ptr(weak_ptr const & other)
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ if (control != nullptr)
+ {
+ ++(control->weak_count);
+ }
+ }
+
+ /**
+ * @brief Move constructor. Constructs a weak_ptr which takes ownership of the object managed by other.
+ */
+ weak_ptr(weak_ptr && other) noexcept
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ /**
+ * @brief Assignment operator. Assigns the weak_ptr to another weak_ptr.
+ */
+ auto operator=(weak_ptr const & other) -> weak_ptr &
+ {
+ if (this != &other)
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+ if (control != nullptr)
+ {
+ ++(control->weak_count);
+ }
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Move assignment operator. Move-assigns a weak_ptr from other.
+ */
+ auto operator=(weak_ptr && other) noexcept -> weak_ptr &
+ {
+ if (this != &other)
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Destructor. Cleans up resources if necessary.
+ */
+ ~weak_ptr()
+ {
+ cleanup();
+ }
+
+ /**
+ * @brief Returns a shared_ptr that shares ownership of the managed object if the managed object still exists, or an
+ * empty shared_ptr otherwise.
+ */
+ [[nodiscard]] auto lock() const -> shared_ptr<T>
+ {
+ return shared_ptr<T>(*this);
+ }
+
+ private:
+ auto cleanup() -> void
+ {
+ if (control != nullptr)
+ {
+ if (--(control->weak_count) == 0 && control->shared_count == 0)
+ {
+ delete control;
+ }
+ }
+ }
+
+ T * pointer;
+ shared_control_block * control;
+ };
+
+ /**
+ * @brief enable_shared_from_this is a base class that allows an object that is currently managed by a shared_ptr to
+ * create additional shared_ptr instances that share ownership of the same object. This is usefl when you want to
+ * create shared_ptr instances in a member function of the object.
+ *
+ * @tparam T The type of the managed object.
+ */
+ template<typename T>
+ struct enable_shared_from_this
+ {
+ template<typename U>
+ friend struct shared_ptr;
+
+ friend T;
+
+ public:
+ /**
+ * @brief Returns a shared_ptr that shares ownership of *this.
+ */
+ auto shared_from_this() -> shared_ptr<T>
+ {
+ return shared_ptr<T>(weak_this);
+ }
+
+ /**
+ * @brief Returns a shared_ptr that shares ownership of *this.
+ */
+ auto shared_from_this() const -> shared_ptr<T const>
+ {
+ return shared_ptr<T const>(weak_this);
+ }
+
+ private:
+ enable_shared_from_this() = default;
+ enable_shared_from_this(enable_shared_from_this const &) = default;
+ auto operator=(enable_shared_from_this const &) -> enable_shared_from_this & = default;
+ ~enable_shared_from_this() = default;
+
+ void internal_assign_ptr(shared_ptr<T> const & ptr) const
+ {
+ weak_this = ptr;
+ }
+
+ mutable weak_ptr<T> weak_this{}; ///< Weak pointer to the object, used for shared_from_this functionality.
+ };
+
+ /**
+ * @brief Shared_pointer is a smart pointer that retains shared ownership of an object through a pointer. Several
+ * shared_ptr objects may own the same object. The object is destroyed and its memory deallocated when either of
+ * the following happens: the last remaining shared_ptr owning the object is destroyed; the last remaining
+ * shared_ptr owning the object is assigned another pointer via operator= or reset(). A
+ * shared_ptr can share ownership of an object while storing a pointer to another object. This feature can be used
+ * to point to member objects while owning the object they belong to. The stored pointer is the one accessed by get(),
+ * the dereference and the comparison operators. The managed pointer is the one passed to the deleter when use count
+ * reaches zero.
+ *
+ * @tparam T The type of the managed object.
+ */
+ template<typename T>
+ struct shared_ptr
+ {
+ template<typename U>
+ friend struct shared_ptr;
+
+ template<typename U>
+ friend struct weak_ptr;
+
+ /**
+ * @brief Construct an empty shared_ptr.
+ */
+ shared_ptr() noexcept
+ : pointer(nullptr)
+ , control(nullptr)
+ {}
+
+ /**
+ * @brief Construct an empty shared_ptr from nullptr.
+ */
+ shared_ptr(std::nullptr_t) noexcept
+ : pointer(nullptr)
+ , control(nullptr)
+ {}
+
+ /**
+ * @brief Constructor.
+ *
+ * @param pointer A pointer to an object to manage (default is nullptr).
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ explicit shared_ptr(U * pointer = nullptr)
+ : pointer(pointer)
+ , control(pointer != nullptr ? new shared_control_block() : nullptr)
+ {
+ assign_enable_shared_from_this(pointer);
+ }
+
+ /**
+ * @brief Constructor a shared_ptr from a weak_ptr. If other is not expired, constructs a shared_ptr which shares
+ * ownership of the object managed by other. Otherwise, constructs an empty shared_ptr.
+ *
+ * @param other The weak_ptr to construct from.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ explicit shared_ptr(weak_ptr<U> const & other)
+ : pointer(nullptr)
+ , control(nullptr)
+ {
+ if (other.control != nullptr && other.control->shared_count != 0)
+ {
+ pointer = other.pointer;
+ control = other.control;
+ ++(control->shared_count);
+ }
+ }
+
+ /**
+ * @brief Copy constructor.
+ *
+ * @param other The shared_ptr to copy from.
+ */
+ shared_ptr(shared_ptr const & other)
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+ }
+
+ /**
+ * @brief Converting copy constructor for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other The shared_ptr to copy from.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ shared_ptr(shared_ptr<U> const & other)
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+ }
+
+ /**
+ * @brief Move constructor.
+ *
+ * @param other The shared_ptr to move from.
+ */
+ shared_ptr(shared_ptr && other) noexcept
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ /**
+ * @brief Converting move constructor for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other The shared_ptr to move from.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ shared_ptr(shared_ptr<U> && other) noexcept
+ : pointer(other.pointer)
+ , control(other.control)
+ {
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ /**
+ * @brief Copy assignment operator. Replaces the managed object with the one managed by r. Shares ownership of the
+ * object managed by r. If r manages no object, *this manages no object too. Equivalent to
+ * shared_ptr<T>(r).swap(*this).
+ *
+ * @param other Another smart pointer to share the ownership with.
+ * @return Reference to this shared pointer.
+ */
+ auto operator=(shared_ptr const & other) -> shared_ptr &
+ {
+ if (this != &other)
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Converting copy assignment for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other Another smart pointer to share ownership with.
+ * @return Reference to this shared pointer.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ auto operator=(shared_ptr<U> const & other) -> shared_ptr &
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+
+ if (control != nullptr)
+ {
+ ++(control->shared_count);
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Move assignment operator. Move-assigns a shared_ptr from r. After the assignment, *this contains a copy of
+ * the previous state of r, and r is empty. Equivalent to shared_ptr<T>(std::move(r)).swap(*this).
+ *
+ * @param other Another smart pointer to acquire the ownership from.
+ * @return Reference to this shared pointer.
+ */
+ auto operator=(shared_ptr && other) noexcept -> shared_ptr &
+ {
+ if (this != &other)
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+ other.pointer = nullptr;
+ other.control = nullptr;
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Converting move assignment for compatible shared_ptr types.
+ *
+ * @tparam U Source pointer element type.
+ * @param other Another smart pointer to acquire ownership from.
+ * @return Reference to this shared pointer.
+ */
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ auto operator=(shared_ptr<U> && other) noexcept -> shared_ptr &
+ {
+ cleanup();
+ pointer = other.pointer;
+ control = other.control;
+ other.pointer = nullptr;
+ other.control = nullptr;
+
+ return *this;
+ }
+
+ /**
+ * @brief Reset this shared_ptr to empty via nullptr assignment.
+ */
+ auto operator=(std::nullptr_t) noexcept -> shared_ptr &
+ {
+ cleanup();
+ pointer = nullptr;
+ control = nullptr;
+ return *this;
+ }
+
+ /**
+ * @brief Destructor. Cleans up resources if necessary.
+ */
+ ~shared_ptr()
+ {
+ cleanup();
+ }
+
+ /**
+ * @brief Replaces the managed object.
+ *
+ * @param ptr Pointer to a new object to manage (default = nullptr).
+ */
+ void reset(T * ptr = nullptr)
+ {
+ cleanup();
+ pointer = ptr;
+ control = ptr != nullptr ? new shared_control_block() : nullptr;
+ assign_enable_shared_from_this(ptr);
+ }
+
+ /**
+ * @brief Exchanges the stored pointer values and the ownerships of *this and r. Reference counts, if any, are not
+ * adjusted.
+ *
+ * @param other The shared_ptr to swap with.
+ */
+ void swap(shared_ptr & other)
+ {
+ std::swap(pointer, other.pointer);
+ std::swap(control, other.control);
+ }
+
+ /**
+ * @brief Dereference operator. If get() is a null pointer, the behavior is undefined.
+ *
+ * @return Returns the object owned by *this, equivalent to *get().
+ */
+ [[nodiscard]] auto operator*() const -> T &
+ {
+ return *pointer;
+ }
+
+ /**
+ * @brief Member access operator.
+ *
+ * @return Returns a pointer to the object owned by *this, i.e. get().
+ */
+ [[nodiscard]] auto operator->() const -> T *
+ {
+ return pointer;
+ }
+
+ /**
+ * @brief Returns a pointer to the managed object or nullptr if no object is owned.
+ *
+ * @return Pointer to the managed object or nullptr if no object is owned.
+ */
+ [[nodiscard]] auto get() const -> T *
+ {
+ return pointer;
+ }
+
+ /**
+ * @brief Returns the number of different shared_ptr instances (*this included) managing the current object. If
+ * there is no managed object, ​0​ is returned.
+ *
+ * @note Common use cases include comparison with ​0​. If use_count returns zero, the shared pointer is empty
+ * and manages no objects (whether or not its stored pointer is nullptr). Comparison with 1. If use_count returns 1,
+ * there are no other owners.
+ *
+ * @return The number of Shared_pointer instances managing the current object or ​0​ if there is no managed
+ * object.
+ */
+ [[nodiscard]] auto use_count() const -> std::size_t
+ {
+ if (control != nullptr)
+ {
+ return control->shared_count;
+ }
+
+ return 0;
+ }
+
+ /**
+ * @brief Checks whether *this owns an object, i.e. whether get() != nullptr.
+ *
+ * @return true if *this owns an object, false otherwise.
+ */
+ [[nodiscard]] explicit operator bool() const
+ {
+ return pointer != nullptr;
+ }
+
+ /**
+ * @brief Compare shared_ptr with nullptr.
+ */
+ [[nodiscard]] auto operator==(std::nullptr_t) const -> bool
+ {
+ return pointer == nullptr;
+ }
+
+ /**
+ * @brief Compare nullptr with shared_ptr.
+ */
+ [[nodiscard]] friend auto operator==(std::nullptr_t, shared_ptr const & ptr) -> bool
+ {
+ return ptr.pointer == nullptr;
+ }
+
+ private:
+ /**
+ * @brief If the candidate type inherits from enable_shared_from_this, assigns the internal weak pointer to this
+ * shared_ptr. This weak_ptr is used to implement shared_from_this functionality for the candidate type. If the
+ * candidate type does not inherit from enable_shared_from_this, this function does nothing.
+ *
+ * @tparam U The candidate type to check for enable_shared_from_this inheritance.
+ * @param candidate The candidate object to assign the internal weak pointer for.
+ */
+ template<typename U>
+ auto assign_enable_shared_from_this(U * candidate) -> void
+ {
+ if constexpr (requires(U * p, shared_ptr<T> const & sp) { p->internal_assign_ptr(sp); })
+ {
+ if (candidate != nullptr)
+ {
+ candidate->internal_assign_ptr(*this);
+ }
+ }
+ }
+
+ /**
+ * @brief Releases ownership and deletes the object if this was the last reference to the owned managed object.
+ */
+ auto cleanup() -> void
+ {
+ if (control != nullptr)
+ {
+ if (--(control->shared_count) == 0)
+ {
+ delete pointer;
+ pointer = nullptr;
+
+ if (control->weak_count == 0)
+ {
+ delete control;
+ }
+ }
+ }
+ }
+
+ T * pointer; ///< The managed object.
+ shared_control_block * control; ///< Shared control block.
+ };
+
+ /**
+ * @brief Specializes the std::swap algorithm for stl::unique_ptr. Swaps the contents of lhs and rhs. Calls
+ * lhs.swap(rhs).
+ *
+ * @tparam T Type of the managed object.
+ * @param lhs, rhs Smart pointers whose contents to swap.
+ */
+ template<typename T>
+ auto swap(shared_ptr<T> & lhs, shared_ptr<T> & rhs) -> void
+ {
+ lhs.swap(rhs);
+ }
+
+ /**
+ * @brief Constructs an object of type T and wraps it in a shared_ptr. Constructs a non-array type T. The
+ * arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is
+ * not an array type. The function is equivalent to: shared_ptr<T>(new T(std::forward<Args>(args)...)).
+ *
+ * @tparam T Type of the managed object.
+ * @tparam Args Argument types for T's constructor.
+ * @param args List of arguments with which an instance of T will be constructed.
+ * @returns Shared_pointer of an instance of type T.
+ */
+ template<typename T, typename... Args>
+ auto make_shared(Args &&... args) -> shared_ptr<T>
+ {
+ return shared_ptr<T>(new T(std::forward<Args>(args)...));
+ }
+
+ /**
+ * @brief Equality operator for shared_ptr. Two shared_ptr instances are equal if they point to the same object
+ * @tparam T, U Types of the managed objects of the shared_ptr instances being compared.
+ * @param lhs, rhs The shared_ptr instances to compare.
+ * @return true if lhs and rhs point to the same object, false otherwise.
+ */
+ template<typename T, typename U>
+ [[nodiscard]] auto inline operator==(shared_ptr<T> const & lhs, shared_ptr<U> const & rhs) -> bool
+ {
+ return lhs.get() == rhs.get();
+ }
+
+ /**
+ * @brief Three-way comparison operator for shared_ptr. Compares the stored pointers of lhs and rhs using operator<=>.
+ * @tparam T, U Types of the managed objects of the shared_ptr instances being compared.
+ * @param lhs, rhs The shared_ptr instances to compare.
+ * @return The result of comparing the stored pointers of lhs and rhs using operator<=>
+ */
+ template<typename T, typename U>
+ [[nodiscard]] auto inline operator<=>(shared_ptr<T> const & lhs, shared_ptr<U> const & rhs)
+ {
+ return lhs.get() <=> rhs.get();
+ }
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/bits/unique_ptr.hpp b/libs/kstd/kstd/bits/unique_ptr.hpp
index e0870b1..3d803b4 100644
--- a/libs/kstd/include/kstd/bits/unique_ptr.hpp
+++ b/libs/kstd/kstd/bits/unique_ptr.hpp
@@ -16,6 +16,9 @@ namespace kstd
template<typename T>
struct unique_ptr
{
+ template<typename U>
+ friend struct unique_ptr;
+
/**
* @brief Constructor.
*
@@ -40,6 +43,12 @@ namespace kstd
*/
unique_ptr(unique_ptr const &) = delete;
+ template<typename U>
+ requires(std::is_convertible_v<U *, T *>)
+ unique_ptr(unique_ptr<U> && other) noexcept
+ : pointer{std::exchange(other.pointer, nullptr)}
+ {}
+
/**
* @brief Deleted copy assignment operator to enforce unique ownership.
*/
@@ -51,10 +60,8 @@ namespace kstd
* @param other Unique pointer to move from.
*/
unique_ptr(unique_ptr && other) noexcept
- : pointer(other.pointer)
- {
- other.pointer = nullptr;
- }
+ : pointer{std::exchange(other.pointer, nullptr)}
+ {}
/**
* @brief Move assignment operator. Transfers ownership from other to *this as if by calling reset(r.release()).
@@ -67,8 +74,7 @@ namespace kstd
if (this != &other)
{
delete pointer;
- pointer = other.pointer;
- other.pointer = nullptr;
+ pointer = std::exchange(other.pointer, nullptr);
}
return *this;
}
@@ -123,9 +129,7 @@ namespace kstd
*/
auto release() -> T *
{
- T * temp = pointer;
- pointer = nullptr;
- return temp;
+ return std::exchange(pointer, nullptr);
}
/**
@@ -139,8 +143,7 @@ namespace kstd
*/
auto reset(T * ptr = nullptr) -> void
{
- delete pointer;
- pointer = ptr;
+ delete std::exchange(pointer, ptr);
}
/**
diff --git a/libs/kstd/kstd/cstring b/libs/kstd/kstd/cstring
new file mode 100644
index 0000000..bd8b28d
--- /dev/null
+++ b/libs/kstd/kstd/cstring
@@ -0,0 +1,21 @@
+#ifndef KSTD_CSTRING
+#define KSTD_CSTRING
+
+#include <cstddef>
+
+namespace kstd::libc
+{
+
+ extern "C"
+ {
+ auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *;
+ auto memset(void * dest, int value, std::size_t size) noexcept -> void *;
+ auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *;
+ auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int;
+
+ auto strlen(char const * string) noexcept -> std::size_t;
+ }
+
+} // namespace kstd::libc
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/ext/bitfield_enum b/libs/kstd/kstd/ext/bitfield_enum
index 80fe9d2..80fe9d2 100644
--- a/libs/kstd/include/kstd/ext/bitfield_enum
+++ b/libs/kstd/kstd/ext/bitfield_enum
diff --git a/libs/kstd/kstd/flat_map b/libs/kstd/kstd/flat_map
new file mode 100644
index 0000000..e51eb91
--- /dev/null
+++ b/libs/kstd/kstd/flat_map
@@ -0,0 +1,406 @@
+#ifndef KSTD_FLAT_MAP_HPP
+#define KSTD_FLAT_MAP_HPP
+
+#include <kstd/bits/flat_map.hpp>
+#include <kstd/os/error.hpp>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <functional>
+#include <iterator>
+#include <utility>
+
+namespace kstd
+{
+
+ template<typename KeyType, typename MappedType, typename KeyCompare = std::less<KeyType>,
+ typename KeyContainerType = kstd::vector<KeyType>, typename MappedContainerType = kstd::vector<MappedType>>
+ struct flat_map
+ {
+ //! The type of container used to store the map keys.
+ using key_container_type = KeyContainerType;
+
+ //! The type of container used to store the map values.
+ using mapped_container_type = MappedContainerType;
+
+ //! The type of the map keys.
+ using key_type = KeyType;
+
+ //! The type of the mappe values.
+ using mapped_type = MappedType;
+
+ //! The type of a single key-value value in the map.
+ using value_type = std::pair<key_type, mapped_type>;
+
+ //! The comparator used to sort the keys.
+ using key_compare = KeyCompare;
+
+ using reference = std::pair<key_type const &, mapped_type &>;
+ using const_reference = std::pair<key_type const &, mapped_type const &>;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+ using iterator = bits::flat_map_iterator<KeyType, MappedType, typename key_container_type::iterator,
+ typename mapped_container_type::iterator>;
+ using const_iterator =
+ bits::flat_map_iterator<KeyType, MappedType const, typename key_container_type::const_iterator,
+ typename mapped_container_type::const_iterator>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using containers = struct
+ {
+ key_container_type keys;
+ mapped_container_type values;
+ };
+
+ //! Compare two object of type value_type.
+ struct value_compare
+ {
+ constexpr auto operator()(const_reference lhs, const_reference rhs) const -> bool
+ {
+ return lhs.first < rhs.first;
+ }
+ };
+
+ //! Construct an empty flat map.
+ constexpr flat_map()
+ : flat_map{key_compare{}}
+ {}
+
+ //! Construct an empty flat map using the given custom comparator.
+ //!
+ //! @param comparator The comparator to use for comparing keys.
+ constexpr explicit flat_map(key_compare const & comparator)
+ : m_containers{}
+ , m_comparator{comparator}
+ {}
+
+ //! Replace the contents of this flat map with the one of a different one.
+ //!
+ //! @param other the flat map to copy from.
+ //! @return A reference to this flat map.
+ auto operator=(flat_map const & other) -> flat_map &
+ {
+ if (this != &other)
+ {
+ std::tie(m_containers, m_comparator) = std::tie(other.m_containers, other.m_comparator);
+ }
+ return *this;
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //!
+ //! @warning This function will panic if the key is not found.
+ //! @param key The key to look up.
+ //! @return A reference to the mapped value.
+ [[nodiscard]] constexpr auto at(key_type const & key) -> mapped_type &
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return *(m_containers.values.begin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //!
+ //! @warning This function will panic if the key is not found.
+ //! @param key The key to look up.
+ //! @return A const reference to the mapped value.
+ [[nodiscard]] constexpr auto at(key_type const & key) const -> mapped_type const &
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ return *(m_containers.values.cbegin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //!
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @warning This function will panic if the key is not found.
+ //! @param x The key to look up.
+ //! @return A reference to the mapped value.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] constexpr auto at(K const & x) -> mapped_type &
+ {
+ auto found = find(x);
+ if (found != end())
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found.key_iterator());
+ return *(m_containers.values.begin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get a reference to the mapped value associated with the given key.
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @warning This function will panic if the key is not found.
+ //! @param x The key to look up.
+ //! @return A const reference to the mapped value.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] auto at(K const & x) const -> mapped_type const &
+ {
+ auto found = find(x);
+ if (found != end())
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found.key_iterator());
+ return *(m_containers.values.cbegin() + offset);
+ }
+ os::panic("[kstd::flat_map] Key not found");
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto begin() noexcept -> iterator
+ {
+ return iterator{m_containers.keys.begin(), m_containers.values.begin()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto begin() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto cbegin() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cbegin(), m_containers.values.cbegin()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto end() noexcept -> iterator
+ {
+ return iterator{m_containers.keys.end(), m_containers.values.end()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto end() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cend(), m_containers.values.cend()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto cend() const noexcept -> const_iterator
+ {
+ return const_iterator{m_containers.keys.cend(), m_containers.values.cend()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto rbegin() noexcept -> reverse_iterator
+ {
+ return reverse_iterator{end()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cend()};
+ }
+
+ //! Get an iterator to the first element.
+ [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cend()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto rend() noexcept -> reverse_iterator
+ {
+ return reverse_iterator{begin()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cbegin()};
+ }
+
+ //! Get an iterator to the element past the last element.
+ [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{cbegin()};
+ }
+
+ //! Check whether this flat map is empty or not
+ [[nodiscard]] auto empty() const noexcept -> bool
+ {
+ return m_containers.keys.empty();
+ }
+
+ //! Get the number of elements in this flat map.
+ [[nodiscard]] auto size() const noexcept -> size_type
+ {
+ return m_containers.keys.size();
+ }
+
+ //! Get the maximum number of elements possible in this flat map
+ [[nodiscard]] auto max_size() const noexcept -> size_type
+ {
+ return std::min(m_containers.keys.max_size(), m_containers.values.max_size());
+ }
+
+ //! Try to insert a new key-value pair into the map.
+ //!
+ //! @param args Arguments to use for constructing the key-value pair.
+ //! @return A pair of an iterator to the inserted element and a boolean indicating whether the insertion took place.
+ template<typename... Args>
+ auto emplace(Args &&... args) -> std::pair<iterator, bool>
+ requires std::constructible_from<value_type, Args...>
+ {
+ auto value = value_type{std::forward<Args>(args)...};
+ auto found = std::ranges::lower_bound(m_containers.keys, value.first, m_comparator);
+
+ if (found != m_containers.keys.cend() && !m_comparator(value.first, *found) && !m_comparator(*found, value.first))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return {
+ iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset},
+ false
+ };
+ }
+
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ auto key_iterator = m_containers.keys.begin() + offset;
+ auto mapped_iterator = m_containers.values.begin() + offset;
+
+ auto inserted_key = m_containers.keys.insert(key_iterator, std::move(value.first));
+ auto inserted_mapped = m_containers.values.insert(mapped_iterator, std::move(value.second));
+
+ return {
+ iterator{inserted_key, inserted_mapped},
+ true
+ };
+ }
+
+ //! Try to insert a element for the given key into this map.
+ //!
+ //! This function does nothing if the key is already present.
+ //!
+ //! @param key The key to insert a value for.
+ //! @param args The arguments to use to construct the mapped value.
+ template<typename... Args>
+ auto try_emplace(key_type const & key, Args &&... args) -> std::pair<iterator, bool>
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(*found, key) && !m_comparator(key, *found))
+ {
+ return {found, false};
+ }
+
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ auto intersertion_point = m_containers.value.begin() + offset;
+
+ auto inserted_key = m_containers.keys.emplace(key);
+ auto inserted_mapped = m_containers.values.emplace(std::forward<Args>(args)...);
+
+ return {
+ iterator{inserted_key, inserted_mapped},
+ true
+ };
+ }
+
+ //! Find an element with an equivalent key.
+ //!
+ //! @param key The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ [[nodiscard]] auto find(key_type const & key) noexcept -> iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset};
+ }
+ return end();
+ }
+
+ //! Find an element with an equivalent key.
+ //!
+ //! @param key The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ [[nodiscard]] auto find(key_type const & key) const noexcept -> const_iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, key, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(key, *found) && !m_comparator(*found, key))
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset};
+ }
+ return cend();
+ }
+
+ //! Find an element with an equivalent key.
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @param x The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] auto find(K const & x) noexcept -> iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x))
+ {
+ auto offset = std::distance(m_containers.keys.begin(), found);
+ return iterator{m_containers.keys.begin() + offset, m_containers.values.begin() + offset};
+ }
+ return end();
+ }
+
+ //! Find an element with an equivalent key.
+ //! @note This overload only participates in overload resolution if the key compare type is transparent.
+ //! @param x The key to look up.
+ //! @return An iterator to the element with the equivalent key, or end() if no such element is found.
+ template<typename K>
+ requires requires(K const & k, key_type const & l) { typename key_compare::is_transparent; }
+ [[nodiscard]] auto find(K const & x) const noexcept -> const_iterator
+ {
+ auto found = std::ranges::lower_bound(m_containers.keys, x, m_comparator);
+ if (found != m_containers.keys.cend() && !m_comparator(x, *found) && !m_comparator(*found, x))
+ {
+ auto offset = std::distance(m_containers.keys.cbegin(), found);
+ return const_iterator{m_containers.keys.cbegin() + offset, m_containers.values.cbegin() + offset};
+ }
+ return cend();
+ }
+
+ //! Check if the map contains the given key.
+ //!
+ //! @param key The key to check.
+ //! @return true iff. the key is found, false otherwise.
+ [[nodiscard]] constexpr auto contains(key_type const & key) const noexcept -> bool
+ {
+ return find(key) != cend();
+ }
+
+ //! Get a reference to the keys container.
+ //!
+ //! @return a reference to the keys container.
+ [[nodiscard]] constexpr auto keys() const noexcept -> key_container_type const &
+ {
+ return m_containers.keys;
+ }
+
+ //! Get a reference to the values container.
+ //!
+ //! @return a reference to the values container.
+ [[nodiscard]] constexpr auto values() const noexcept -> mapped_container_type const &
+ {
+ return m_containers.values;
+ }
+
+ private:
+ containers m_containers;
+ key_compare m_comparator;
+ };
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/flat_map.test.cpp b/libs/kstd/kstd/flat_map.test.cpp
new file mode 100644
index 0000000..9df03bb
--- /dev/null
+++ b/libs/kstd/kstd/flat_map.test.cpp
@@ -0,0 +1,351 @@
+#include <kstd/flat_map>
+
+#include <kstd/test_support/os_panic.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+SCENARIO("Flat Map initialization and construction", "[flat_map]")
+{
+ GIVEN("An empty context")
+ {
+ WHEN("constructing by default")
+ {
+ auto map = kstd::flat_map<int, int>{};
+
+ THEN("the Flat Map does not contain elements")
+ {
+ REQUIRE_FALSE(map.contains(1));
+ }
+
+ THEN("the keys container is empty")
+ {
+ REQUIRE(map.keys().empty());
+ }
+
+ THEN("the values container is empty")
+ {
+ REQUIRE(map.values().empty());
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map modifiers", "[flat_map]")
+{
+ GIVEN("An empty Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+
+ WHEN("emplacing a new element")
+ {
+ auto [it, inserted] = map.emplace(1, 100);
+
+ THEN("the map contains the new element")
+ {
+ REQUIRE(inserted);
+ REQUIRE(map.contains(1));
+ }
+ }
+
+ WHEN("emplacing an existing element")
+ {
+ map.emplace(1, 100);
+ auto [it, inserted] = map.emplace(1, 200);
+
+ THEN("the map does not insert the duplicate")
+ {
+ REQUIRE_FALSE(inserted);
+ REQUIRE(map.contains(1));
+ }
+ }
+
+ AND_GIVEN("a populated Flat Map")
+ {
+ auto other = kstd::flat_map<int, int>{};
+ other.emplace(1, 10);
+ other.emplace(2, 20);
+ other.emplace(3, 30);
+
+ WHEN("assigning the populated Flat Map to the empty one")
+ {
+ map = other;
+
+ THEN("the elements are copied")
+ {
+ REQUIRE(map.at(1) == 10);
+ REQUIRE(map.at(2) == 20);
+ REQUIRE(map.at(3) == 30);
+ }
+
+ THEN("the elements are still in the populated Flat Map")
+ {
+ REQUIRE(other.at(1) == 10);
+ REQUIRE(other.at(2) == 20);
+ REQUIRE(other.at(3) == 30);
+ }
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map element access", "[flat_map]")
+{
+ GIVEN("A populated Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+ map.emplace(1, 10);
+ map.emplace(2, 20);
+ map.emplace(3, 30);
+
+ WHEN("accessing an existing element with at()")
+ {
+ auto & val = map.at(2);
+
+ THEN("it returns a reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+
+ THEN("the mapped value can be modified")
+ {
+ val = 200;
+ REQUIRE(map.at(2) == 200);
+ }
+ }
+
+ WHEN("accessing a non-existent element with at()")
+ {
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic);
+ }
+ }
+ }
+
+ GIVEN("A const populated Flat Map")
+ {
+ auto map_builder = kstd::flat_map<int, int>{};
+ map_builder.emplace(1, 10);
+ map_builder.emplace(2, 20);
+ auto const map = map_builder;
+
+ WHEN("accessing an existing element with const at()")
+ {
+ auto const & val = map.at(2);
+
+ THEN("it returns a const reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+ }
+
+ WHEN("accessing a non-existent element with const at()")
+ {
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(4), kstd::tests::os_panic);
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map iterators", "[flat_map]")
+{
+ GIVEN("A populated Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+ map.emplace(1, 10);
+ map.emplace(2, 20);
+ map.emplace(3, 30);
+
+ WHEN("using forward iterators")
+ {
+ THEN("they navigate the elements in the correct forward order")
+ {
+ auto it = map.begin();
+ REQUIRE(it != map.end());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it != map.end());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.end());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it == map.end());
+ }
+
+ THEN("const forward iterators provide correct access")
+ {
+ auto it = map.cbegin();
+ REQUIRE(it != map.cend());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it != map.cend());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.cend());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it == map.cend());
+ }
+
+ THEN("assignment through the proxy modifies the mapped value")
+ {
+ auto it = map.begin();
+
+ *it = std::pair{1, 100};
+
+ REQUIRE(it->second == 100);
+ REQUIRE(map.at(1) == 100);
+ }
+
+ THEN("structured bindings evaluate correctly")
+ {
+ auto it = map.cbegin();
+
+ auto [key, value] = *it;
+
+ REQUIRE(key == 1);
+ REQUIRE(value == 10);
+
+ STATIC_REQUIRE(std::is_same_v<decltype(key), int const &>);
+ STATIC_REQUIRE(std::is_same_v<decltype(value), int const &>);
+ }
+ }
+
+ WHEN("using reverse iterators")
+ {
+ THEN("they navigate the elements in the correct reverse order")
+ {
+ auto it = map.rbegin();
+ REQUIRE(it != map.rend());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it != map.rend());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.rend());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it == map.rend());
+ }
+
+ THEN("const reverse iterators provide correct access")
+ {
+ auto it = map.crbegin();
+ REQUIRE(it != map.crend());
+ REQUIRE((*it).first == 3);
+
+ ++it;
+ REQUIRE(it != map.crend());
+ REQUIRE((*it).first == 2);
+
+ ++it;
+ REQUIRE(it != map.crend());
+ REQUIRE((*it).first == 1);
+
+ ++it;
+ REQUIRE(it == map.crend());
+ }
+ }
+ }
+
+ GIVEN("an empty Flat Map")
+ {
+ auto map = kstd::flat_map<int, int>{};
+
+ WHEN("getting iterators")
+ {
+ THEN("begin() equals end() and cbegin() equals cend()")
+ {
+ REQUIRE(map.begin() == map.end());
+ REQUIRE(map.cbegin() == map.cend());
+ }
+
+ THEN("rbegin() equals rend() and crbegin() equals crend()")
+ {
+ REQUIRE(map.rbegin() == map.rend());
+ REQUIRE(map.crbegin() == map.crend());
+ }
+ }
+ }
+}
+
+SCENARIO("Flat Map heterogeneous element access", "[flat_map]")
+{
+ GIVEN("A populated Flat Map with a transparent comparator")
+ {
+ auto map = kstd::flat_map<int, int, std::less<void>>{};
+ map.emplace(1, 10);
+ map.emplace(2, 20);
+ map.emplace(3, 30);
+
+ WHEN("accessing an existing element with a different key type via at()")
+ {
+ long const key = 2L;
+ auto & val = map.at(key);
+
+ THEN("it returns a reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+
+ THEN("the mapped value can be modified")
+ {
+ val = 200;
+ REQUIRE(map.at(2L) == 200);
+ }
+ }
+
+ WHEN("accessing a non-existent element with a different key type via at()")
+ {
+ long const key = 4L;
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic);
+ }
+ }
+ }
+
+ GIVEN("A const populated Flat Map with a transparent comparator")
+ {
+ auto map_builder = kstd::flat_map<int, int, std::less<void>>{};
+ map_builder.emplace(1, 10);
+ map_builder.emplace(2, 20);
+ auto const map = map_builder;
+
+ WHEN("accessing an existing element with a different key type via const at()")
+ {
+ long const key = 2L;
+ auto const & val = map.at(key);
+
+ THEN("it returns a const reference to the mapped value")
+ {
+ REQUIRE(val == 20);
+ }
+ }
+
+ WHEN("accessing a non-existent element with a different key type via const at()")
+ {
+ long const key = 4L;
+ THEN("it panics")
+ {
+ REQUIRE_THROWS_AS(map.at(key), kstd::tests::os_panic);
+ }
+ }
+ }
+}
diff --git a/libs/kstd/kstd/format b/libs/kstd/kstd/format
new file mode 100644
index 0000000..e04b79a
--- /dev/null
+++ b/libs/kstd/kstd/format
@@ -0,0 +1,22 @@
+#ifndef KSTD_FORMAT_HPP
+#define KSTD_FORMAT_HPP
+
+#include <kstd/bits/format/arg.hpp> // IWYU pragma: export
+#include <kstd/bits/format/args.hpp> // IWYU pragma: export
+#include <kstd/bits/format/context.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/bool.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/byte.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/char.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/cstring.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/integral.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/ordering.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/pointer.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/range.hpp> // IWYU pragma: export
+#include <kstd/bits/format/formatter/string_view.hpp> // IWYU pragma: export
+#include <kstd/bits/format/output_buffer.hpp> // IWYU pragma: export
+#include <kstd/bits/format/parse_context.hpp> // IWYU pragma: export
+#include <kstd/bits/format/string.hpp> // IWYU pragma: export
+#include <kstd/bits/format/vformat.hpp> // IWYU pragma: export
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/format.test.cpp b/libs/kstd/kstd/format.test.cpp
new file mode 100644
index 0000000..624779a
--- /dev/null
+++ b/libs/kstd/kstd/format.test.cpp
@@ -0,0 +1,114 @@
+#include <kstd/format>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <iterator>
+#include <sstream>
+
+SCENARIO("Formatting to a new string", "[format]")
+{
+ GIVEN("a format string without any placeholders")
+ {
+ auto const & fmt = "This is a test";
+
+ WHEN("calling format with without any arguments.")
+ {
+ auto result = kstd::format(fmt);
+
+ THEN("the result is the unmodified string")
+ {
+ REQUIRE(result == "This is a test");
+ }
+ }
+
+ WHEN("calling format with additional arguments")
+ {
+ auto result = kstd::format(fmt, 1, 2, 3);
+
+ THEN("the result is the unmodified string")
+ {
+ REQUIRE(result == "This is a test");
+ }
+ }
+ }
+
+ GIVEN("a format string with placeholders")
+ {
+ auto const & fmt = "Here are some placeholders: {} {} {}";
+
+ WHEN("calling format with the same number of arguments as there are placeholders")
+ {
+ auto result = kstd::format(fmt, 1, true, 'a');
+
+ THEN("the result is the formatted string")
+ {
+ REQUIRE(result == "Here are some placeholders: 1 true a");
+ }
+ }
+
+ WHEN("calling format with too many arguments")
+ {
+ auto result = kstd::format(fmt, 2, false, 'b', 4, 5, 6);
+
+ THEN("the result is the formatted string")
+ {
+ REQUIRE(result == "Here are some placeholders: 2 false b");
+ }
+ }
+ }
+}
+
+SCENARIO("Formatting to an output iterator", "[format]")
+{
+ auto buffer = std::ostringstream{};
+
+ GIVEN("a format string without any placeholders")
+ {
+ auto const & fmt = "This is a test";
+
+ WHEN("calling format with without any arguments.")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt);
+
+ THEN("the unmodified string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "This is a test");
+ }
+ }
+
+ WHEN("calling format with additional arguments")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, 2, 3);
+
+ THEN("the unmodified string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "This is a test");
+ }
+ }
+ }
+
+ GIVEN("a format string with placeholders")
+ {
+ auto const & fmt = "Here are some placeholders: {} {} {}";
+
+ WHEN("calling format with the same number of arguments as there are placeholders")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 1, true, -100);
+
+ THEN("the formatted string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "Here are some placeholders: 1 true -100");
+ }
+ }
+
+ WHEN("calling format with too many arguments")
+ {
+ kstd::format_to(std::ostream_iterator<char>{buffer}, fmt, 2, false, -200, 4, 5, 6);
+
+ THEN("the formatted string is written to the iterator")
+ {
+ REQUIRE(buffer.str() == "Here are some placeholders: 2 false -200");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/kstd/src/libc/stdlib.cpp b/libs/kstd/kstd/libc/stdlib.cpp
index 4a5c91f..7ed051f 100644
--- a/libs/kstd/src/libc/stdlib.cpp
+++ b/libs/kstd/kstd/libc/stdlib.cpp
@@ -1,4 +1,4 @@
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
namespace kstd::libc
{
diff --git a/libs/kstd/kstd/libc/string.cpp b/libs/kstd/kstd/libc/string.cpp
new file mode 100644
index 0000000..b7cdb82
--- /dev/null
+++ b/libs/kstd/kstd/libc/string.cpp
@@ -0,0 +1,78 @@
+#include <kstd/cstring>
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <iterator>
+#include <span>
+
+namespace kstd::libc
+{
+
+ auto memcpy(void * dest, void const * src, std::size_t size) noexcept -> void *
+ {
+ auto dest_span = std::span{static_cast<std::byte *>(dest), size};
+ auto src_span = std::span{static_cast<std::byte const *>(src), size};
+
+ for (std::size_t i = 0; i < size; ++i)
+ {
+ dest_span[i] = src_span[i];
+ }
+
+ return dest;
+ }
+
+ auto memset(void * dest, int value, std::size_t size) noexcept -> void *
+ {
+ auto const byte_value = static_cast<std::byte>(static_cast<unsigned char>(value));
+ auto dest_span = std::span{static_cast<std::byte *>(dest), size};
+
+ for (std::size_t i = 0; i < size; ++i)
+ {
+ dest_span[i] = byte_value;
+ }
+
+ return dest;
+ }
+
+ auto memcmp(void const * lhs, void const * rhs, std::size_t size) noexcept -> int
+ {
+ auto left_span = std::span{static_cast<std::byte const *>(lhs), size};
+ auto right_span = std::span{static_cast<std::byte const *>(rhs), size};
+ auto mismatched = std::ranges::mismatch(left_span, right_span);
+
+ if (mismatched.in1 == left_span.end())
+ {
+ return 0;
+ }
+
+ return std::bit_cast<char>(*mismatched.in1) - std::bit_cast<char>(*mismatched.in2);
+ }
+
+ auto memmove(void * dest, void const * src, std::size_t size) noexcept -> void *
+ {
+ auto dest_span = std::span{static_cast<std::byte *>(dest), size};
+ auto src_span = std::span{static_cast<std::byte const *>(src), size};
+ if (dest < src)
+ {
+ for (std::size_t i = 0; i < size; ++i)
+ {
+ dest_span[i] = src_span[i];
+ }
+ }
+ else
+ {
+ for (std::size_t i = size; i > 0; --i)
+ {
+ dest_span[i - 1] = src_span[i - 1];
+ }
+ }
+ return dest;
+ }
+
+ auto strlen(char const * string) noexcept -> std::size_t
+ {
+ return std::distance(string, std::ranges::find(string, nullptr, '\0'));
+ }
+
+} // namespace kstd::libc \ No newline at end of file
diff --git a/libs/kstd/kstd/memory b/libs/kstd/kstd/memory
new file mode 100644
index 0000000..f108c6d
--- /dev/null
+++ b/libs/kstd/kstd/memory
@@ -0,0 +1,8 @@
+#ifndef KSTD_MEMORY_HPP
+#define KSTD_MEMORY_HPP
+
+#include <kstd/bits/observer_ptr.hpp> // IWYU pragma: export
+#include <kstd/bits/shared_ptr.hpp> // IWYU pragma: export
+#include <kstd/bits/unique_ptr.hpp> // IWYU pragma: export
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/mutex b/libs/kstd/kstd/mutex
index b2a31aa..b2a31aa 100644
--- a/libs/kstd/include/kstd/mutex
+++ b/libs/kstd/kstd/mutex
diff --git a/libs/kstd/src/mutex.cpp b/libs/kstd/kstd/mutex.cpp
index d66cb98..7387657 100644
--- a/libs/kstd/src/mutex.cpp
+++ b/libs/kstd/kstd/mutex.cpp
@@ -1,6 +1,6 @@
-#include "kstd/mutex"
+#include <kstd/mutex>
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
#include <atomic>
diff --git a/libs/kstd/src/os/error.cpp b/libs/kstd/kstd/os/error.cpp
index b82158d..f969cb5 100644
--- a/libs/kstd/src/os/error.cpp
+++ b/libs/kstd/kstd/os/error.cpp
@@ -1,4 +1,4 @@
-#include "kstd/os/error.hpp"
+#include <kstd/os/error.hpp>
namespace kstd::os
{
diff --git a/libs/kstd/include/kstd/os/error.hpp b/libs/kstd/kstd/os/error.hpp
index 9d43fb1..9d43fb1 100644
--- a/libs/kstd/include/kstd/os/error.hpp
+++ b/libs/kstd/kstd/os/error.hpp
diff --git a/libs/kstd/include/kstd/os/print.hpp b/libs/kstd/kstd/os/print.hpp
index f189042..36cb43d 100644
--- a/libs/kstd/include/kstd/os/print.hpp
+++ b/libs/kstd/kstd/os/print.hpp
@@ -1,8 +1,8 @@
#ifndef KSTD_OS_PRINT_HPP
#define KSTD_OS_PRINT_HPP
-#include "kstd/bits/formatter.hpp"
-#include "kstd/bits/print_sink.hpp"
+#include <kstd/bits/format/args.hpp>
+#include <kstd/bits/print_sink.hpp>
#include <string_view>
diff --git a/libs/kstd/include/kstd/print b/libs/kstd/kstd/print
index ffafda9..1033f72 100644
--- a/libs/kstd/include/kstd/print
+++ b/libs/kstd/kstd/print
@@ -1,12 +1,10 @@
#ifndef KSTD_PRINT
#define KSTD_PRINT
-#include "bits/print_sink.hpp" // IWYU pragma: export
-#include "os/print.hpp"
-
+#include <kstd/bits/print_sink.hpp> // IWYU pragma: export
#include <kstd/format>
+#include <kstd/os/print.hpp>
-#include <array>
#include <type_traits>
namespace kstd
@@ -19,13 +17,10 @@ namespace kstd
//! @param args The arguments to use to place in the format string's placeholders.
template<typename... Args>
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
- auto print(kstd::format_string<std::type_identity_t<Args>...> format, Args &&... args) -> void
+ auto print(kstd::format_string<std::type_identity_t<Args>...> format, Args const &... args) -> void
{
- auto arguments = std::array<kstd::format_arg, sizeof...(Args)>{
- kstd::format_arg{&args, kstd::format_dispatcher<std::remove_cvref_t<Args>>}
- ...
- };
- os::vprint(print_sink::stdout, format.str, kstd::format_args{arguments.data(), sizeof...(Args)});
+ auto const arg_store = kstd::make_format_args(args...);
+ os::vprint(print_sink::stdout, format.str_view, arg_store.args);
}
//! @qualifier kernel-defined
@@ -37,11 +32,8 @@ namespace kstd
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
auto print(print_sink sink, kstd::format_string<std::type_identity_t<Args>...> format, Args &&... args) -> void
{
- auto arguments = std::array<kstd::format_arg, sizeof...(Args)>{
- kstd::format_arg{&args, kstd::format_dispatcher<std::remove_cvref_t<Args>>}
- ...
- };
- os::vprint(sink, format.str, kstd::format_args{arguments.data(), sizeof...(Args)});
+ auto const arg_store = kstd::make_format_args(args...);
+ os::vprint(sink, format.str_view, arg_store.args);
}
//! @qualifier kernel-defined
@@ -54,7 +46,7 @@ namespace kstd
// NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward)
auto println(kstd::format_string<std::type_identity_t<Args>...> format, Args &&... args) -> void
{
- print(format, std::forward<Args>(args)...);
+ print(print_sink::stdout, format, std::forward<Args>(args)...);
print(print_sink::stdout, "\n");
}
diff --git a/libs/kstd/kstd/ranges b/libs/kstd/kstd/ranges
new file mode 100644
index 0000000..78c3adb
--- /dev/null
+++ b/libs/kstd/kstd/ranges
@@ -0,0 +1,21 @@
+#ifndef KSTD_RANGES
+#define KSTD_RANGES
+
+#include <ranges> // IWYU pragma: export
+
+namespace kstd
+{
+#if __glibcxx_ranges_to_container
+ using std::from_range;
+ using std::from_range_t;
+#else
+ struct from_range_t
+ {
+ explicit from_range_t() = default;
+ };
+ constexpr auto inline from_range = from_range_t{};
+#endif
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/include/kstd/stack b/libs/kstd/kstd/stack
index 9750376..02e44ea 100644
--- a/libs/kstd/include/kstd/stack
+++ b/libs/kstd/kstd/stack
@@ -1,7 +1,8 @@
#ifndef KSTD_STACK_HPP
#define KSTD_STACK_HPP
-#include "kstd/vector"
+#include <kstd/vector>
+
#include <initializer_list>
#include <utility>
diff --git a/libs/kstd/kstd/string b/libs/kstd/kstd/string
new file mode 100644
index 0000000..9343b42
--- /dev/null
+++ b/libs/kstd/kstd/string
@@ -0,0 +1,369 @@
+#ifndef KSTD_STRING_HPP
+#define KSTD_STRING_HPP
+
+#include <kstd/bits/format/context.hpp>
+#include <kstd/bits/format/formatter.hpp>
+#include <kstd/bits/format/formatter/string_view.hpp>
+#include <kstd/cstring>
+#include <kstd/os/error.hpp>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <string_view>
+
+namespace kstd
+{
+ /**
+ * @brief A simple string implementation that owns its data and provides basic operations.
+ */
+ struct string
+ {
+ //! The type of the characters contained in this string.
+ using value_type = char;
+ //! The type of the underlying storage used by this string.
+ using storage_type = kstd::vector<value_type>;
+ //! The type of all sizes used in and with this string.
+ using size_type = std::size_t;
+ //! The type of the difference between two iterators.
+ using difference_type = std::ptrdiff_t;
+ //! The type of references to single values in this string.
+ using reference = value_type &;
+ //! The type of references to constant single values in this string.
+ using const_reference = value_type const &;
+ //! The type of pointers to single values in this string.
+ using pointer = value_type *;
+ //! The type of pointers to constant single values in this string.
+ using const_pointer = value_type const *;
+ //! The type of iterators into this string.
+ using iterator = pointer;
+ //! The type of constant iterators into this string.
+ using const_iterator = const_pointer;
+
+ /**
+ * @brief Constructs an empty null-terminated string.
+ */
+ string()
+ : m_storage{value_type{'\0'}}
+ {}
+
+ /**
+ * @brief Constructs a string from a string view by copying the characters into owned storage.
+ * @param view The string view to copy the characters from.
+ */
+ explicit string(std::string_view view)
+ : string()
+ {
+ append(view);
+ }
+
+ /**
+ * @brief Constructs a string from a null-terminated C-style string by copying the characters into owned storage.
+ * @param c_str The null-terminated C-style string to copy.
+ */
+ string(char const * c_str)
+ : string()
+ {
+ if (c_str != nullptr)
+ {
+ append(std::string_view{c_str});
+ }
+ }
+
+ /**
+ * @brief Constructs a string containing a single character.
+ * @param c The character to copy.
+ */
+ string(value_type c)
+ : string()
+ {
+ push_back(c);
+ }
+
+ /**
+ * @brief Constructs a string by copying another string.
+ * @param other The string to copy.
+ */
+ constexpr string(string const & other)
+ : m_storage{other.m_storage}
+ {}
+
+ /**
+ * @brief Destructs the string.
+ */
+ constexpr ~string() = default;
+
+ /**
+ * @brief Assigns the value of another string to this string.
+ * @param other The string to assign from.
+ * @return A reference to this string.
+ */
+ constexpr auto operator=(string const & other) -> string & = default;
+
+ constexpr auto operator=(std::string_view const & other) -> string &
+ {
+ clear();
+ append(other);
+ return *this;
+ }
+
+ constexpr auto operator=(char const * other) -> string &
+ {
+ clear();
+ append(std::string_view{other});
+ return *this;
+ }
+
+ //! Create a string view from this string.
+ constexpr operator std::string_view() const noexcept
+ {
+ return {data(), size()};
+ }
+
+ /**
+ * @brief Returns the number of characters in this string, not including the null terminator.
+ */
+ [[nodiscard]] constexpr auto size() const noexcept -> size_type
+ {
+ return m_storage.empty() ? 0 : m_storage.size() - 1;
+ }
+
+ /**
+ * @brief Checks if this string is empty, not including the null terminator.
+ */
+ [[nodiscard]] constexpr auto empty() const noexcept -> bool
+ {
+ return size() == 0;
+ }
+
+ /**
+ * @brief Clears the content of the string, resulting in an empty string.
+ * The string remains null-terminated after this operation.
+ */
+ constexpr auto clear() -> void
+ {
+ m_storage.clear();
+ m_storage.push_back(value_type{'\0'});
+ }
+
+ //! Get a pointer to the underlying storage of the string
+ [[nodiscard]] constexpr auto data() noexcept -> pointer
+ {
+ return m_storage.data();
+ }
+
+ //! Get a const pointer to the underlying storage of the string
+ [[nodiscard]] constexpr auto data() const noexcept -> const_pointer
+ {
+ return m_storage.data();
+ }
+
+ //! Get a const pointer to the underlying storage of the string
+ [[nodiscard]] constexpr auto c_str() const noexcept -> const_pointer
+ {
+ return data();
+ }
+
+ //! Get an iterator to the beginning of the string
+ [[nodiscard]] constexpr auto begin() noexcept -> iterator
+ {
+ return data();
+ }
+
+ //! Get an const iterator to the beginning of the string
+ [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator
+ {
+ return data();
+ }
+
+ //! Get an const iterator to the beginning of the string
+ [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ //! Get an iterator to the end of the string
+ [[nodiscard]] constexpr auto end() noexcept -> iterator
+ {
+ return data() + size();
+ }
+
+ //! Get an const iterator to the end of the string
+ [[nodiscard]] constexpr auto end() const noexcept -> const_iterator
+ {
+ return data() + size();
+ }
+
+ //! Get an const iterator to the end of the string
+ [[nodiscard]] constexpr auto cend() const noexcept -> const_iterator
+ {
+ return end();
+ }
+
+ //! Get a reference to the first character of the string
+ [[nodiscard]] constexpr auto front() -> reference
+ {
+ return m_storage.front();
+ }
+
+ //! Get a const reference to the first character of the string
+ [[nodiscard]] constexpr auto front() const -> const_reference
+ {
+ return m_storage.front();
+ }
+
+ //! Get a reference to the last character of the string
+ [[nodiscard]] constexpr auto back() -> reference
+ {
+ return m_storage[size() - 1];
+ }
+
+ //! Get a const reference to the last character of the string
+ [[nodiscard]] constexpr auto back() const -> const_reference
+ {
+ return m_storage[size() - 1];
+ }
+
+ /**
+ * @brief Appends a character to the end of the string.
+ * @param ch The character to append.
+ */
+ constexpr auto push_back(value_type ch) -> void
+ {
+ m_storage.back() = ch;
+ m_storage.push_back(value_type{'\0'});
+ }
+
+ /**
+ * @brief Appends a string view to the end of the string by copying the characters into owned storage.
+ * @param view The string view to append.
+ * @return A reference to this string.
+ */
+ constexpr auto append(std::string_view view) -> string &
+ {
+ if (!view.empty())
+ {
+ m_storage.reserve(size() + view.size() + 1);
+ std::ranges::for_each(view, [this](auto const ch) { push_back(ch); });
+ }
+
+ return *this;
+ }
+
+ /**
+ * @brief Appends another string to the end of this string by copying the characters into owned storage.
+ * @param other The string to append.
+ * @return A reference to this string.
+ */
+ constexpr auto append(string const & other) -> string &
+ {
+ return append(static_cast<std::string_view>(other));
+ }
+
+ /**
+ * @brief Appends another string to the end of this string by copying the characters into owned storage.
+ * @param other The string to append.
+ * @return A reference to this string.
+ */
+ constexpr auto operator+=(string const & other) -> string &
+ {
+ return append(other);
+ }
+
+ /**
+ * @brief Appends a character to the end of the string.
+ * @param ch The character to append.
+ * @return A reference to this string.
+ */
+ constexpr auto operator+=(value_type ch) -> string &
+ {
+ push_back(ch);
+ return *this;
+ }
+
+ //! Compare this string lexicographically to another string.
+ //!
+ //! @param other The string to compare to this one.
+ [[nodiscard]] constexpr auto operator<=>(string const & other) const noexcept -> std::strong_ordering = default;
+
+ [[nodiscard]] constexpr auto operator==(string const & other) const noexcept -> bool = default;
+
+ //! Compare this string lexicographically to a C-style string.
+ //!
+ //! @param other The C-style string to compare to this one.
+ //! @return The result of the comparison.
+ [[nodiscard]] constexpr auto operator<=>(char const * other) const noexcept
+ {
+ return static_cast<std::string_view>(*this) <=> other;
+ }
+
+ //! Check if this string compares equal to a C-style string.
+ //!
+ //! @param other The C-style string to compare to this one.
+ //! @return @p true iff. this string compares equal to @p other, @p false otherwise.
+ [[nodiscard]] constexpr auto operator==(char const * other) const noexcept -> bool
+ {
+ return static_cast<std::string_view>(*this) == other;
+ }
+
+ private:
+ //! The underlying storage of the string, which owns the characters and ensures null-termination.
+ storage_type m_storage{};
+ };
+
+ /**
+ * @brief Concatenates a strings and a character and returns the result as a new string.
+ * @param lhs The string to concatenate.
+ * @param rhs The string to concatenate.
+ * @return A new string that is the result of concatenating @p lhs and @p rhs.
+ */
+ [[nodiscard]] constexpr auto inline operator+(string const & lhs, string const & rhs) -> string
+ {
+ string result{lhs};
+ result += rhs;
+ return result;
+ }
+
+ /**
+ * @brief Converts an unsigned integer to a string by converting each digit to the corresponding character and
+ * concatenating them.
+ * @tparam N The type of the unsigned integer to convert.
+ * @param value The unsigned integer to convert.
+ * @return A string representation of the given unsigned integer.
+ */
+ template<typename N>
+ requires std::unsigned_integral<N>
+ [[nodiscard]] constexpr auto inline to_string(N value) -> string
+ {
+ if (value == 0)
+ {
+ return "0";
+ }
+
+ string result;
+
+ while (value > 0)
+ {
+ char const digit = '0' + (value % 10);
+ result.push_back(digit);
+ value /= 10;
+ }
+
+ std::reverse(result.begin(), result.end());
+ return result;
+ }
+
+ template<>
+ struct formatter<string> : formatter<std::string_view>
+ {
+ auto format(string const & str, format_context & context) const -> void
+ {
+ formatter<std::string_view>::format(static_cast<std::string_view>(str), context);
+ }
+ };
+
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/string.test.cpp b/libs/kstd/kstd/string.test.cpp
new file mode 100644
index 0000000..b81cd3a
--- /dev/null
+++ b/libs/kstd/kstd/string.test.cpp
@@ -0,0 +1,445 @@
+#include <kstd/string>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <cstring>
+#include <string_view>
+
+SCENARIO("String initialization and construction", "[string]")
+{
+ GIVEN("Nothing")
+ {
+ WHEN("constructing an empty string")
+ {
+ auto str = kstd::string{};
+
+ THEN("the size is zero and therefore the string is empty")
+ {
+ REQUIRE(str.empty());
+ REQUIRE(str.size() == 0);
+ }
+
+ THEN("the string is empty")
+ {
+ REQUIRE(str == std::string_view{});
+ }
+ }
+ }
+
+ GIVEN("A string view")
+ {
+ auto view = std::string_view{"Blub Blub"};
+
+ WHEN("constructing a string from string_view")
+ {
+ auto str = kstd::string{view};
+
+ THEN("the string is not empty and has the same size as the view")
+ {
+ REQUIRE_FALSE(str.empty());
+ REQUIRE(str.size() == view.size());
+ }
+
+ THEN("the string contains the same characters as the view")
+ {
+ REQUIRE(str == view);
+ }
+ }
+ }
+
+ GIVEN("A C-style string")
+ {
+ auto c_str = "Blub Blub";
+
+ WHEN("constructing a string from the C-style string")
+ {
+ auto str = kstd::string{c_str};
+
+ THEN("the string is not empty and has the same size as the C-style string")
+ {
+ REQUIRE_FALSE(str.empty());
+ REQUIRE(str.size() == std::strlen(c_str));
+ }
+
+ THEN("the string contains the same characters as the C-style string")
+ {
+ REQUIRE(str == c_str);
+ }
+ }
+ }
+
+ GIVEN("A character")
+ {
+ auto ch = 'x';
+
+ WHEN("constructing a string from the character")
+ {
+ auto str = kstd::string{ch};
+
+ THEN("the string is not empty and has size 1")
+ {
+ REQUIRE_FALSE(str.empty());
+ REQUIRE(str.size() == 1);
+ }
+
+ THEN("the string contains the same character as the given character")
+ {
+ REQUIRE(str == std::string_view{&ch, 1});
+ }
+ }
+ }
+
+ GIVEN("Another string")
+ {
+ auto other = kstd::string{"Blub Blub"};
+
+ WHEN("copy constructing a new string")
+ {
+ auto str = kstd::string{other};
+
+ THEN("the new string contains the same characters as the original")
+ {
+ REQUIRE(static_cast<std::string_view>(str) == static_cast<std::string_view>(other));
+ }
+ }
+
+ auto str = kstd::string{"Blub"};
+
+ WHEN("copy assigning another string")
+ {
+ auto other = kstd::string{"Blub Blub"};
+ str = other;
+
+ THEN("the string contains the same characters as the assigned string")
+ {
+ REQUIRE(static_cast<std::string_view>(str) == static_cast<std::string_view>(other));
+ }
+ }
+ }
+
+ GIVEN("A string")
+ {
+ auto str = kstd::string{"Hello"};
+
+ WHEN("copy assigning a string view")
+ {
+ auto view = std::string_view{"Hello, world!"};
+ str = view;
+
+ THEN("the string contains the same characters as the assigned view")
+ {
+ REQUIRE(str == view);
+ }
+ }
+ }
+
+ GIVEN("A string")
+ {
+ auto str = kstd::string{"Hello"};
+
+ WHEN("copy assigning a C-style string")
+ {
+ auto c_str = "Hello, world!";
+ str = c_str;
+
+ THEN("the string contains the same characters as the assigned C-style string")
+ {
+ REQUIRE(str == c_str);
+ }
+ }
+ }
+}
+
+SCENARIO("String concatenation", "[string]")
+{
+ GIVEN("Two strings")
+ {
+ auto str1 = kstd::string{"Blub"};
+ auto str2 = kstd::string{" Blub"};
+
+ WHEN("appending the second string to the first string")
+ {
+ str1.append(str2);
+
+ THEN("the first string contains the characters of both strings concatenated")
+ {
+ REQUIRE(str1 == "Blub Blub");
+ }
+
+ THEN("the size of the first string is the sum of the sizes of both strings")
+ {
+ REQUIRE(str1.size() == str2.size() + 4);
+ }
+ }
+
+ WHEN("using operator+= to append the second string to the first string")
+ {
+ str1 += str2;
+
+ THEN("the first string contains the characters of both strings concatenated")
+ {
+ REQUIRE(str1 == "Blub Blub");
+ }
+
+ THEN("the size of the first string is the sum of the sizes of both strings")
+ {
+ REQUIRE(str1.size() == str2.size() + 4);
+ }
+ }
+
+ WHEN("using operator+ to concatenate the two strings into a new string")
+ {
+ auto str3 = str1 + str2;
+
+ THEN("the new string contains the characters of both strings concatenated")
+ {
+ REQUIRE(str3 == "Blub Blub");
+ }
+
+ THEN("the size of the new string is the sum of the sizes of both strings")
+ {
+ REQUIRE(str3.size() == str1.size() + str2.size());
+ }
+ }
+ }
+
+ GIVEN("A string and a string view")
+ {
+ auto str = kstd::string{"Blub"};
+ auto view = std::string_view{" Blub"};
+
+ WHEN("appending the string view to the string")
+ {
+ str.append(view);
+
+ THEN("the string contains the characters of both the original string and the appended view concatenated")
+ {
+ REQUIRE(str == "Blub Blub");
+ }
+
+ THEN("the size of the string is the sum of the sizes of the original string and the appended view")
+ {
+ REQUIRE(str.size() == view.size() + 4);
+ }
+ }
+ }
+
+ GIVEN("A string and a character")
+ {
+ auto str = kstd::string{"Blub"};
+ auto ch = '!';
+
+ WHEN("appending the character to the string")
+ {
+ str.push_back(ch);
+
+ THEN("the string contains the original characters followed by the appended character")
+ {
+ REQUIRE(str == "Blub!");
+ }
+
+ THEN("the size of the string is one more than the original size")
+ {
+ REQUIRE(str.size() == 5);
+ }
+ }
+
+ WHEN("using operator+= to append the character to the string")
+ {
+ str += ch;
+
+ THEN("the string contains the original characters followed by the appended character")
+ {
+ REQUIRE(str == "Blub!");
+ }
+
+ THEN("the size of the string is one more than the original size")
+ {
+ REQUIRE(str.size() == 5);
+ }
+ }
+ }
+}
+
+SCENARIO("String conversion and comparison", "[string]")
+{
+ GIVEN("An unsigned integer")
+ {
+ constexpr auto value1 = 12345u;
+ constexpr auto value2 = 0u;
+
+ WHEN("converting the unsigned integer to a string")
+ {
+ auto str1 = kstd::to_string(value1);
+ auto str2 = kstd::to_string(value2);
+
+ THEN("the string contains the decimal representation of the unsigned integer")
+ {
+ REQUIRE(str1 == "12345");
+ REQUIRE(str2 == "0");
+ }
+ }
+ }
+
+ GIVEN("Two strings with the same characters")
+ {
+ auto str1 = kstd::string{"Blub Blub"};
+ auto str2 = kstd::string{"Blub Blub"};
+
+ THEN("the strings are equal")
+ {
+ REQUIRE(str1 == str2);
+ }
+
+ THEN("the strings are not unequal")
+ {
+ REQUIRE_FALSE(str1 != str2);
+ }
+ }
+
+ GIVEN("A string and a string view with the same characters")
+ {
+ auto str = kstd::string{"Blub Blub"};
+ auto view = std::string_view{"Blub Blub"};
+
+ THEN("the string and the string view are equal")
+ {
+ REQUIRE(str == view);
+ REQUIRE(view == str);
+ }
+
+ THEN("the string and the string view are not unequal")
+ {
+ REQUIRE_FALSE(str != view);
+ REQUIRE_FALSE(view != str);
+ }
+ }
+}
+
+SCENARIO("String clearing", "[string]")
+{
+ GIVEN("A non-empty string")
+ {
+ auto str = kstd::string{"Blub Blub"};
+
+ WHEN("clearing the string")
+ {
+ str.clear();
+
+ THEN("the string is empty and has size zero")
+ {
+ REQUIRE(str.empty());
+ REQUIRE(str.size() == 0);
+ }
+
+ THEN("the string contains no characters")
+ {
+ REQUIRE(str == std::string_view{});
+ }
+ }
+ }
+}
+
+SCENARIO("String iteration", "[string]")
+{
+ GIVEN("A string")
+ {
+ auto str = kstd::string{"Blub"};
+
+ WHEN("iterating over the characters of the string as string_view using a range-based for loop")
+ {
+ kstd::string result;
+
+ for (auto ch : static_cast<std::string_view>(str))
+ {
+ result.push_back(ch);
+ }
+
+ THEN("the iterated characters are the same as the characters in the string")
+ {
+ REQUIRE(result == str);
+ }
+ }
+
+ WHEN("using std::ranges::for_each to iterate over the characters of the string")
+ {
+ kstd::string result;
+
+ std::ranges::for_each(str, [&result](auto ch) { result.push_back(ch); });
+
+ THEN("the iterated characters are the same as the characters in the string")
+ {
+ REQUIRE(result == str);
+ }
+ }
+
+ WHEN("using front and back to access the first and last characters of the string")
+ {
+ THEN("front returns the first character of the string")
+ {
+ REQUIRE(str.front() == 'B');
+ }
+
+ THEN("back returns the last character of the string")
+ {
+ REQUIRE(str.back() == 'b');
+ }
+ }
+ }
+
+ GIVEN("A const string")
+ {
+ auto const str = kstd::string{"Blub"};
+
+ WHEN("iterating over the characters of the string as string_view using a range-based for loop")
+ {
+ kstd::string result;
+
+ for (auto ch : static_cast<std::string_view>(str))
+ {
+ result.push_back(ch);
+ }
+
+ THEN("the iterated characters are the same as the characters in the string")
+ {
+ REQUIRE(result == static_cast<std::string_view>(str));
+ }
+ }
+
+ WHEN("using front and back to access the first and last characters of the string")
+ {
+ THEN("front returns the first character of the string")
+ {
+ REQUIRE(str.front() == 'B');
+ }
+
+ THEN("back returns the last character of the string")
+ {
+ REQUIRE(str.back() == 'b');
+ }
+ }
+ }
+
+ GIVEN("An empty string")
+ {
+ auto str = kstd::string{};
+
+ WHEN("iterating over the characters of an empty string")
+ {
+ kstd::string result;
+
+ for (auto ch : static_cast<std::string_view>(str))
+ {
+ result.push_back(ch);
+ }
+
+ THEN("no characters are iterated and the result is an empty string")
+ {
+ REQUIRE(result.empty());
+ REQUIRE(result.size() == 0);
+ REQUIRE(static_cast<std::string_view>(result) == std::string_view{});
+ }
+ }
+ }
+}
diff --git a/libs/kstd/kstd/test_support/os_panic.hpp b/libs/kstd/kstd/test_support/os_panic.hpp
new file mode 100644
index 0000000..4396a9f
--- /dev/null
+++ b/libs/kstd/kstd/test_support/os_panic.hpp
@@ -0,0 +1,23 @@
+#ifndef KSTD_TESTS_OS_PANIC_HPP
+#define KSTD_TESTS_OS_PANIC_HPP
+
+#include <source_location>
+#include <stdexcept>
+#include <string>
+
+namespace kstd::tests
+{
+
+ struct os_panic : std::runtime_error
+ {
+ os_panic(std::string message, std::source_location location)
+ : std::runtime_error{message}
+ , location(location)
+ {}
+
+ std::source_location location;
+ };
+
+} // namespace kstd::tests
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/test_support/os_panic.test.cpp b/libs/kstd/kstd/test_support/os_panic.test.cpp
new file mode 100644
index 0000000..c30411a
--- /dev/null
+++ b/libs/kstd/kstd/test_support/os_panic.test.cpp
@@ -0,0 +1,15 @@
+#include <kstd/test_support/os_panic.hpp>
+
+#include <source_location>
+#include <string>
+#include <string_view>
+
+namespace kstd::os
+{
+
+ auto panic(std::string_view message, std::source_location location)
+ {
+ throw kstd::tests::os_panic{std::string{message}, location};
+ }
+
+} // namespace kstd::os \ No newline at end of file
diff --git a/libs/kstd/kstd/test_support/test_types.hpp b/libs/kstd/kstd/test_support/test_types.hpp
new file mode 100644
index 0000000..8231fd3
--- /dev/null
+++ b/libs/kstd/kstd/test_support/test_types.hpp
@@ -0,0 +1,313 @@
+#ifndef KSTD_TESTS_TEST_TYPES_HPP
+#define KSTD_TESTS_TEST_TYPES_HPP
+
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+namespace kstd::tests
+{
+
+ //! A type tracking copy and move operations
+ //!
+ //! This type is designed to test move and copy semantics of standard library containers implemented in kstd.
+ struct special_member_tracker
+ {
+ //! A value indicating that the object was moved from.
+ constexpr auto static moved_from_v = -1;
+
+ constexpr special_member_tracker()
+ : default_constructed_count{1}
+ {}
+
+ //! Construct a new move tracker with the given value, if any.
+ constexpr special_member_tracker(int v = 0)
+ : value{v}
+ , value_constructed_count{1}
+ {}
+
+ //! Construct a new move tracker by copying an existing one.
+ constexpr special_member_tracker(special_member_tracker const & other)
+ : value{other.value}
+ , copy_constructed_count{1}
+ {}
+
+ //! Construct a new move tracker by moving from an existing one.
+ constexpr special_member_tracker(special_member_tracker && other) noexcept
+ : value{other.value}
+ , move_constructed_count{1}
+ {
+ other.value = moved_from_v;
+ }
+
+ //! Copy assign a new move tracker from an existing one.
+ constexpr auto operator=(special_member_tracker const & other) -> special_member_tracker &
+ {
+ if (this != &other)
+ {
+ value = other.value;
+ ++copy_assigned_count;
+ ++other.copied_from_count;
+ }
+ return *this;
+ }
+
+ //! Move assign a new move tracker from an existing one.
+ //!
+ //! This function ensures that the moved-from state is marked.
+ constexpr auto operator=(special_member_tracker && other) noexcept -> special_member_tracker &
+ {
+ if (this != &other)
+ {
+ value = other.value;
+ ++move_assigned_count;
+ other.value = moved_from_v;
+ ++other.moved_from_count;
+ }
+ return *this;
+ }
+
+ ~special_member_tracker()
+ {
+ ++destroyed_count;
+ }
+
+ auto reset_counts() -> void
+ {
+ default_constructed_count = 0;
+ copy_constructed_count = 0;
+ move_constructed_count = 0;
+ value_constructed_count = 0;
+ copy_assigned_count = 0;
+ move_assigned_count = 0;
+ destroyed_count = 0;
+ copied_from_count = 0;
+ moved_from_count = 0;
+ }
+
+ //! A simple value to be able to track the move-from state.
+ int value{};
+ //! A counter to track how many times an instance of this type was default constructed.
+ std::size_t default_constructed_count{0};
+ //! A counter to track how many times an instance of this type was copy constructed.
+ std::size_t copy_constructed_count{0};
+ //! A counter to track how many times an instance of this type was move constructed.
+ std::size_t move_constructed_count{0};
+ //! A counter to track how many times an instance of this type was value constructed.
+ std::size_t value_constructed_count{0};
+ //! A counter to track how many times an instance of this type was copy assigned.
+ std::size_t copy_assigned_count{0};
+ //! A counter to track how many times an instance of this type was move assigned.
+ std::size_t move_assigned_count{0};
+ //! A counter to track how many times an instance of this type was destroyed.
+ std::size_t destroyed_count{0};
+ //! A counter to track how many times an instance of this type was copied from another instance.
+ mutable std::size_t copied_from_count{0};
+ //! A counter to track how many times an instance of this type was moved from another instance.
+ std::size_t moved_from_count{0};
+ };
+
+ //! A type that is not default constructible.
+ //!
+ //! This type is designed to test default construction semantics of standard library containers implemented in kstd.
+ struct non_default_constructible
+ {
+ //! A simple placeholder value.
+ int value{};
+
+ //! Construct a new non-default-constructible object with the given value.
+ constexpr explicit non_default_constructible(int v)
+ : value{v}
+ {}
+
+ //! Compare two non-default-constructible objects for equality.
+ [[nodiscard]] constexpr auto operator==(non_default_constructible const & other) const -> bool
+ {
+ return value == other.value;
+ }
+ };
+
+ //! An allocator that tracks the number of allocations.
+ //!
+ //! This allocator is designed to test allocation semantics of standard library containers implemented in kstd.
+ //!
+ //! @tparam T The type of the elements to be allocated.
+ template<typename T>
+ struct tracking_allocator
+ {
+ using value_type = T;
+ using size_type = std::size_t;
+ using difference_type = std::ptrdiff_t;
+
+ //! A pointer to a counter that is incremented on allocation.
+ //!
+ //! A pointer is used so that multiple allocators can share the same counter.
+ int * allocation_count{};
+
+ //! Construct a new tracking allocator referencing the given counter.
+ tracking_allocator(int * counter)
+ : allocation_count{counter}
+ {}
+
+ //! Construct a new tracking allocator by copying from another tracking allocator.
+ //!
+ //! This constructor is templated to allow for conversion from allocators of different types.
+ //!
+ //! @tparam U The type of the elements to be allocated by the other allocator.
+ template<typename U>
+ tracking_allocator(tracking_allocator<U> const & other) noexcept
+ : allocation_count{other.allocation_count}
+ {}
+
+ //! Allocate memory for n elements of type T.
+ //!
+ //! @param n The number of elements to allocate.
+ //! @return A pointer to the allocated memory.
+ [[nodiscard]] auto allocate(std::size_t n) -> T *
+ {
+ if (allocation_count != nullptr)
+ {
+ ++(*allocation_count);
+ }
+ return static_cast<T *>(::operator new(n * sizeof(T)));
+ }
+
+ //! Deallocate memory for n elements of type T.
+ //!
+ //! @param p A pointer to the memory to deallocate.
+ //! @param n The number of elements to deallocate.
+ auto deallocate(T * p, std::size_t n) noexcept -> void
+ {
+ ::operator delete(p, n * sizeof(T));
+ }
+
+ //! Compare two tracking allocators for equality.
+ //!
+ //! Two allocators are considered equal if they reference the same allocation counter.
+ //!
+ //! @param other The other tracking allocator to compare to.
+ //! @return True if the two tracking allocators are equal, false otherwise.
+ [[nodiscard]] auto operator==(tracking_allocator const & other) const -> bool
+ {
+ return allocation_count == other.allocation_count;
+ }
+
+ //! Compare two tracking_allocators for inequality.
+ //!
+ //! @param other The other tracking_allocator to compare to.
+ //! @return True if the two tracking_allocators are not equal, false otherwise.
+ [[nodiscard]] auto operator!=(tracking_allocator const & other) const -> bool
+ {
+ return allocation_count != other.allocation_count;
+ }
+ };
+
+ //! An allocator that propagates copy assignment.
+ //!
+ //! This allocator is designed to test copy assignment semantics of standard library containers implemented in kstd.
+ //!
+ //! @tparam T The type of the elements to be allocated.
+ template<typename T>
+ struct propagating_allocator : tracking_allocator<T>
+ {
+ //! A flag to indicate that the allocator propagates copy assignment.
+ using propagate_on_container_copy_assignment = std::true_type;
+
+ //! Construct a new propagating allocator referencing the given counter.
+ //!
+ //! @param counter A pointer to a counter that is incremented on allocation.
+ //! @see tracking_allocator::tracking_allocator(int*)
+ propagating_allocator(int * counter)
+ : tracking_allocator<T>{counter}
+ {}
+
+ //! Construct a new propagating allocator by copying from another propagating allocator.
+ //!
+ //! This constructor is templated to allow for conversion from allocators of different types.
+ //!
+ //! @tparam U The type of the elements to be allocated by the other allocator.
+ //! @see tracking_allocator::tracking_allocator(tracking_allocator<U> const&)
+ template<typename U>
+ propagating_allocator(propagating_allocator<U> const & other) noexcept
+ : tracking_allocator<T>{other.allocation_count}
+ {}
+ };
+
+ //! A test input iterator.
+ //!
+ //! This iterator is designed to test input iterator semantics of standard library containers implemented in kstd.
+ struct test_input_iterator
+ {
+ using iterator_concept = std::input_iterator_tag;
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = int;
+ using reference = int const &;
+ using pointer = int const *;
+
+ //! The current element pointed to by the iterator.
+ int const * current;
+ //! The number of elements in the range.
+ std::size_t count;
+
+ explicit test_input_iterator()
+ : current{nullptr}
+ , count{0}
+ {}
+
+ //! Construct a new test input iterator.
+ //!
+ //! @param current The current element pointed to by the iterator.
+ //! @param count The number of elements in the range.
+ explicit test_input_iterator(int const * current, std::size_t count)
+ : current{current}
+ , count{count}
+ {}
+
+ //! Dereference the iterator to get the current element.
+ //!
+ //! @return The current element pointed to by the iterator.
+ [[nodiscard]] auto operator*() const -> int
+ {
+ return *current;
+ }
+
+ //! Increment the iterator to point to the next element.
+ //!
+ //! @return A reference to the incremented iterator.
+ auto operator++() -> test_input_iterator &
+ {
+ ++current;
+ --count;
+ return *this;
+ }
+
+ //! Increment the iterator to point to the next element.
+ auto operator++(int) -> void
+ {
+ ++*this;
+ }
+
+ //! Compare two test input iterators for equality.
+ //!
+ //! @param other The other test input iterator to compare to.
+ //! @return True if the two test input iterators are equal, false otherwise.
+ [[nodiscard]] auto operator==(test_input_iterator const & other) const -> bool
+ {
+ if (current == nullptr && other.current == nullptr)
+ {
+ return true;
+ }
+
+ if (current == nullptr || other.current == nullptr)
+ {
+ return count == other.count;
+ }
+
+ return current == other.current && count == other.count;
+ }
+ };
+
+} // namespace kstd::tests
+
+#endif
diff --git a/libs/kstd/kstd/unikstd.h b/libs/kstd/kstd/unikstd.h
new file mode 100644
index 0000000..aa60be6
--- /dev/null
+++ b/libs/kstd/kstd/unikstd.h
@@ -0,0 +1,12 @@
+#ifndef KSTD_UNIKSTD_HPP
+#define KSTD_UNIKSTD_HPP
+
+#include <cstddef>
+#include <type_traits>
+
+namespace kstd
+{
+ using ssize_t = std::make_signed_t<std::size_t>;
+} // namespace kstd
+
+#endif \ No newline at end of file
diff --git a/libs/kstd/kstd/units b/libs/kstd/kstd/units
new file mode 100644
index 0000000..df5eb37
--- /dev/null
+++ b/libs/kstd/kstd/units
@@ -0,0 +1,149 @@
+#ifndef KSTD_UNITS_HPP
+#define KSTD_UNITS_HPP
+
+#include <compare>
+#include <concepts>
+#include <cstddef>
+
+namespace kstd
+{
+
+ //! A basic template for strongly typed units.
+ template<typename ValueType, typename Tag>
+ struct basic_unit
+ {
+ using value_type = ValueType;
+
+ constexpr basic_unit() noexcept
+ : value{}
+ {}
+
+ explicit constexpr basic_unit(value_type value) noexcept
+ : value{value}
+ {}
+
+ explicit constexpr operator value_type() const noexcept
+ {
+ return value;
+ }
+
+ constexpr auto operator+(basic_unit const & other) const noexcept -> basic_unit
+ {
+ return basic_unit{value + other.value};
+ }
+
+ constexpr auto operator+=(basic_unit const & other) noexcept -> basic_unit &
+ {
+ return *this = *this + other;
+ }
+
+ constexpr auto operator-(basic_unit const & other) const noexcept -> basic_unit
+ {
+ return basic_unit{value - other.value};
+ }
+
+ constexpr auto operator-=(basic_unit const & other) noexcept -> basic_unit &
+ {
+ return *this = *this - other;
+ }
+
+ constexpr auto operator*(std::integral auto factor) noexcept -> basic_unit
+ {
+ return basic_unit{value * factor};
+ }
+
+ constexpr auto operator*=(std::integral auto factor) noexcept -> basic_unit
+ {
+ return *this = *this * factor;
+ }
+
+ constexpr auto operator/(std::integral auto divisor) noexcept -> basic_unit
+ {
+ return basic_unit{value / divisor};
+ }
+
+ constexpr auto operator/=(std::integral auto divisor) noexcept -> basic_unit
+ {
+ return *this = *this / divisor;
+ }
+
+ constexpr auto operator/(basic_unit const & other) const noexcept
+ {
+ return value / other.value;
+ }
+
+ constexpr auto operator<=>(basic_unit const & other) const noexcept -> std::strong_ordering = default;
+
+ value_type value;
+ };
+
+ template<std::integral Factor, typename ValueType, typename Tag>
+ constexpr auto operator*(Factor factor, basic_unit<ValueType, Tag> const & unit) noexcept
+ -> basic_unit<ValueType, Tag>
+ {
+ return basic_unit<ValueType, Tag>{unit.value * factor};
+ }
+
+ namespace units
+ {
+ using bytes = basic_unit<std::size_t, struct bytes_tag>;
+
+ constexpr auto KiB(std::size_t value) noexcept -> bytes
+ {
+ return bytes{value * 1024};
+ }
+
+ constexpr auto MiB(std::size_t value) noexcept -> bytes
+ {
+ return bytes{value * 1024 * 1024};
+ }
+
+ constexpr auto GiB(std::size_t value) noexcept -> bytes
+ {
+ return bytes{value * 1024 * 1024 * 1024};
+ }
+
+ template<typename ValueType>
+ constexpr auto operator+(ValueType * pointer, bytes offset) -> ValueType *
+ {
+ return pointer + offset.value;
+ }
+
+ } // namespace units
+
+ namespace units_literals
+ {
+ constexpr auto operator""_B(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::bytes{value};
+ }
+
+ constexpr auto operator""_KiB(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::KiB(value);
+ }
+
+ constexpr auto operator""_MiB(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::MiB(value);
+ }
+
+ constexpr auto operator""_GiB(unsigned long long value) noexcept -> units::bytes
+ {
+ return units::GiB(value);
+ }
+
+ } // namespace units_literals
+
+ template<typename ValueType>
+ constexpr auto object_size(ValueType const &) -> units::bytes
+ {
+ return units::bytes{sizeof(ValueType)};
+ }
+
+ template<typename T>
+ constexpr auto type_size = units::bytes{sizeof(T)};
+
+} // namespace kstd
+
+#endif
diff --git a/libs/kstd/kstd/vector b/libs/kstd/kstd/vector
new file mode 100644
index 0000000..736b854
--- /dev/null
+++ b/libs/kstd/kstd/vector
@@ -0,0 +1,1079 @@
+#ifndef KSTD_VECTOR_HPP
+#define KSTD_VECTOR_HPP
+
+#include <kstd/allocator>
+#include <kstd/bits/concepts.hpp>
+#include <kstd/os/error.hpp>
+#include <kstd/ranges>
+
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <initializer_list>
+#include <iterator>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+ //! A resizable, contiguous container.
+ //!
+ //! @tparam ValueType The type of values contained in this vector.
+ //! @tparam Allocator The type of allocator used for memory management by this container.
+ template<typename ValueType, typename Allocator = kstd::allocator<ValueType>>
+ struct vector
+ {
+ //! The type of the elements contained in this vector.
+ using value_type = ValueType;
+ //! The allocator used by this vector for memory management.
+ using allocator_type = Allocator;
+ //! The type of all sizes used in and with this vector.
+ using size_type = std::size_t;
+ //! The type of the difference between two iterators.
+ using difference_type = std::ptrdiff_t;
+ //! The type of references to elements in this vector.
+ using reference = value_type &;
+ //! The type of references to constant elements in this vector.
+ using const_reference = value_type const &;
+ //! The type of pointers to elements in this vector.
+ using pointer = std::allocator_traits<allocator_type>::pointer;
+ //! The type of pointers to constant elements in this vector.
+ using const_pointer = std::allocator_traits<allocator_type>::const_pointer;
+ //! The type of iterators into this container.
+ using iterator = pointer;
+ //! The type of constant iterators into this container.
+ using const_iterator = const_pointer;
+ //! The type of reverse iterators into this container.
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ //! The type of constant reverse iterators into this container.
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ //! Construct a new, empty vector.
+ constexpr vector() noexcept(std::is_nothrow_default_constructible_v<allocator_type>)
+ : vector(allocator_type{})
+ {}
+
+ //! Construct a new, empty vector with a given allocator.
+ //!
+ //! @param allocator The allocator to use in the vector.
+ explicit constexpr vector(allocator_type const & allocator) noexcept(
+ std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{0}
+ , m_capacity{0}
+ , m_data{allocate_n(m_capacity)}
+ {}
+
+ //! Construct a new vector and fill it with the given number of default constructed elements.
+ //!
+ //! @param count The number of element to create the vector with.
+ //! @param allocator The allocator to use in the vector.
+ explicit constexpr vector(size_type count, allocator_type const & allocator = allocator_type{}) noexcept(
+ std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{count}
+ , m_capacity{count}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i);
+ }
+ }
+
+ //! Construct a new vector and fill it with the given number of copy constructed elements.
+ //!
+ //! @param count The number of element to create the vector with.
+ //! @param value The value to copy for each element
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(size_type count, const_reference value,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{count}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value);
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //! @tparam ForwardIterator An iterator type used to describe the source range.
+ //! @param first The start of the source range.
+ //! @param last The end of the source range.
+ template<std::forward_iterator ForwardIterator>
+ constexpr vector(ForwardIterator first, ForwardIterator last,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{static_cast<std::size_t>(std::ranges::distance(first, last))}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ for (auto destination = m_data; first != last; ++first, ++destination)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, destination, *first);
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //! @tparam InputIterator An iterator type used to describe the source range.
+ //! @param first The start of the source range.
+ //! @param last The end of the source range.
+ template<std::input_iterator InputIterator>
+ constexpr vector(InputIterator first, InputIterator last,
+ allocator_type const & allocator =
+ allocator_type{}) noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ : m_allocator{allocator}
+ , m_size{0}
+ , m_capacity{0}
+ , m_data{}
+ {
+ while (first != last)
+ {
+ emplace_back(*first);
+ ++first;
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given range.
+ //!
+ //!
+ template<typename Range>
+ requires((std::ranges::forward_range<Range> || std::ranges::sized_range<Range>) &&
+ kstd::bits::container_compatible_range<Range, value_type>)
+ constexpr vector(kstd::from_range_t, Range && range, allocator_type const & allocator = allocator_type{})
+ : m_allocator{allocator}
+ , m_size{std::ranges::size(range)}
+ , m_capacity{m_size}
+ , m_data{allocate_n(m_capacity)}
+ {
+ auto destination = m_data;
+ for (auto && element : std::forward<Range>(range))
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, destination++,
+ std::forward<std::ranges::range_reference_t<Range>>(element));
+ }
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements from a given vector.
+ //!
+ //! @param other The source vector.
+ constexpr vector(vector const & other)
+ : m_allocator{other.m_allocator}
+ , m_size{other.m_size}
+ , m_capacity{other.m_capacity}
+ , m_data(allocate_n(m_capacity))
+ {
+ uninitialized_copy_with_allocator(other.begin(), begin(), other.size());
+ }
+
+ //! Construct a new vector and initialize it's content by moving from a given vector.
+ //!
+ //! @param other The source vector.
+ constexpr vector(vector && other) noexcept
+ : m_allocator{std::move(other.m_allocator)}
+ , m_size{std::exchange(other.m_size, size_type{})}
+ , m_capacity(std::exchange(other.m_capacity, size_type{}))
+ , m_data(std::exchange(other.m_data, nullptr))
+ {}
+
+ //! Construct a new vector and initialize it's content by copying from a given vector.
+ //!
+ //! @param other The source vector.
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(vector const & other, std::type_identity_t<Allocator> const & allocator)
+ : m_allocator{allocator}
+ , m_size{other.m_size}
+ , m_capacity{other.m_capacity}
+ , m_data{allocate_n(m_capacity)}
+ {
+ uninitialized_copy_with_allocator(other.begin(), begin(), other.size());
+ }
+
+ //! Construct a new vector and initialize it's content by copying from a given vector.
+ //!
+ //! @param other The source vector.
+ //! @param allocator The allocator to use in the vector.
+ constexpr vector(vector && other, std::type_identity_t<Allocator> const & allocator)
+ : m_allocator{allocator}
+ , m_size{}
+ , m_capacity{}
+ , m_data{}
+ {
+ if constexpr (!std::allocator_traits<allocator_type>::is_always_equal::value)
+ {
+ if (m_allocator != other.m_allocator)
+ {
+ m_capacity = other.size();
+ m_data = allocate_n(capacity());
+ m_size = other.size();
+ uninitialized_move_with_allocator(other.begin(), begin(), other.size());
+ other.clear();
+ return;
+ }
+ }
+ m_size = std::exchange(other.m_size, size_type{});
+ m_capacity = std::exchange(other.m_capacity, size_type{});
+ m_data = std::exchange(other.m_data, nullptr);
+ }
+
+ //! Construct a new vector and initialize it's content by copying all elements in the given initializer list.
+ //!
+ //! @param list The initializer list containing the source objects.
+ vector(std::initializer_list<value_type> list, allocator_type const & allocator = allocator_type{})
+ : vector{std::ranges::begin(list), std::ranges::end(list), allocator}
+ {}
+
+ //! Destroy this vector.
+ constexpr ~vector()
+ {
+ clear_and_deallocate();
+ }
+
+ //! Replace the contents of this vector by the copying from the given source vector.
+ //!
+ //! @param other The source vector.
+ constexpr auto operator=(vector const & other) -> vector &
+ {
+ if (this == std::addressof(other))
+ {
+ return *this;
+ }
+
+ if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value)
+ {
+ if (get_allocator() != other.get_allocator())
+ {
+ clear_and_deallocate();
+ }
+ m_allocator = other.get_allocator();
+ }
+
+ if (capacity() >= other.size())
+ {
+ auto const overlap = std::min(m_size, other.m_size);
+ std::ranges::copy(other.begin(), other.begin() + overlap, begin());
+
+ if (m_size < other.m_size)
+ {
+ uninitialized_copy_with_allocator(other.begin() + size(), begin() + size(), other.m_size - size());
+ }
+ else if (m_size > other.m_size)
+ {
+ destroy_n(begin() + other.size(), size() - other.size());
+ }
+ }
+ else
+ {
+ auto new_data = allocate_n(other.size());
+ uninitialized_copy_with_allocator(other.begin(), new_data, other.size());
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = other.size();
+ }
+
+ m_size = other.size();
+ return *this;
+ }
+
+ //! Replace the contents fo this vector by moving from the given source.
+ //!
+ //! @param other The source vector.
+ constexpr auto operator=(vector && other) noexcept(
+ std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value ||
+ std::allocator_traits<allocator_type>::is_always_equal::value) -> vector &
+ {
+ using std::swap;
+
+ if (this == std::addressof(other))
+ {
+ return *this;
+ }
+
+ if constexpr (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
+ {
+ clear_and_deallocate();
+ swap(m_allocator, other.m_allocator);
+ swap(m_size, other.m_size);
+ swap(m_capacity, other.m_capacity);
+ swap(m_data, other.m_data);
+ }
+ else if (m_allocator == other.m_allocator)
+ {
+ clear_and_deallocate();
+ swap(m_size, other.m_size);
+ swap(m_capacity, other.m_capacity);
+ swap(m_data, other.m_data);
+ }
+ else
+ {
+ if (capacity() >= other.size())
+ {
+ auto const overlap = std::min(size(), other.size());
+ std::ranges::move(other.begin(), other.begin() + overlap, begin());
+
+ if (size() < other.size())
+ {
+ uninitialized_move_with_allocator(other.begin() + size(), begin() + size(), other.size() - size());
+ }
+ else if (size() > other.size())
+ {
+ destroy_n(begin() + other.size(), size() - other.size());
+ }
+ }
+ else
+ {
+ auto new_data = allocate_n(other.size());
+ uninitialized_move_with_allocator(other.begin(), new_data, other.size());
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = other.m_size;
+ }
+
+ other.destroy_n(other.begin(), other.size());
+ m_size = std::exchange(other.m_size, size_type{});
+ }
+
+ return *this;
+ }
+
+ //! Get a copy of the allocator associated with this vector.
+ [[nodiscard]] constexpr auto get_allocator() const noexcept(std::is_nothrow_copy_constructible_v<allocator_type>)
+ -> allocator_type
+ {
+ return m_allocator;
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! This function will panic if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto at(size_type index) -> reference
+ {
+ panic_if_out_of_bounds(index);
+ return (*this)[index];
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! This function will panic if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto at(size_type index) const -> const_reference
+ {
+ panic_if_out_of_bounds(index);
+ return (*this)[index];
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! The behavior is undefined if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto operator[](size_type index) -> reference
+ {
+ return data()[index];
+ }
+
+ //! Get a reference to the element at the given index.
+ //!
+ //! The behavior is undefined if the index is out of bounds for this vector.
+ //!
+ //! @param index The index of the element to retrieve.
+ //! @return A reference to the element at the specified index.
+ [[nodiscard]] constexpr auto operator[](size_type index) const -> const_reference
+ {
+ return data()[index];
+ }
+
+ //! Get a reference to the first element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto front() -> reference
+ {
+ return *begin();
+ }
+
+ //! Get a reference to the first element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto front() const -> const_reference
+ {
+ return *begin();
+ }
+
+ //! Get a reference to the last element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto back() -> reference
+ {
+ return *rbegin();
+ }
+
+ //! Get a reference to the last element of this vector.
+ //!
+ //! The behavior is undefined if this vector is empty.
+ [[nodiscard]] constexpr auto back() const -> const_reference
+ {
+ return *rbegin();
+ }
+
+ //! Get a pointer to the beginning of the underlying contiguous storage.
+ [[nodiscard]] constexpr auto data() noexcept -> pointer
+ {
+ return m_data;
+ }
+
+ //! Get a pointer to the beginning of the underlying contiguous storage.
+ [[nodiscard]] constexpr auto data() const noexcept -> const_pointer
+ {
+ return m_data;
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto begin() noexcept -> iterator
+ {
+ return empty() ? end() : data();
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto begin() const noexcept -> const_iterator
+ {
+ return empty() ? end() : data();
+ }
+
+ //! Get an iterator to the first element of this vector.
+ //!
+ //! @return An iterator to the first element of this container, or end() if the container is empty.
+ [[nodiscard]] constexpr auto cbegin() const noexcept -> const_iterator
+ {
+ return begin();
+ }
+
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto end() noexcept -> pointer
+ {
+ return capacity() ? data() + size() : nullptr;
+ }
+
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto end() const noexcept -> const_pointer
+ {
+ return capacity() ? data() + size() : nullptr;
+ }
+
+ //! Get an iterator past the last element of this vector.
+ [[nodiscard]] constexpr auto cend() const noexcept -> const_pointer
+ {
+ return end();
+ }
+
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto rbegin() noexcept -> reverse_iterator
+ {
+ return empty() ? rend() : reverse_iterator{end()};
+ }
+
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto rbegin() const noexcept -> const_reverse_iterator
+ {
+ return empty() ? rend() : const_reverse_iterator{end()};
+ }
+
+ //! Get a reverse iterator to the reverse beginning.
+ [[nodiscard]] constexpr auto crbegin() const noexcept -> const_reverse_iterator
+ {
+ return rbegin();
+ }
+
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto rend() noexcept -> reverse_iterator
+ {
+ return reverse_iterator{begin()};
+ }
+
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto rend() const noexcept -> const_reverse_iterator
+ {
+ return const_reverse_iterator{begin()};
+ }
+
+ //! Get a reverse iterator to the reverse end.
+ [[nodiscard]] constexpr auto crend() const noexcept -> const_reverse_iterator
+ {
+ return rend();
+ }
+
+ //! Check whether this vector is empty.
+ [[nodiscard]] constexpr auto empty() const noexcept -> bool
+ {
+ return !size();
+ }
+
+ //! Get the number of elements present in this vector.
+ [[nodiscard]] constexpr auto size() const noexcept -> size_type
+ {
+ return m_size;
+ }
+
+ //! Get the maximum possible number of element for this vector.
+ [[nodiscard]] constexpr auto max_size() const noexcept -> size_type
+ {
+ return std::allocator_traits<allocator_type>::max_size(m_allocator);
+ }
+
+ //! Reserve storage for at list the given number of elements.
+ constexpr auto reserve(size_type new_capacity) -> void
+ {
+ if (new_capacity <= capacity())
+ {
+ return;
+ }
+
+ if (new_capacity > max_size())
+ {
+ kstd::os::panic("[kstd:vector] Tried to reserve more space than theoretically possible.");
+ }
+
+ reallocate_exactly(new_capacity);
+ }
+
+ //! Resize this vector to contain @p new_size elements.
+ constexpr auto resize(size_type new_size) -> void
+ {
+ resize(new_size, value_type{});
+ }
+
+ //! Resize this vector to contain @p new_size elements, filling new elements with @p value.
+ constexpr auto resize(size_type new_size, const_reference value) -> void
+ {
+ if (new_size < size())
+ {
+ destroy_n(begin() + new_size, size() - new_size);
+ m_size = new_size;
+ return;
+ }
+
+ if (new_size == size())
+ {
+ return;
+ }
+
+ if (new_size > max_size())
+ {
+ kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible.");
+ }
+
+ if (new_size > capacity())
+ {
+ reserve(new_size);
+ }
+
+ for (auto i = size(); i < new_size; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value);
+ }
+
+ m_size = new_size;
+ }
+
+ //! Get the number of element this vector has currently space for, including elements currently in this vector.
+ [[nodiscard]] constexpr auto capacity() const noexcept -> size_type
+ {
+ return m_capacity;
+ }
+
+ //! Try to release unused storage space.
+ constexpr auto shrink_to_fit() -> void
+ {
+ if (m_size == m_capacity)
+ {
+ return;
+ }
+
+ reallocate_exactly(m_size);
+ }
+
+ //! Clear the contents of this vector.
+ constexpr auto clear() noexcept -> void
+ {
+ destroy_n(begin(), size());
+ m_size = 0;
+ }
+
+ //! Insert an element at a given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param value The value to insert.
+ //! @return An iterator to the inserted element.
+ constexpr auto insert(const_iterator position, value_type const & value) -> iterator
+ {
+ return do_insert(position, value);
+ }
+
+ //! Insert an element at a given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param value The value to insert.
+ //! @return An iterator to the inserted element.
+ constexpr auto insert(const_iterator position, value_type && value) -> iterator
+ {
+ return do_insert(position, std::move(value));
+ }
+
+ //! Insert the element of a given range into the vector at a given position.
+ //!
+ //! @param range The source range to insert elements from.
+ //! @tparam SourceRange A container compatible range type.
+ template<typename SourceRange>
+ requires requires(allocator_type allocator, pointer destination, SourceRange range) {
+ requires kstd::bits::container_compatible_range<SourceRange, ValueType>;
+ requires std::move_constructible<value_type>;
+ requires std::is_move_assignable_v<value_type>;
+ requires std::swappable<value_type>;
+ std::allocator_traits<allocator_type>::construct(allocator, destination, *std::ranges::begin(range));
+ }
+ // NOLINTNEXTLINE(misc-no-recursion)
+ constexpr auto insert_range(const_iterator position, SourceRange && range) -> iterator
+ {
+ auto prefix_size = std::ranges::distance(begin(), position);
+
+ if (position == end())
+ {
+ append_range(std::forward<SourceRange>(range));
+ return begin() + prefix_size;
+ }
+
+ if constexpr (std::ranges::forward_range<SourceRange> || std::ranges::sized_range<SourceRange>)
+ {
+ auto number_of_elements = static_cast<size_type>(std::ranges::distance(range));
+ if (!number_of_elements)
+ {
+ return begin() + prefix_size;
+ }
+
+ if (capacity() - size() < number_of_elements)
+ {
+ reserve(size() + std::max(size(), number_of_elements));
+ }
+
+ auto insert_position = begin() + prefix_size;
+ auto suffix_size = static_cast<size_type>(std::ranges::distance(insert_position, end()));
+
+ if (number_of_elements >= suffix_size)
+ {
+ uninitialized_move_with_allocator(insert_position, insert_position + number_of_elements, suffix_size);
+ auto result = std::ranges::copy_n(std::ranges::begin(range), suffix_size, insert_position);
+ uninitialized_copy_with_allocator(std::move(result.in), end(), number_of_elements - suffix_size);
+ }
+ else
+ {
+ uninitialized_move_with_allocator(end() - number_of_elements, end(), number_of_elements);
+ std::ranges::move_backward(insert_position, end() - number_of_elements, end());
+ std::ranges::copy_n(std::ranges::begin(range), number_of_elements, insert_position);
+ }
+
+ m_size += number_of_elements;
+ return insert_position;
+ }
+
+ auto range_begin = std::ranges::begin(range);
+ auto range_end = std::ranges::end(range);
+
+ auto remainder = vector{get_allocator()};
+ for (; range_begin != range_end; ++range_begin)
+ {
+ remainder.emplace_back(*static_cast<std::ranges::iterator_t<SourceRange>>(range_begin));
+ }
+ reserve(size() + std::max(size(), remainder.size()));
+
+ return insert_range(begin() + prefix_size, remainder);
+ }
+
+ template<typename... Args>
+ constexpr auto emplace(const_iterator position, Args &&... args) -> iterator
+ {
+ auto prefix_size = std::ranges::distance(cbegin(), position);
+ if (position == cend())
+ {
+ emplace_back(std::forward<Args>(args)...);
+ }
+ else if (m_capacity == m_size)
+ {
+ reallocate_and_insert(begin() + prefix_size, std::forward<Args>(args)...);
+ }
+ else
+ {
+ auto to_insert = value_type{std::forward<Args>(args)...};
+ auto insert_position = begin() + prefix_size;
+ shift_back(insert_position);
+ *insert_position = std::move(to_insert);
+ ++m_size;
+ }
+
+ return begin() + prefix_size;
+ }
+
+ //! Erase an element at a given position.
+ //!
+ //! @note This function will panic if position == end()
+ //!
+ //! @param position An interator pointing to the element to delete
+ //! @return An iterator pointing to the element after the deleted element
+ constexpr auto erase(const_iterator position) -> iterator
+ {
+ if (position == end())
+ {
+ os::panic("[kstd:vector] Attempted to erase end()!");
+ }
+
+ auto prefix_size = std::ranges::distance(cbegin(), position);
+
+ std::ranges::move(begin() + prefix_size + 1, end(), begin() + prefix_size);
+ std::allocator_traits<allocator_type>::destroy(m_allocator, end() - 1);
+ --m_size;
+
+ return begin() + prefix_size;
+ }
+
+ //! Erase a range of elements from this vector.
+ //!
+ //! @param first The start of the range to erase.
+ //! @param last The end of the range to erase.
+ //! @return An iterator pointing to the element after the last deleted element.
+ constexpr auto erase(const_iterator first, const_iterator last) -> iterator
+ {
+ if (first == last)
+ {
+ return begin() + std::ranges::distance(cbegin(), first);
+ }
+
+ auto prefix_size = std::ranges::distance(cbegin(), first);
+ auto element_count = std::ranges::distance(first, last);
+
+ std::ranges::move(begin() + prefix_size + element_count, end(), begin() + prefix_size);
+ destroy_n(end() - element_count, element_count);
+ m_size -= element_count;
+
+ return begin() + prefix_size;
+ }
+
+ //! Append a given element to this vector via copy construction.
+ constexpr auto push_back(value_type const & value) -> void
+ {
+ emplace_back(value);
+ }
+
+ //! Append a given element to this vector via move construction.
+ constexpr auto push_back(value_type && value) -> void
+ {
+ emplace_back(std::move(value));
+ }
+
+ //! Append a given element to this vector via direct construction.
+ template<class... Args>
+ constexpr auto emplace_back(Args &&... args) -> reference
+ {
+ if (m_capacity == m_size)
+ {
+ reallocate_and_insert(end(), std::forward<Args>(args)...);
+ }
+ else
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, data() + size(), std::forward<Args>(args)...);
+ ++m_size;
+ }
+ return this->back();
+ }
+
+ //! Append the elements of a given range to this vector.
+ //!
+ //! @param range The range of elements to be appended.
+ //! @tparam SourceRange A container compatible range type.
+ template<kstd::bits::container_compatible_range<ValueType> SourceRange>
+ requires requires(Allocator allocator, pointer destination, SourceRange range) {
+ std::allocator_traits<Allocator>::construct(allocator, destination, *std::ranges::begin(range));
+ }
+ // NOLINTNEXTLINE(cppcoreguidelines-missing-std-forward, misc-no-recursion)
+ constexpr auto append_range(SourceRange && range) -> void
+ {
+ if constexpr (std::ranges::forward_range<SourceRange> || std::ranges::sized_range<SourceRange>)
+ {
+ auto number_of_elements = static_cast<size_type>(std::ranges::distance(range));
+
+ if (!capacity())
+ {
+ reserve(number_of_elements);
+ }
+
+ if (capacity() - size() >= number_of_elements)
+ {
+ uninitialized_copy_with_allocator(std::ranges::begin(range), end(), number_of_elements);
+ m_size += number_of_elements;
+ return;
+ }
+
+ auto new_capacity = m_capacity + std::max(size(), number_of_elements);
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+
+ uninitialized_move_with_allocator(begin(), new_data, size());
+ uninitialized_copy_with_allocator(std::ranges::begin(range), new_data + size(), number_of_elements);
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size + number_of_elements;
+ return;
+ }
+
+ auto range_begin = std::ranges::begin(range);
+ auto range_end = std::ranges::end(range);
+
+ for (auto i = capacity() - size(); i > 0; --i, ++range_begin)
+ {
+ emplace_back(*range_begin);
+ }
+
+ if (range_begin == range_end)
+ {
+ return;
+ }
+
+ auto remainder = vector{get_allocator()};
+ for (; range_begin != range_end; ++range_begin)
+ {
+ remainder.emplace_back(*static_cast<std::ranges::iterator_t<SourceRange>>(range_begin));
+ }
+ reserve(size() + std::max(size(), remainder.size()));
+ append_range(remainder);
+ }
+
+ //! Remove the last element of this vector.
+ //!
+ //! If this vector is empty, the behavior is undefined.
+ constexpr auto pop_back() -> void
+ {
+ --m_size;
+ std::allocator_traits<allocator_type>::destroy(m_allocator, data() + size());
+ }
+
+ private:
+ //! Use the allocator of this vector to allocate enough space for the given number of elements.
+ //!
+ //! @param count The number of elements to allocate space for.
+ [[nodiscard]] constexpr auto allocate_n(std::size_t count) -> std::allocator_traits<allocator_type>::pointer
+ {
+ if (count)
+ {
+ return std::allocator_traits<allocator_type>::allocate(m_allocator, count);
+ }
+ return nullptr;
+ }
+
+ //! Clear this vector and release it's memory.
+ constexpr auto clear_and_deallocate() -> void
+ {
+ clear();
+ deallocate();
+ }
+
+ //! Release the memory of this vector.
+ constexpr auto deallocate()
+ {
+ if (m_data)
+ {
+ std::allocator_traits<allocator_type>::deallocate(m_allocator, m_data, m_capacity);
+ m_capacity = 0;
+ m_size = 0;
+ m_data = nullptr;
+ }
+ }
+
+ //! Insert an element into this vector at the given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param value The value to insert.
+ template<typename U>
+ constexpr auto do_insert(const_iterator position, U && value)
+ {
+ auto prefix_size = std::ranges::distance(cbegin(), position);
+ if (position == cend())
+ {
+ push_back(std::forward<U>(value));
+ }
+ else if (m_capacity == m_size)
+ {
+ reallocate_and_insert(begin() + prefix_size, std::forward<U>(value));
+ }
+ else if (&value >= cbegin() && &value < cend())
+ {
+ auto temporary = std::forward<U>(value);
+ shift_back(begin() + prefix_size);
+ *(begin() + prefix_size) = std::move(temporary);
+ ++m_size;
+ }
+ else
+ {
+ shift_back(begin() + prefix_size);
+ *(begin() + prefix_size) = std::forward<U>(value);
+ ++m_size;
+ }
+ return begin() + prefix_size;
+ }
+
+ //! Destroy a number of elements in this vector.
+ //!
+ //! @param first The start of the range of the elements to be destroyed.
+ //! @param count The number of elements to destroy.
+ constexpr auto destroy_n(iterator first, std::size_t count) -> void
+ {
+ std::ranges::for_each(first, first + count, [&](auto & element) {
+ std::allocator_traits<allocator_type>::destroy(m_allocator, std::addressof(element));
+ });
+ }
+
+ //! Panic the kernel if the given index is out of bounds.
+ //!
+ //! @param index The index to check.
+ constexpr auto panic_if_out_of_bounds(size_type index) const -> void
+ {
+ if (index >= m_size)
+ {
+ os::panic("[kstd:vector] Attempted to read element at invalid index");
+ }
+ }
+
+ //! Copy a number of elements from a source range into the uninitialized destination range inside this vector.
+ //!
+ //! @param from The start of the source range.
+ //! @param to The start of the target range inside this vector.
+ //! @param count The number of elements to copy
+ template<typename SourceIterator>
+ constexpr auto uninitialized_copy_with_allocator(SourceIterator from, iterator to, size_type count)
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, to++, *from++);
+ }
+ }
+
+ //! Move a number of elements from a source range into the uninitialized destination range inside this vector.
+ //!
+ //! @param from The start of the source range.
+ //! @param to The start of the target range inside this vector.
+ //! @param count The number of elements to copy
+ template<typename SourceIterator>
+ constexpr auto uninitialized_move_with_allocator(SourceIterator from, iterator to, size_type count)
+ {
+ for (auto i = 0uz; i < count; ++i)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, to++, std::move(*from++));
+ }
+ }
+
+ //! Reallocate the storage space to be exactly as large as the given size.
+ constexpr auto reallocate_exactly(size_type new_capacity) -> void
+ {
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+ uninitialized_move_with_allocator(begin(), new_data, old_size);
+ clear_and_deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size;
+ }
+
+ //! Shift all elements, starting the given position, one position back inside the vector.
+ constexpr auto shift_back(iterator starting_at)
+ {
+ std::allocator_traits<allocator_type>::construct(m_allocator, end(), std::move(*(end() - 1)));
+ std::ranges::move_backward(starting_at, end() - 1, end());
+ }
+
+ //! Reallocate the storage of this vector and insert an element at the given position.
+ //!
+ //! @param position The position to insert the element at.
+ //! @param args The constructor arguments for the inserted element.
+ template<typename... Args>
+ constexpr auto reallocate_and_insert(iterator position, Args &&... args)
+ {
+ auto prefix_size = std::ranges::distance(begin(), position);
+ auto suffix_size = std::ranges::distance(position, end());
+ auto new_capacity = m_capacity == 0 ? 1 : m_capacity * 2;
+ auto new_data = allocate_n(new_capacity);
+ auto old_size = size();
+
+ std::allocator_traits<allocator_type>::construct(m_allocator, new_data + prefix_size,
+ std::forward<Args>(args)...);
+ uninitialized_move_with_allocator(begin(), new_data, prefix_size);
+ uninitialized_move_with_allocator(begin() + prefix_size, new_data + prefix_size + 1, suffix_size);
+ destroy_n(begin(), old_size);
+ deallocate();
+ m_data = new_data;
+ m_capacity = new_capacity;
+ m_size = old_size + 1;
+ }
+
+ //! The allocator used by this vector.
+ [[no_unique_address]] allocator_type m_allocator{};
+
+ //! The number of elements in this vector.
+ size_type m_size{};
+
+ //! The number of elements this vector has room for.
+ size_type m_capacity{};
+
+ //! The pointer to the start of the memory managed by this vector.
+ value_type * m_data{};
+ };
+
+ //! Check if the content of two vectors is equal.
+ template<typename ValueType, typename Allocator>
+ constexpr auto operator==(vector<ValueType, Allocator> const & lhs, vector<ValueType, Allocator> const & rhs) -> bool
+ {
+ return std::ranges::equal(lhs, rhs);
+ }
+
+ //! Perform a lexicographical comparison of the content of two vectors.
+ template<typename ValueType, typename Allocator>
+ constexpr auto operator<=>(vector<ValueType, Allocator> const & lhs, vector<ValueType, Allocator> const & rhs)
+ -> decltype(std::declval<ValueType const &>() <=> std::declval<ValueType const &>())
+ {
+ return std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+ }
+
+ //! Deduction guide for vector construction from an interator pair.
+ template<std::forward_iterator ForwardIterator,
+ typename Allocator = kstd::allocator<typename std::iterator_traits<ForwardIterator>::value_type>>
+ vector(ForwardIterator, ForwardIterator, Allocator = Allocator())
+ -> vector<typename std::iterator_traits<ForwardIterator>::value_type, Allocator>;
+
+ //! Deduction guide for vector construction from an interator pair.
+ template<std::input_iterator InputIterator,
+ typename Allocator = kstd::allocator<typename std::iterator_traits<InputIterator>::value_type>>
+ vector(InputIterator, InputIterator, Allocator = Allocator())
+ -> vector<typename std::iterator_traits<InputIterator>::value_type, Allocator>;
+
+ //! Deduction guide for vector construction from a range.
+ template<std::ranges::input_range Range, typename Allocator = kstd::allocator<std::ranges::range_value_t<Range>>>
+ vector(kstd::from_range_t, Range &&, Allocator = Allocator()) -> vector<std::ranges::range_value_t<Range>, Allocator>;
+
+} // namespace kstd
+
+#endif
diff --git a/libs/kstd/kstd/vector.test.cpp b/libs/kstd/kstd/vector.test.cpp
new file mode 100644
index 0000000..8bf8f79
--- /dev/null
+++ b/libs/kstd/kstd/vector.test.cpp
@@ -0,0 +1,2003 @@
+#include <kstd/vector>
+
+#include <kstd/ranges>
+#include <kstd/test_support/os_panic.hpp>
+#include <kstd/test_support/test_types.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <array>
+#include <iterator>
+#include <ranges>
+#include <utility>
+
+SCENARIO("Vector initialization and construction", "[vector]")
+{
+ GIVEN("An empty context")
+ {
+ WHEN("constructing by default")
+ {
+ auto v = kstd::vector<int>{};
+
+ THEN("the vector is empty")
+ {
+ REQUIRE(v.empty());
+ }
+
+ THEN("the size and capacity are zero")
+ {
+ REQUIRE(v.size() == 0);
+ REQUIRE(v.capacity() == 0);
+ }
+ }
+
+ WHEN("constructing with a specific size")
+ {
+ auto v = kstd::vector<int>(10);
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size is and capacity match the specified value")
+ {
+ REQUIRE(v.size() == 10);
+ REQUIRE(v.capacity() == 10);
+ }
+ }
+
+ WHEN("constructing from an initializer list")
+ {
+ auto v = kstd::vector<int>{1, 2, 3, 4, 5};
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size is and capacity match the specified value")
+ {
+ REQUIRE(v.size() == 5);
+ REQUIRE(v.capacity() == 5);
+ }
+
+ THEN("the elements are correctly initialized")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ REQUIRE(v[3] == 4);
+ REQUIRE(v[4] == 5);
+ }
+ }
+ }
+
+ GIVEN("A non-empty range")
+ {
+ auto range = std::array<int, 3>{1, 2, 3};
+
+ WHEN("constructing from a random-access iterator range")
+ {
+ auto v = kstd::vector<int>{std::begin(range), std::end(range)};
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size and capacity match the range size")
+ {
+ REQUIRE(v.size() == std::size(range));
+ REQUIRE(v.capacity() == std::size(range));
+ }
+
+ THEN("the elements are correctly initialized")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("constructing from a range")
+ {
+ auto v = kstd::vector<int>{kstd::from_range, range};
+
+ THEN("the vector is not empty")
+ {
+ REQUIRE_FALSE(v.empty());
+ }
+
+ THEN("the size and capacity match the range size")
+ {
+ REQUIRE(v.size() == std::ranges::size(range));
+ REQUIRE(v.capacity() == std::ranges::size(range));
+ }
+
+ THEN("the elements are correctly initialized")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+
+ GIVEN("A populated vector")
+ {
+ auto source = kstd::vector<int>{1, 2, 3, 4, 5};
+
+ WHEN("copy constructing a new vector")
+ {
+ auto copy = kstd::vector<int>(source);
+
+ THEN("the copy matches the original")
+ {
+ REQUIRE(copy.size() == source.size());
+ REQUIRE(copy.capacity() == source.capacity());
+ REQUIRE(copy[0] == 1);
+ REQUIRE(copy[1] == 2);
+ REQUIRE(copy[2] == 3);
+ REQUIRE(copy[3] == 4);
+ REQUIRE(copy[4] == 5);
+ }
+
+ THEN("the original is left unchanged")
+ {
+ REQUIRE(source.size() == 5);
+ REQUIRE(source.capacity() == 5);
+ REQUIRE(source[0] == 1);
+ REQUIRE(source[1] == 2);
+ REQUIRE(source[2] == 3);
+ REQUIRE(source[3] == 4);
+ REQUIRE(source[4] == 5);
+ }
+ }
+
+ WHEN("move constructing a new vector")
+ {
+ auto moved = kstd::vector<int>(std::move(source));
+
+ THEN("The new vector has the original elements")
+ {
+ REQUIRE(moved.size() == 5);
+ REQUIRE(moved.capacity() == 5);
+ REQUIRE(moved[0] == 1);
+ REQUIRE(moved[1] == 2);
+ REQUIRE(moved[2] == 3);
+ REQUIRE(moved[3] == 4);
+ REQUIRE(moved[4] == 5);
+ }
+
+ THEN("The original vector is left in a valid but unspecified state")
+ {
+ REQUIRE(source.empty());
+ REQUIRE(source.size() == 0);
+ REQUIRE(source.capacity() == 0);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector element access", "[vector]")
+{
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{10, 20, 30};
+
+ WHEN("accessing elements for reading")
+ {
+ THEN("operator[] and at() return the correct elements")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+
+ REQUIRE(v.at(0) == 10);
+ REQUIRE(v.at(1) == 20);
+ REQUIRE(v.at(2) == 30);
+ }
+
+ THEN("front() and back() return the first and last elements")
+ {
+ REQUIRE(v.front() == 10);
+ REQUIRE(v.back() == 30);
+ }
+
+ THEN("data() return a pointer to the contiguous storage")
+ {
+ auto ptr = v.data();
+ REQUIRE(ptr);
+ REQUIRE(ptr[0] == 10);
+ REQUIRE(ptr[1] == 20);
+ REQUIRE(ptr[2] == 30);
+ }
+
+ THEN("accessing out of bounds elements panics")
+ {
+ REQUIRE_THROWS_AS(v.at(3), kstd::tests::os_panic);
+ }
+ }
+
+ WHEN("accessing elements for writing")
+ {
+ v[0] = 100;
+ v.at(1) = 200;
+ v.back() = 300;
+
+ THEN("the elements are correctly modified")
+ {
+ REQUIRE(v[0] == 100);
+ REQUIRE(v[1] == 200);
+ REQUIRE(v[2] == 300);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector iterators", "[vector]")
+{
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{1, 2, 3};
+
+ WHEN("using forward iterators")
+ {
+ THEN("they navigate the elements in the correct forward order")
+ {
+ auto it = v.begin();
+ REQUIRE(it != v.end());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it != v.end());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.end());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it == v.end());
+ }
+
+ THEN("const forward iterators provide correct access")
+ {
+ auto it = v.cbegin();
+ REQUIRE(it != v.cend());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it != v.cend());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.cend());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it == v.cend());
+ }
+ }
+
+ WHEN("using reverse iterators")
+ {
+ THEN("they navigate the elements in the correct reverse order")
+ {
+ auto it = v.rbegin();
+ REQUIRE(it != v.rend());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it != v.rend());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.rend());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it == v.rend());
+ }
+
+ THEN("const reverse iterators provide correct access")
+ {
+ auto it = v.crbegin();
+ REQUIRE(it != v.crend());
+ REQUIRE(*it == 3);
+
+ ++it;
+ REQUIRE(it != v.crend());
+ REQUIRE(*it == 2);
+
+ ++it;
+ REQUIRE(it != v.crend());
+ REQUIRE(*it == 1);
+
+ ++it;
+ REQUIRE(it == v.crend());
+ }
+ }
+ }
+
+ GIVEN("an empty vector")
+ {
+ auto v = kstd::vector<int>{};
+
+ WHEN("getting iterators")
+ {
+ THEN("begin() equals end() and cbegin() equals cend()")
+ {
+ REQUIRE(v.begin() == v.end());
+ REQUIRE(v.cbegin() == v.cend());
+ }
+
+ THEN("rbegin() equals rend() and crbegin() equals crend()")
+ {
+ REQUIRE(v.rbegin() == v.rend());
+ REQUIRE(v.crbegin() == v.crend());
+ }
+ }
+ }
+}
+
+SCENARIO("Vector capacity management", "[vector]")
+{
+ GIVEN("An empty vector")
+ {
+ auto v = kstd::vector<int>{};
+
+ WHEN("reserving space")
+ {
+ v.reserve(10);
+
+ THEN("the capacity is at least the reserved amount")
+ {
+ REQUIRE(v.capacity() >= 10);
+ }
+
+ THEN("the size is still zero")
+ {
+ REQUIRE(v.size() == 0);
+ }
+
+ THEN("the vector is still empty")
+ {
+ REQUIRE(v.empty());
+ }
+ }
+
+ WHEN("reserving space less than or equal to current capacity")
+ {
+ v.reserve(10);
+ auto const current_capacity = v.capacity();
+ v.reserve(5);
+
+ THEN("the capacity remains unchanged")
+ {
+ REQUIRE(v.capacity() == current_capacity);
+ }
+ }
+
+ WHEN("reserving space greater than max_size")
+ {
+ THEN("a panic is triggered")
+ {
+ REQUIRE_THROWS_AS(v.reserve(v.max_size() + 1), kstd::tests::os_panic);
+ }
+ }
+ }
+
+ GIVEN("A populated vector with excess capacity")
+ {
+ auto v = kstd::vector<int>{1, 2, 3};
+ v.reserve(10);
+
+ REQUIRE(v.capacity() == 10);
+
+ WHEN("calling shrink_to_fit")
+ {
+ v.shrink_to_fit();
+
+ THEN("the capacity is reduced to match the size")
+ {
+ REQUIRE(v.capacity() == 3);
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the elements remain unchanged")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector modifiers", "[vector]")
+{
+ GIVEN("An empty vector")
+ {
+ auto v = kstd::vector<int>{};
+
+ WHEN("push_back is called with a value")
+ {
+ v.push_back(10);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 10);
+ }
+ }
+
+ WHEN("emplace_back is called with constructor arguments")
+ {
+ v.emplace_back(20);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("elements are added while capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ v.push_back(10);
+ v.emplace_back(20);
+
+ THEN("the elements are added without reallocation")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ }
+ }
+
+ WHEN("emplace is called with the end iterator and constructor arguments")
+ {
+ v.emplace(v.end(), 20);
+
+ THEN("the element is appended and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("emplace is called while capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ v.emplace(v.end(), 20);
+
+ THEN("the element is appended without reallocation")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v.back() == 20);
+ }
+ }
+
+ WHEN("inserting an element")
+ {
+ auto it = v.insert(v.cbegin(), 40);
+
+ THEN("the size and capacity increase and the element is inserted")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v[0] == 40);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting an lvalue element")
+ {
+ auto const value = 40;
+ auto it = v.insert(v.cbegin(), value);
+
+ THEN("the size and capacity increase and the element is inserted")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() >= 1);
+ REQUIRE(v[0] == 40);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("appending a range")
+ {
+ auto const range = std::views::iota(0, 3);
+ v.append_range(range);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[0] == 0);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 2);
+ }
+ }
+
+ WHEN("appending from an input range")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+
+ v.append_range(std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("appending from an input range with sufficient capacity")
+ {
+ v.reserve(3);
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+
+ v.append_range(std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity stays the same")
+ {
+ REQUIRE(v.capacity() == 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size")
+ {
+ v.resize(3);
+
+ THEN("the size and capacity increase and the elements are value initialized")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() >= 3);
+ REQUIRE(v[0] == 0);
+ REQUIRE(v[1] == 0);
+ REQUIRE(v[2] == 0);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size with initial value")
+ {
+ v.resize(3, 2);
+
+ THEN("the size and capacity increase and the elements are initialized to the given value")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() >= 3);
+ REQUIRE(v[0] == 2);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 2);
+ }
+ }
+
+ WHEN("inserting a range at the beginning")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.begin(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting a range at the end")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.end(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting from an input range without sufficient capacity")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+ auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+ }
+
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{10, 20, 30};
+ auto initial_capacity = v.capacity();
+
+ WHEN("push_back is called")
+ {
+ v.push_back(40);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ }
+ }
+
+ WHEN("emplace_back is called with constructor arguments")
+ {
+ v.emplace_back(40);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ }
+ }
+
+ WHEN("emplace is called with an iterator and constructor arguments")
+ {
+ auto it = v.emplace(v.begin() + 2, 25);
+
+ THEN("the element is inserted and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v.at(2) == 25);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.cbegin() + 2);
+ REQUIRE(*it == 25);
+ }
+ }
+
+ WHEN("emplace is called with an iterator and sufficient capacity")
+ {
+ v.reserve(v.size() + 1);
+
+ auto it = v.emplace(v.begin() + 2, 25);
+
+ THEN("the element is inserted and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() >= initial_capacity);
+ REQUIRE(v.at(2) == 25);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.cbegin() + 2);
+ REQUIRE(*it == 25);
+ }
+ }
+
+ WHEN("push_back is called with a reference to an internal element")
+ {
+ v.shrink_to_fit();
+ auto const original_value = v[0];
+
+ v.push_back(v[0]);
+
+ THEN("reallocation handles the internal reference safely without dangling")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == original_value);
+ REQUIRE(v.back() == original_value);
+ }
+ }
+
+ WHEN("pop_back is called")
+ {
+ v.pop_back();
+
+ THEN("the last element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v.capacity() == initial_capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ }
+ }
+
+ WHEN("clear is called")
+ {
+ v.clear();
+
+ THEN("the vector is empty")
+ {
+ REQUIRE(v.empty());
+ REQUIRE(v.size() == 0);
+ REQUIRE(v.capacity() == initial_capacity);
+ }
+ }
+
+ WHEN("inserting at the beginning")
+ {
+ auto it = v.insert(v.cbegin(), 5);
+
+ THEN("the element is inserted at the front")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 5);
+ REQUIRE(v[1] == 10);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting in the middle")
+ {
+ auto it = v.insert(v.cbegin() + 1, 15);
+
+ THEN("the element is inserted in the middle")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 15);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting at the end")
+ {
+ auto it = v.insert(v.cend(), 40);
+
+ THEN("the element is inserted at the back")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ REQUIRE(it == v.begin() + 3);
+ }
+ }
+
+ WHEN("inserting an lvalue at the end")
+ {
+ auto const value = 40;
+ auto it = v.insert(v.cend(), value);
+
+ THEN("the element is inserted at the back")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 40);
+ REQUIRE(it == v.begin() + 3);
+ }
+ }
+
+ WHEN("inserting when capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+
+ auto it = v.insert(v.cbegin() + 1, 15);
+
+ THEN("the element is added without reallocation")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v.capacity() == capacity);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 15);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting a reference to an existing element with reallocation")
+ {
+ v.shrink_to_fit();
+ REQUIRE(v.capacity() == v.size());
+ auto it = v.insert(v.cbegin() + 1, v[2]);
+
+ THEN("the element is correctly copied and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting a reference to an existing element without reallocation")
+ {
+ v.reserve(10);
+ REQUIRE(v.capacity() > v.size());
+ auto it = v.insert(v.cbegin() + 1, v[2]);
+
+ THEN("the element is correctly copied and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting an rvalue reference to an existing element with reallocation")
+ {
+ v.shrink_to_fit();
+ REQUIRE(v.capacity() == v.size());
+ auto it = v.insert(v.cbegin() + 1, std::move(v[2]));
+
+ THEN("the element is correctly moved and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting an rvalue reference to an existing element without reallocation")
+ {
+ v.reserve(10);
+ REQUIRE(v.capacity() > v.size());
+ auto it = v.insert(v.cbegin() + 1, std::move(v[2]));
+
+ THEN("the element is correctly moved and inserted")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing the first element")
+ {
+ auto it = v.erase(v.cbegin());
+
+ THEN("the first element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 20);
+ REQUIRE(v[1] == 30);
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("erasing a middle element")
+ {
+ auto it = v.erase(v.cbegin() + 1);
+
+ THEN("the middle element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing the last element")
+ {
+ auto it = v.erase(v.cend() - 1);
+
+ THEN("the last element is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(it == v.end());
+ }
+ }
+
+ WHEN("erasing the end() iterator")
+ {
+ THEN("a panic is triggered")
+ {
+ REQUIRE_THROWS_AS(v.erase(v.end()), kstd::tests::os_panic);
+ }
+ }
+
+ WHEN("erasing a range of elements")
+ {
+ auto it = v.erase(v.cbegin() + 1, v.cend() - 1);
+
+ THEN("the specified range is removed and the size decreases")
+ {
+ REQUIRE(v.size() == 2);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing an empty range")
+ {
+ auto it = v.erase(v.cbegin() + 1, v.cbegin() + 1);
+
+ THEN("the vector is unchanged")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("appending a range")
+ {
+ auto initial_size = v.size();
+ v.append_range(std::views::iota(0, 3));
+
+ THEN("capacity is increased")
+ {
+ REQUIRE(v.capacity() >= initial_capacity);
+ }
+
+ THEN("size is increased")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the elements are appended")
+ {
+ REQUIRE(v[initial_size + 0] == 0);
+ REQUIRE(v[initial_size + 1] == 1);
+ REQUIRE(v[initial_size + 2] == 2);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size")
+ {
+ auto initial_size = v.size();
+ v.resize(initial_size + 3);
+
+ THEN("the size and capacity increase and the elements are value initialized")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ REQUIRE(v.capacity() >= initial_size + 3);
+ REQUIRE(v[initial_size + 0] == 0);
+ REQUIRE(v[initial_size + 1] == 0);
+ REQUIRE(v[initial_size + 2] == 0);
+ }
+ }
+
+ WHEN("resizing the vector to a greater size with initial value")
+ {
+ auto initial_size = v.size();
+ v.resize(initial_size + 3, 2);
+
+ THEN("the size and capacity increase and the elements are initialized to the given value")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ REQUIRE(v.capacity() >= initial_size + 3);
+ REQUIRE(v[initial_size + 0] == 2);
+ REQUIRE(v[initial_size + 1] == 2);
+ REQUIRE(v[initial_size + 2] == 2);
+ }
+ }
+
+ WHEN("resizing the vector to a smaller size")
+ {
+ v.resize(1);
+
+ THEN("the size decreases and the elements are destroyed")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v[0] == 10);
+ }
+ }
+
+ WHEN("inserting an empty range")
+ {
+ auto initial_size = v.size();
+ auto it = v.insert_range(v.begin(), std::views::empty<int>);
+
+ THEN("the size does not change")
+ {
+ REQUIRE(v.size() == initial_size);
+ }
+
+ THEN("the capacity does not change")
+ {
+ REQUIRE(v.capacity() == initial_capacity);
+ }
+
+ THEN("the content is unchanged")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ }
+
+ THEN("the returned iterator points to the position of insertion")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting a range at the beginning")
+ {
+ auto initial_size = v.size();
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.begin(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= initial_size + 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ REQUIRE(v[3] == 10);
+ REQUIRE(v[4] == 20);
+ REQUIRE(v[5] == 30);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+
+ WHEN("inserting a range at the end")
+ {
+ auto initial_size = v.size();
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.end(), arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= initial_size + 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v[2] == 30);
+ REQUIRE(v[3] == 1);
+ REQUIRE(v[4] == 2);
+ REQUIRE(v[5] == 3);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin() + initial_size);
+ }
+ }
+
+ WHEN("inserting a range that causes reallocation")
+ {
+ auto initial_size = v.size();
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto it = v.insert_range(v.begin() + 1, arr);
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == initial_size + 3);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= initial_size + 3);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 2);
+ REQUIRE(v[3] == 3);
+ REQUIRE(v[4] == 20);
+ REQUIRE(v[5] == 30);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting fewer elements than the suffix without reallocation")
+ {
+ v.reserve(10);
+ auto const capacity = v.capacity();
+ auto const arr = std::array<int, 1>{1};
+ auto it = v.insert_range(v.begin() + 1, arr);
+
+ THEN("the elements are correctly placed")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ }
+
+ THEN("no reallocation occurs")
+ {
+ REQUIRE(v.capacity() == capacity);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting fewer elements than the suffix without sufficient capacity")
+ {
+ v.shrink_to_fit();
+ auto const arr = std::array<int, 1>{1};
+ auto it = v.insert_range(v.begin() + 1, arr);
+
+ THEN("the elements are correctly placed")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0] == 10);
+ REQUIRE(v[1] == 1);
+ REQUIRE(v[2] == 20);
+ REQUIRE(v[3] == 30);
+ }
+
+ THEN("the returned iterator points to the inserted element")
+ {
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("inserting from an input range without sufficient capacity")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+ auto it = v.insert_range(v.begin(), std::ranges::subrange{first, last});
+
+ THEN("the size increases")
+ {
+ REQUIRE(v.size() == 6);
+ }
+
+ THEN("the capacity increases")
+ {
+ REQUIRE(v.capacity() >= 6);
+ }
+
+ THEN("the elements are inserted")
+ {
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[1] == 2);
+ REQUIRE(v[2] == 3);
+ REQUIRE(v[3] == 10);
+ REQUIRE(v[4] == 20);
+ REQUIRE(v[5] == 30);
+ }
+
+ THEN("the returned iterator points to the beginning of the inserted range")
+ {
+ REQUIRE(it == v.begin());
+ }
+ }
+ }
+}
+
+SCENARIO("Vector comparison", "[vector]")
+{
+ GIVEN("Two identical vectors")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{1, 2, 3};
+
+ WHEN("comparing for equality")
+ {
+ THEN("the vectors are equal")
+ {
+ REQUIRE(v1 == v2);
+ }
+
+ THEN("the vectors and not not-equal")
+ {
+ REQUIRE_FALSE(v1 != v2);
+ }
+ }
+
+ WHEN("comparing using the spaceship operator")
+ {
+ THEN("the vectors are equivalent")
+ {
+ REQUIRE((v1 <=> v2) == 0);
+ REQUIRE(v1 <= v2);
+ REQUIRE(v1 >= v2);
+ }
+ }
+ }
+
+ GIVEN("Two vectors of different sizes")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{1, 2, 3, 4};
+
+ WHEN("comparing for equality")
+ {
+ THEN("the vectors are not equal")
+ {
+ REQUIRE_FALSE(v1 == v2);
+ }
+
+ THEN("the vectors are not-equal")
+ {
+ REQUIRE(v1 != v2);
+ }
+ }
+
+ WHEN("comparing for ordering")
+ {
+ THEN("the shorter vector evaluates as less than the longer vector")
+ {
+ REQUIRE(v1 < v2);
+ REQUIRE(v2 > v1);
+ }
+ }
+ }
+
+ GIVEN("Two vectors of the same size but different elements")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{1, 2, 4};
+
+ WHEN("comparing for ordering")
+ {
+ THEN("they are ordered lexicographically")
+ {
+ REQUIRE(v1 < v2);
+ REQUIRE(v2 > v1);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector with non-default-constructible types", "[vector]")
+{
+ GIVEN("A type without a default constructor")
+ {
+ WHEN("constructing an empty vector")
+ {
+ auto v = kstd::vector<kstd::tests::non_default_constructible>{};
+
+ THEN("the vector is empty")
+ {
+ REQUIRE(v.empty());
+ }
+ }
+
+ WHEN("using emplace_back")
+ {
+ auto v = kstd::vector<kstd::tests::non_default_constructible>{};
+ v.emplace_back(40);
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back() == kstd::tests::non_default_constructible{40});
+ }
+ }
+ }
+}
+
+SCENARIO("Vector with custom allocator", "[vector]")
+{
+ GIVEN("a tracking allocator acting as the vector's memory manager")
+ {
+ auto allocations = 0;
+ auto allocator = kstd::tests::tracking_allocator<int>{&allocations};
+
+ WHEN("a vector uses this allocator to allocate memory")
+ {
+ auto v = kstd::vector<int, kstd::tests::tracking_allocator<int>>(allocator);
+ REQUIRE(allocations == 0);
+
+ v.reserve(10);
+
+ THEN("the allocator was used to allocate memory")
+ {
+ REQUIRE(allocations > 0);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector modifier move semantics", "[vector]")
+{
+ GIVEN("An empty vector and a move tracker element")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ auto tracker = kstd::tests::special_member_tracker{40};
+
+ WHEN("push_back is called with the move tracker")
+ {
+ v.push_back(std::move(tracker));
+
+ THEN("the element is added and the size and capacity increase")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back().move_constructed_count == 1);
+ REQUIRE(v.back().copy_constructed_count == 0);
+ REQUIRE(v.back().value == 40);
+ }
+
+ THEN("the original tracker is left in a valid but unspecified state")
+ {
+ REQUIRE(tracker.value == -1);
+ }
+ }
+ }
+
+ GIVEN("An empty vector")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+
+ WHEN("emplace_back is called with constructor arguments")
+ {
+ v.emplace_back(40);
+
+ THEN("the element is constructed directly, without moves or copies")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
+ REQUIRE(v.back().copy_constructed_count == 0);
+ REQUIRE(v.back().value == 40);
+ }
+ }
+ }
+
+ GIVEN("A populated vector of move trackers")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ v.reserve(10);
+ v.emplace_back(10);
+ v.emplace_back(20);
+ v.emplace_back(30);
+
+ WHEN("inserting an element in the middle with sufficient capacity")
+ {
+ auto const tracker = kstd::tests::special_member_tracker{15};
+ v.insert(v.cbegin() + 1, tracker);
+
+ THEN("the shifted elements are move-assigned and the new element is copy-assigned")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+ REQUIRE(v[1].value == 15);
+ REQUIRE(v[1].move_assigned_count == 0);
+ REQUIRE(v[1].copy_assigned_count == 1);
+ REQUIRE(tracker.copied_from_count == 1);
+ REQUIRE(v[2].value == 20);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[2].copy_assigned_count == 0);
+ REQUIRE(v[3].value == 30);
+ REQUIRE(v[3].move_constructed_count == 1);
+ }
+ }
+
+ WHEN("inserting an rvalue element in the middle with sufficient capacity")
+ {
+ auto tracker = kstd::tests::special_member_tracker{15};
+ v.insert(v.cbegin() + 1, std::move(tracker));
+
+ THEN("the shifted elements are move-assigned and the new element is move-assigned")
+ {
+ REQUIRE(v.size() == 4);
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+ REQUIRE(v[1].value == 15);
+ REQUIRE(v[1].move_assigned_count == 1);
+ REQUIRE(v[1].copy_assigned_count == 0);
+ REQUIRE(tracker.moved_from_count == 1);
+ REQUIRE(v[2].value == 20);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[3].value == 30);
+ REQUIRE(v[3].move_constructed_count == 1);
+ }
+ }
+
+ WHEN("erasing an element in the middle")
+ {
+ for (auto & elem : v)
+ {
+ elem.reset_counts();
+ }
+
+ auto it = v.erase(v.cbegin() + 1);
+
+ THEN("the subsequent elements are move-assigned leftwards")
+ {
+ REQUIRE(v.size() == 2);
+
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+
+ REQUIRE(v[1].value == 30);
+ REQUIRE(v[1].move_assigned_count == 1);
+ REQUIRE(v[1].copy_assigned_count == 0);
+
+ REQUIRE(v.data()[2].destroyed_count == 1);
+ REQUIRE(v.data()[2].moved_from_count == 1);
+
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+
+ WHEN("erasing the last element")
+ {
+ for (auto & elem : v)
+ {
+ elem.reset_counts();
+ }
+
+ auto it = v.erase(v.cend() - 1);
+
+ THEN("no elements are moved, just the last element destroyed")
+ {
+ REQUIRE(v.size() == 2);
+
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_constructed_count == 0);
+ REQUIRE(v[0].copy_constructed_count == 0);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+
+ REQUIRE(v[1].value == 20);
+ REQUIRE(v[1].move_constructed_count == 0);
+ REQUIRE(v[1].copy_constructed_count == 0);
+ REQUIRE(v[1].move_assigned_count == 0);
+ REQUIRE(v[1].copy_assigned_count == 0);
+
+ REQUIRE(v.data()[2].destroyed_count == 1);
+ REQUIRE(v.data()[2].moved_from_count == 0);
+ REQUIRE(v.data()[2].copied_from_count == 0);
+
+ REQUIRE(it == v.end());
+ }
+ }
+
+ WHEN("erasing a range of elements in the middle")
+ {
+ v.emplace_back(40);
+ v.emplace_back(50);
+
+ for (auto & elem : v)
+ {
+ elem.reset_counts();
+ }
+
+ auto it = v.erase(v.cbegin() + 1, v.cbegin() + 3);
+
+ THEN("the specified elements are destroyed and subsequent elements are move-assigned leftwards")
+ {
+ REQUIRE(v.size() == 3);
+
+ REQUIRE(v[0].value == 10);
+ REQUIRE(v[0].move_constructed_count == 0);
+ REQUIRE(v[0].copy_constructed_count == 0);
+ REQUIRE(v[0].move_assigned_count == 0);
+ REQUIRE(v[0].copy_assigned_count == 0);
+
+ REQUIRE(v[1].value == 40);
+ REQUIRE(v[1].move_constructed_count == 0);
+ REQUIRE(v[1].copy_constructed_count == 0);
+ REQUIRE(v[1].move_assigned_count == 1);
+ REQUIRE(v[1].copy_assigned_count == 0);
+
+ REQUIRE(v[2].value == 50);
+ REQUIRE(v[2].move_constructed_count == 0);
+ REQUIRE(v[2].copy_constructed_count == 0);
+ REQUIRE(v[2].move_assigned_count == 1);
+ REQUIRE(v[2].copy_assigned_count == 0);
+
+ REQUIRE(v.data()[3].destroyed_count == 1);
+ REQUIRE(v.data()[3].moved_from_count == 1);
+ REQUIRE(v.data()[3].copied_from_count == 0);
+
+ REQUIRE(v.data()[4].destroyed_count == 1);
+ REQUIRE(v.data()[4].moved_from_count == 1);
+ REQUIRE(v.data()[4].copied_from_count == 0);
+
+ REQUIRE(it == v.begin() + 1);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector advanced construction", "[vector]")
+{
+ GIVEN("A count and a default value")
+ {
+ WHEN("constructing with count and default argument")
+ {
+ auto const count = 5uz;
+ auto const value = 42;
+ auto v = kstd::vector<int>(count, value);
+
+ THEN("the vector is initialized with count copies of value")
+ {
+ REQUIRE(v.size() == 5);
+ REQUIRE(v.capacity() == 5);
+ REQUIRE(v.front() == 42);
+ REQUIRE(v.back() == 42);
+ }
+ }
+ }
+
+ GIVEN("A pure input iterator range")
+ {
+ WHEN("constructing from input iterators")
+ {
+ auto const arr = std::array<int, 3>{1, 2, 3};
+ auto const first = kstd::tests::test_input_iterator{arr.data(), arr.size()};
+ auto const last = kstd::tests::test_input_iterator{};
+
+ auto v = kstd::vector<int>(first, last);
+
+ THEN("the vector is generated dynamically and initialized correctly")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+
+ GIVEN("A tracking allocator and a source vector")
+ {
+ auto allocs = 0;
+ auto allocator = kstd::tests::tracking_allocator<int>{&allocs};
+ auto source = kstd::vector<int, kstd::tests::tracking_allocator<int>>{allocator};
+ source.push_back(1);
+ source.push_back(2);
+ source.push_back(3);
+
+ allocs = 0;
+
+ WHEN("copy constructing with an allocator")
+ {
+ auto copy = kstd::vector<int, kstd::tests::tracking_allocator<int>>(source, allocator);
+
+ THEN("the copy succeeds and the allocator is used")
+ {
+ REQUIRE(copy.size() == 3);
+ REQUIRE(allocs > 0);
+ REQUIRE(copy[0] == 1);
+ REQUIRE(copy[2] == 3);
+ }
+ }
+
+ WHEN("move constructing with an identically comparing allocator")
+ {
+ auto moved = kstd::vector<int, kstd::tests::tracking_allocator<int>>(std::move(source), allocator);
+
+ THEN("the move succeeds and no new allocations are made (memory is stolen)")
+ {
+ REQUIRE(moved.size() == 3);
+ REQUIRE(allocs == 0);
+ REQUIRE(moved[0] == 1);
+ REQUIRE(moved[2] == 3);
+ }
+ }
+
+ WHEN("move constructing with a non-equal allocator")
+ {
+ auto allocs2 = 0;
+ auto allocator2 = kstd::tests::tracking_allocator<int>{&allocs2};
+
+ auto moved = kstd::vector<int, kstd::tests::tracking_allocator<int>>(std::move(source), allocator2);
+
+ THEN("the move allocates new memory and moves elements")
+ {
+ REQUIRE(allocs2 > 0);
+ REQUIRE(moved.size() == 3);
+ REQUIRE(source.empty());
+ }
+ }
+ }
+}
+
+SCENARIO("Vector assignment operators", "[vector]")
+{
+ GIVEN("A source vector and an empty target vector")
+ {
+ auto source = kstd::vector<int>{1, 2, 3};
+ auto target = kstd::vector<int>{};
+
+ WHEN("copy assigning")
+ {
+ target = source;
+
+ THEN("the target matches the source")
+ {
+ REQUIRE(target.size() == 3);
+ REQUIRE(target[0] == 1);
+ REQUIRE(target[2] == 3);
+ REQUIRE(source.size() == 3);
+ }
+ }
+
+ WHEN("move assigning")
+ {
+ target = std::move(source);
+
+ THEN("the target assumes the source's data")
+ {
+ REQUIRE(target.size() == 3);
+ REQUIRE(target[0] == 1);
+ REQUIRE(target[2] == 3);
+ REQUIRE(source.empty());
+ }
+ }
+ }
+
+ GIVEN("Vectors with propagating copy allocator")
+ {
+ auto allocs1 = 0;
+ auto allocs2 = 0;
+ auto alloc1 = kstd::tests::propagating_allocator<int>{&allocs1};
+ auto alloc2 = kstd::tests::propagating_allocator<int>{&allocs2};
+
+ auto v1 = kstd::vector<int, kstd::tests::propagating_allocator<int>>{alloc1};
+ v1.push_back(1);
+ v1.push_back(2);
+ auto v2 = kstd::vector<int, kstd::tests::propagating_allocator<int>>{alloc2};
+
+ WHEN("copy assigning")
+ {
+ v2 = v1;
+ THEN("the allocator propagates")
+ {
+ REQUIRE(v2.get_allocator() == v1.get_allocator());
+ }
+ }
+ }
+
+ GIVEN("Vectors for copy assignment overlap")
+ {
+ auto v1 = kstd::vector<int>{1, 2, 3};
+ auto v2 = kstd::vector<int>{4, 5};
+ v2.reserve(10);
+
+ WHEN("copy assigning a larger vector to a smaller one with enough capacity")
+ {
+ v2 = v1;
+ THEN("elements are copied and size is updated")
+ {
+ REQUIRE(v2.size() == 3);
+ REQUIRE(v2.capacity() >= 10);
+ REQUIRE(v2[2] == 3);
+ }
+ }
+
+ auto v3 = kstd::vector<int>{1, 2, 3, 4};
+ v3.reserve(10);
+ WHEN("copy assigning a smaller vector to a larger one")
+ {
+ v3 = v1;
+ THEN("excess elements are destroyed")
+ {
+ REQUIRE(v3.size() == 3);
+ REQUIRE(v3[0] == 1);
+ }
+ }
+ }
+
+ GIVEN("Vectors with the same tracking allocator")
+ {
+ auto allocs = 0;
+ auto alloc = kstd::tests::tracking_allocator<int>{&allocs};
+ auto v1 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc};
+ v1.push_back(1);
+ auto v2 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc};
+
+ WHEN("move assigning")
+ {
+ v2 = std::move(v1);
+ THEN("memory is stolen without allocation")
+ {
+ REQUIRE(v2.size() == 1);
+ REQUIRE(allocs == 1);
+ }
+ }
+ }
+
+ GIVEN("Vectors with different non-propagating tracking allocators")
+ {
+ auto allocs1 = 0;
+ auto allocs2 = 0;
+ auto alloc1 = kstd::tests::tracking_allocator<int>{&allocs1};
+ auto alloc2 = kstd::tests::tracking_allocator<int>{&allocs2};
+
+ auto v1 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc1};
+ v1.push_back(1);
+ v1.push_back(2);
+ v1.push_back(3);
+
+ auto v2 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc2};
+ v2.push_back(4);
+ v2.push_back(5);
+
+ WHEN("move assigning a larger vector to a smaller one without enough capacity")
+ {
+ v2.shrink_to_fit();
+ v2 = std::move(v1);
+ THEN("memory is reallocated and elements are moved")
+ {
+ REQUIRE(v2.size() == 3);
+ REQUIRE(allocs2 > 2);
+ }
+ }
+
+ auto v3 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc2};
+ v3.reserve(10);
+ v3.push_back(4);
+ v3.push_back(5);
+ WHEN("move assigning a larger vector to a smaller one with enough capacity")
+ {
+ v3 = std::move(v1);
+ THEN("elements are move-assigned over overlap and move-constructed over remainder")
+ {
+ REQUIRE(v3.size() == 3);
+ }
+ }
+
+ auto v4 = kstd::vector<int, kstd::tests::tracking_allocator<int>>{alloc2};
+ v4.reserve(10);
+ v4.push_back(4);
+ v4.push_back(5);
+ v4.push_back(6);
+ v4.push_back(7);
+ WHEN("move assigning a smaller vector to a larger one with enough capacity")
+ {
+ v4 = std::move(v1);
+ THEN("overlap is moved and excess is destroyed")
+ {
+ REQUIRE(v4.size() == 3);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector self-assignment operators", "[vector]")
+{
+ GIVEN("A populated vector")
+ {
+ auto v = kstd::vector<int>{1, 2, 3};
+ auto const initial_capacity = v.capacity();
+ auto const * initial_data = v.data();
+
+ WHEN("copy assigning to itself")
+ {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wunknown-warning-option"
+#pragma GCC diagnostic ignored "-Wself-assign-overloaded"
+ v = v;
+#pragma GCC diagnostic pop
+
+ THEN("the vector remains unchanged")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() == initial_capacity);
+ REQUIRE(v.data() == initial_data);
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[2] == 3);
+ }
+ }
+
+ WHEN("move assigning to itself")
+ {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wunknown-warning-option"
+#pragma GCC diagnostic ignored "-Wself-move"
+ v = std::move(v);
+#pragma GCC diagnostic pop
+
+ THEN("the vector remains unchanged")
+ {
+ REQUIRE(v.size() == 3);
+ REQUIRE(v.capacity() == initial_capacity);
+ REQUIRE(v.data() == initial_data);
+ REQUIRE(v[0] == 1);
+ REQUIRE(v[2] == 3);
+ }
+ }
+ }
+}
+
+SCENARIO("Vector const accessors and copy insertion", "[vector]")
+{
+ GIVEN("A const populated vector")
+ {
+ auto const v = kstd::vector<int>{10, 20, 30};
+
+ WHEN("calling const accessors")
+ {
+ THEN("elements are read correctly as const references")
+ {
+ REQUIRE(v.front() == 10);
+ REQUIRE(v.back() == 30);
+ REQUIRE(v[1] == 20);
+ REQUIRE(v.at(1) == 20);
+ }
+ }
+ }
+
+ GIVEN("An empty vector and a const lvalue tracker")
+ {
+ auto v = kstd::vector<kstd::tests::special_member_tracker>{};
+ auto const tracker = kstd::tests::special_member_tracker{42};
+
+ WHEN("push_back is called with the const lvalue")
+ {
+ v.push_back(tracker);
+
+ THEN("the element is gracefully copy-constructed")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.back().value == 42);
+ REQUIRE(v.back().copy_constructed_count == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
+ }
+ }
+
+ WHEN("push_back is called with a const lvalue when capacity is sufficient")
+ {
+ v.reserve(10);
+ auto const current_capacity = v.capacity();
+ v.push_back(tracker);
+
+ THEN("the element is copy-constructed without reallocation")
+ {
+ REQUIRE(v.size() == 1);
+ REQUIRE(v.capacity() == current_capacity);
+ REQUIRE(v.back().value == 42);
+ REQUIRE(v.back().copy_constructed_count == 1);
+ REQUIRE(v.back().move_constructed_count == 0);
+ }
+ }
+ }
+}
diff --git a/libs/kstd/kstd/vformat.cpp b/libs/kstd/kstd/vformat.cpp
new file mode 100644
index 0000000..b7c5121
--- /dev/null
+++ b/libs/kstd/kstd/vformat.cpp
@@ -0,0 +1,209 @@
+#include <kstd/format>
+#include <kstd/string>
+
+#include <cstddef>
+#include <iterator>
+#include <string_view>
+#include <utility>
+
+namespace kstd::bits::format
+{
+
+ auto vformat_to(output_buffer & buffer, std::string_view format, format_args args) -> void
+ {
+ auto context = kstd::format_context{buffer, args};
+ auto parse_context = kstd::format_parse_context{format, args.size()};
+
+ auto it = parse_context.begin();
+ auto end = parse_context.end();
+
+ while (it != end)
+ {
+ if (*it != '{' && *it != '}')
+ {
+ auto start = it;
+ while (it != end && *it != '{' && *it != '}')
+ {
+ std::advance(it, 1);
+ }
+ parse_context.advance_to(it);
+ context.push(std::string_view(start, it - start));
+ continue;
+ }
+
+ if (*it == '{')
+ {
+ std::advance(it, 1);
+ if (it != end && *it == '{')
+ {
+ context.push('{');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ continue;
+ }
+
+ parse_context.advance_to(it);
+ auto index = 0uz;
+
+ if (it != end && *it >= '0' && *it <= '9')
+ {
+ while (it != end && *it >= '0' && *it <= '9')
+ {
+ index = index * 10 + static_cast<std::size_t>(*it - '0');
+ std::advance(it, 1);
+ }
+ parse_context.check_arg_id(index);
+ }
+ else
+ {
+ index = parse_context.next_arg_id();
+ }
+
+ if (it != end && *it == ':')
+ {
+ std::advance(it, 1);
+ }
+
+ parse_context.advance_to(it);
+
+ if (index < args.size())
+ {
+ auto const & arg = args[index];
+ switch (arg.type)
+ {
+ case kstd::bits::format::arg_type::boolean:
+ {
+ auto fmt = kstd::formatter<bool>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.boolean, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::character:
+ {
+ auto fmt = kstd::formatter<char>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.character, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::integer:
+ {
+ auto fmt = kstd::formatter<long long>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.integer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::unsigned_integer:
+ {
+ auto fmt = kstd::formatter<unsigned long long>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.unsigned_integer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::string_view:
+ {
+ auto fmt = kstd::formatter<std::string_view>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.string_view, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::c_string:
+ {
+ auto fmt = kstd::formatter<char const *>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.c_string, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::pointer:
+ {
+ auto fmt = kstd::formatter<void const *>{};
+ auto const parsed = fmt.parse(parse_context);
+ parse_context.advance_to(parsed);
+ fmt.format(arg.value.pointer, context);
+ break;
+ }
+ case kstd::bits::format::arg_type::user_defined:
+ {
+ if (arg.value.user_defined.format)
+ {
+ arg.value.user_defined.format(arg.value.user_defined.pointer, parse_context, context);
+ }
+ else
+ {
+ context.push("{?}");
+ }
+ break;
+ }
+ default:
+ {
+ context.push("{fmt-err: unknown-type}");
+ break;
+ }
+ }
+ }
+ else
+ {
+ context.push("{fmt-err: bound}");
+ }
+
+ it = parse_context.begin();
+
+ if (it != end && *it == '}')
+ {
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ else
+ {
+ context.push("{fmt-err: unconsumed}");
+ while (it != end && *it != '}')
+ {
+ std::advance(it, 1);
+ }
+
+ if (it != end)
+ {
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ }
+ }
+ else if (*it == '}')
+ {
+ std::advance(it, 1);
+ if (it != end && *it == '}')
+ {
+ context.push('}');
+ std::advance(it, 1);
+ parse_context.advance_to(it);
+ }
+ else
+ {
+ context.push("{fmt-err: unescaped}");
+ parse_context.advance_to(it);
+ }
+ }
+ }
+ }
+
+ auto string_writer::push(std::string_view text) -> void
+ {
+ m_result.append(text);
+ }
+
+ auto string_writer::push(char character) -> void
+ {
+ m_result.push_back(character);
+ }
+
+ auto string_writer::release() -> string &&
+ {
+ return std::move(m_result);
+ }
+
+} // namespace kstd::bits::format \ No newline at end of file
diff --git a/libs/kstd/src/libc/string.cpp b/libs/kstd/src/libc/string.cpp
deleted file mode 100644
index b7d4c6b..0000000
--- a/libs/kstd/src/libc/string.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-#include <algorithm>
-#include <bit>
-#include <cstddef>
-#include <iterator>
-#include <span>
-
-namespace kstd::libc
-{
-
- extern "C"
- {
- auto strlen(char const * string) -> std::size_t
- {
- return std::distance(string, std::ranges::find(string, nullptr, '\0'));
- }
-
- auto memcmp(void const * lhs, void const * rhs, std::size_t size) -> std::size_t
- {
- auto left_span = std::span{static_cast<std::byte const *>(lhs), size};
- auto right_span = std::span{static_cast<std::byte const *>(rhs), size};
- auto mismatched = std::ranges::mismatch(left_span, right_span);
-
- if (mismatched.in1 == left_span.end())
- {
- return 0;
- }
-
- return std::bit_cast<char>(*mismatched.in1) - std::bit_cast<char>(*mismatched.in2);
- }
-
- auto memmove(void * dest, void const * src, std::size_t size) -> void *
- {
- auto dest_span = std::span{static_cast<std::byte *>(dest), size};
- auto src_span = std::span{static_cast<std::byte const *>(src), size};
- if (dest < src)
- {
- for (std::size_t i = 0; i < size; ++i)
- {
- dest_span[i] = src_span[i];
- }
- }
- else
- {
- for (std::size_t i = size; i > 0; --i)
- {
- dest_span[i - 1] = src_span[i - 1];
- }
- }
- return dest;
- }
- }
-
-} // namespace kstd::libc \ No newline at end of file
diff --git a/libs/multiboot2/CMakeLists.txt b/libs/multiboot2/CMakeLists.txt
index 350a996..5ab56db 100644
--- a/libs/multiboot2/CMakeLists.txt
+++ b/libs/multiboot2/CMakeLists.txt
@@ -1,27 +1,42 @@
+cmake_minimum_required(VERSION "3.27.0")
+
+project("multiboot2"
+ DESCRIPTION "Multiboot2 bootloader specification library"
+ VERSION "0.0.1"
+ LANGUAGES CXX
+)
+
+include("CTest")
+
+#[============================================================================[
+# Library
+#]============================================================================]
+
add_library("multiboot2" INTERFACE)
-add_library("libs::multiboot2" ALIAS "multiboot2")
+add_library("multiboot2::lib" ALIAS "multiboot2")
+
+file(GLOB_RECURSE MULTIBOOT2_HEADERS
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ CONFIGURE_DEPENDS
+ "include/**.hpp"
+)
target_sources("multiboot2" INTERFACE
FILE_SET HEADERS
BASE_DIRS "include"
FILES
- "include/multiboot2/constants.hpp"
- "include/multiboot2/information.hpp"
-
- "include/multiboot2/impl/data.hpp"
- "include/multiboot2/impl/ids.hpp"
- "include/multiboot2/impl/iterator.hpp"
- "include/multiboot2/impl/tag.hpp"
+ ${MULTIBOOT2_HEADERS}
)
target_include_directories("multiboot2" INTERFACE
- "include"
+ "${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries("multiboot2" INTERFACE
- "libs::elf"
+ "elf::lib"
+ "kstd::lib"
)
set_target_properties("multiboot2" PROPERTIES
VERIFY_INTERFACE_HEADER_SETS YES
-) \ No newline at end of file
+)
diff --git a/libs/multiboot2/include/multiboot2/constants.hpp b/libs/multiboot2/multiboot2/constants.hpp
index 2198210..57fa940 100644
--- a/libs/multiboot2/include/multiboot2/constants.hpp
+++ b/libs/multiboot2/multiboot2/constants.hpp
@@ -1,7 +1,7 @@
#ifndef MULTIBOOT2_CONSTANTS_HPP
#define MULTIBOOT2_CONSTANTS_HPP
-#include "constants/architecture_id.hpp" // IWYU pragma: export
+#include <multiboot2/constants/architecture_id.hpp> // IWYU pragma: export
#include <cstdint>
diff --git a/libs/multiboot2/include/multiboot2/constants/architecture_id.hpp b/libs/multiboot2/multiboot2/constants/architecture_id.hpp
index e13c471..e13c471 100644
--- a/libs/multiboot2/include/multiboot2/constants/architecture_id.hpp
+++ b/libs/multiboot2/multiboot2/constants/architecture_id.hpp
diff --git a/libs/multiboot2/include/multiboot2/constants/information_id.hpp b/libs/multiboot2/multiboot2/constants/information_id.hpp
index be492eb..27c5300 100644
--- a/libs/multiboot2/include/multiboot2/constants/information_id.hpp
+++ b/libs/multiboot2/multiboot2/constants/information_id.hpp
@@ -57,10 +57,10 @@ namespace multiboot2
smbios_tables,
//! A copy of RSDP as defined per ACPI 1.0 specification.
- acpi_old_rsdp,
+ acpi_rsdp,
- //! A copy of RSDP as defined per ACPI 2.0 or later specification.
- acpi_new_rsdp,
+ //! A copy of XSDP as defined per ACPI 2.0 or later specification.
+ acpi_xsdp,
//! The network information specified specified as per DHCP.
networking_information,
@@ -83,4 +83,4 @@ namespace multiboot2
} // namespace multiboot2
-#endif \ No newline at end of file
+#endif
diff --git a/libs/multiboot2/include/multiboot2/constants/memory_type.hpp b/libs/multiboot2/multiboot2/constants/memory_type.hpp
index 6be94bd..6be94bd 100644
--- a/libs/multiboot2/include/multiboot2/constants/memory_type.hpp
+++ b/libs/multiboot2/multiboot2/constants/memory_type.hpp
diff --git a/libs/multiboot2/include/multiboot2/constants/tag_id.hpp b/libs/multiboot2/multiboot2/constants/tag_id.hpp
index 23d39cc..23d39cc 100644
--- a/libs/multiboot2/include/multiboot2/constants/tag_id.hpp
+++ b/libs/multiboot2/multiboot2/constants/tag_id.hpp
diff --git a/libs/multiboot2/include/multiboot2/information.hpp b/libs/multiboot2/multiboot2/information.hpp
index 0f48835..f688fe5 100644
--- a/libs/multiboot2/include/multiboot2/information.hpp
+++ b/libs/multiboot2/multiboot2/information.hpp
@@ -1,18 +1,21 @@
#ifndef MULTIBOOT2_INFORMATION_HPP
#define MULTIBOOT2_INFORMATION_HPP
-#include "information/data.hpp" // IWYU pragma: export
-#include "information/iterator.hpp" // IWYU pragma: export
-#include "information/tag.hpp" // IWYU pragma: export
-
#include <elf/format.hpp>
#include <elf/section_header.hpp>
+#include <kstd/units>
+
+#include <multiboot2/information/data.hpp> // IWYU pragma: export
+#include <multiboot2/information/iterator.hpp> // IWYU pragma: export
+#include <multiboot2/information/tag.hpp> // IWYU pragma: export
+
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <optional>
+#include <ranges>
#include <span>
#include <string_view>
@@ -106,6 +109,47 @@ namespace multiboot2
}
};
+ /**
+ * @copydoc multiboot2::data::module
+ */
+ struct module : vla_tag<data::module, char, std::basic_string_view>
+ {
+ using vla_tag::vla_tag;
+
+ /**
+ * @brief The module command line or name.
+ */
+ [[nodiscard]] auto string() const noexcept -> std::string_view
+ {
+ return {data(), vla_tag::size()};
+ }
+
+ [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{end_address - start_address};
+ }
+ };
+
+ struct acpi_rsdp : vla_tag<data::acpi_rsdp, std::byte, std::span>
+ {
+ using vla_tag::vla_tag;
+
+ [[nodiscard]] auto pointer() const noexcept -> range_type
+ {
+ return {data(), size()};
+ }
+ };
+
+ struct acpi_xsdp : vla_tag<data::acpi_xsdp, std::byte, std::span>
+ {
+ using vla_tag::vla_tag;
+
+ [[nodiscard]] auto pointer() const noexcept -> range_type
+ {
+ return {data(), size()};
+ }
+ };
+
struct information_view
{
using iterator = iterator;
@@ -113,9 +157,9 @@ namespace multiboot2
using pointer = iterator::pointer;
using reference = iterator::reference;
- [[nodiscard]] auto size_bytes() const noexcept -> std::size_t
+ [[nodiscard]] auto size() const noexcept -> kstd::units::bytes
{
- return m_size;
+ return kstd::units::bytes{m_size};
}
// Range access
@@ -173,7 +217,7 @@ namespace multiboot2
{
return get<multiboot2::elf_symbols<Format>>().and_then(
[](auto x) -> std::optional<multiboot2::elf_symbols<Format>> {
- if (x.entry_size == elf::section_header_size<Format>)
+ if (x.entry_size_in_B == elf::section_header_size<Format>)
{
return std::optional{x};
}
@@ -210,6 +254,38 @@ namespace multiboot2
return maybe_memory_map().value();
}
+ [[nodiscard]] auto modules() const noexcept
+ {
+ auto filter_modules = [](auto const & tag) {
+ return tag.information_id() == module::id;
+ };
+ auto transform_module = [](auto const & tag) {
+ return module{&tag};
+ };
+ return std::ranges::subrange(begin(), end()) | std::views::filter(filter_modules) |
+ std::views::transform(transform_module);
+ }
+
+ [[nodiscard]] auto maybe_acpi_rsdp() const noexcept -> std::optional<acpi_rsdp>
+ {
+ return get<multiboot2::acpi_rsdp>();
+ }
+
+ [[nodiscard]] auto acpi_rsdp() const noexcept -> acpi_rsdp
+ {
+ return maybe_acpi_rsdp().value();
+ }
+
+ [[nodiscard]] auto maybe_acpi_xsdp() const noexcept -> std::optional<acpi_xsdp>
+ {
+ return get<multiboot2::acpi_xsdp>();
+ }
+
+ [[nodiscard]] auto acpi_xsdp() const noexcept -> acpi_xsdp
+ {
+ return maybe_acpi_xsdp().value();
+ }
+
private:
template<typename Tag>
[[nodiscard]] constexpr auto get() const noexcept -> std::optional<Tag>
@@ -229,4 +305,4 @@ namespace multiboot2
} // namespace multiboot2
-#endif \ No newline at end of file
+#endif
diff --git a/libs/multiboot2/include/multiboot2/information/data.hpp b/libs/multiboot2/multiboot2/information/data.hpp
index ccd8fbb..f39a6cb 100644
--- a/libs/multiboot2/include/multiboot2/information/data.hpp
+++ b/libs/multiboot2/multiboot2/information/data.hpp
@@ -3,8 +3,10 @@
// IWYU pragma: private, include <multiboot2/information.hpp>
-#include "multiboot2/constants/information_id.hpp"
-#include "multiboot2/constants/memory_type.hpp"
+#include <kstd/units>
+
+#include <multiboot2/constants/information_id.hpp>
+#include <multiboot2/constants/memory_type.hpp>
#include <cstdint>
@@ -27,6 +29,16 @@ namespace multiboot2
//! loader.
struct basic_memory : tag_data<information_id::basic_memory_information>
{
+ [[nodiscard]] constexpr auto lower() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{lower_KiB * 1024};
+ }
+
+ [[nodiscard]] constexpr auto upper() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{upper_KiB * 1024};
+ }
+
//! The amount of lower memory available to the system.
//!
//! Any memory below the 1 MiB address boundary is considered to be lower memory. The maximum possible value for
@@ -72,11 +84,16 @@ namespace multiboot2
//! time. The array begins after the last member of this structure.
struct elf_symbols : tag_data<information_id::elf_sections>
{
+ [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{entry_size_in_B};
+ }
+
//! The number of section header table entries.
std::uint32_t count;
//! The size of each section header table entry.
- std::uint32_t entry_size;
+ std::uint32_t entry_size_in_B;
//! The section number of the string table containing the section names.
std::uint32_t string_table_index;
@@ -102,6 +119,11 @@ namespace multiboot2
return type == memory_type::available;
}
+ [[nodiscard]] constexpr auto size() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{size_in_B};
+ }
+
//! The physical start address of this region
std::uint64_t base;
@@ -117,18 +139,46 @@ namespace multiboot2
std::uint32_t : 0;
};
+ [[nodiscard]] constexpr auto entry_size() const noexcept -> kstd::units::bytes
+ {
+ return kstd::units::bytes{entry_size_in_B};
+ }
+
//! The size of each entry present in the map.
//!
//! This field is present to allow for future extension of the entry format. Each entry's size is guaranteed to
//! always be an integer multiple of 8.
- std::uint32_t entry_size;
+ std::uint32_t entry_size_in_B;
//! The version of each entry present in the map
std::uint32_t entry_version;
};
+ //! A module loaded by the bootloader.
+ //!
+ //! @note the command line associated with this module is not part of this structure, since it is of variable size
+ //! and the contained information starts at the end of this structure.
+ struct module : tag_data<information_id::module>
+ {
+ //! The physical start address of this module.
+ std::uint32_t start_address;
+
+ //! The physical end address of this module.
+ std::uint32_t end_address;
+ };
+
+ //! A copy of the ACPI RSDP
+ struct acpi_rsdp : tag_data<information_id::acpi_rsdp>
+ {
+ };
+
+ //! A copy of the ACPI XSDP
+ struct acpi_xsdp : tag_data<information_id::acpi_xsdp>
+ {
+ };
+
} // namespace data
} // namespace multiboot2
-#endif \ No newline at end of file
+#endif
diff --git a/libs/multiboot2/include/multiboot2/information/iterator.hpp b/libs/multiboot2/multiboot2/information/iterator.hpp
index 62c267d..bded43e 100644
--- a/libs/multiboot2/include/multiboot2/information/iterator.hpp
+++ b/libs/multiboot2/multiboot2/information/iterator.hpp
@@ -3,8 +3,8 @@
// IWYU pragma: private, include <multiboot2/information.hpp>
-#include "multiboot2/constants/information_id.hpp"
-#include "tag.hpp"
+#include <multiboot2/constants/information_id.hpp>
+#include <multiboot2/information/tag.hpp>
#include <cstddef>
#include <iterator>
diff --git a/libs/multiboot2/include/multiboot2/information/tag.hpp b/libs/multiboot2/multiboot2/information/tag.hpp
index cd1fc0e..0c29299 100644
--- a/libs/multiboot2/include/multiboot2/information/tag.hpp
+++ b/libs/multiboot2/multiboot2/information/tag.hpp
@@ -3,7 +3,7 @@
// IWYU pragma: private, include <multiboot2/information.hpp>
-#include "multiboot2/constants/information_id.hpp"
+#include <multiboot2/constants/information_id.hpp>
#include <bit>
#include <cstddef>
@@ -172,9 +172,9 @@ namespace multiboot2
return m_vla.data();
}
- [[nodiscard]] auto at() const -> const_reference
+ [[nodiscard]] auto at(std::size_t index) const -> const_reference
{
- return m_vla.at();
+ return m_vla.at(index);
}
[[nodiscard]] auto operator[](std::size_t index) const noexcept -> const_reference
diff --git a/libs/multiboot2/test_data/mbi.bin b/libs/multiboot2/test_data/mbi.bin
new file mode 100644
index 0000000..4985526
--- /dev/null
+++ b/libs/multiboot2/test_data/mbi.bin
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3b67ca95183fde8e7dc8dac2d20af9331122128127926ebe6f8bd80ca9fed7c3
+size 1176
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..94630e9
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,1296 @@
+# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand.
+
+[[package]]
+name = "accessible-pygments"
+version = "0.0.5"
+description = "A collection of accessible pygments styles"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7"},
+ {file = "accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872"},
+]
+
+[package.dependencies]
+pygments = ">=1.5"
+
+[package.extras]
+dev = ["pillow", "pkginfo (>=1.10)", "playwright", "pre-commit", "setuptools", "twine (>=5.0)"]
+tests = ["hypothesis", "pytest"]
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+description = "A light, configurable Sphinx theme"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"},
+ {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"},
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+description = "Reusable constraint types to use with typing.Annotated"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[[package]]
+name = "babel"
+version = "2.18.0"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"},
+ {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"},
+]
+
+[package.extras]
+dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.3"
+description = "Screen-scraping library"
+optional = false
+python-versions = ">=3.7.0"
+groups = ["main"]
+files = [
+ {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"},
+ {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"},
+]
+
+[package.dependencies]
+soupsieve = ">=1.6.1"
+typing-extensions = ">=4.0.0"
+
+[package.extras]
+cchardet = ["cchardet"]
+chardet = ["chardet"]
+charset-normalizer = ["charset-normalizer"]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+name = "black"
+version = "26.5.0"
+description = "The uncompromising code formatter."
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "black-26.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:402454bfdd7a940be00455e87309438a24b328b7ba7d80b7207e8a87b32ffc29"},
+ {file = "black-26.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4863b2a2c382661a018bf2213f2b957fa34511df131259ffaa8d54859620ac31"},
+ {file = "black-26.5.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:490b623006a75c0ea59c1ecf91cc76ecb9d66df1482c3a53f4f7de95a7c85e10"},
+ {file = "black-26.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:6f53deb3d1108a523212da5c79e5c0cd76abcc548948f2d8415e62929c81a569"},
+ {file = "black-26.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:a62f9d069ac27de20c6fa3dbf60d7c951141c4025bb9755274802d05b1aa418b"},
+ {file = "black-26.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:862945b2a08193cdff9f632f51bdadbb11e6852da1d31c306a3508449dc81b84"},
+ {file = "black-26.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:03102aa97c279e5f62e1e1ab828cfe8aa72c3af4cf86f9448e5537b2519cbfea"},
+ {file = "black-26.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:990ee0e1d96dd8ca623f19dd3f339c138bdc02f74e4fea01cc64aee38944ea2b"},
+ {file = "black-26.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:209fabb250681900502b3b6a03e31d8cac606c9ef9629fd0fbd5d33235647c00"},
+ {file = "black-26.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:dbb6fc70f8bd9821981fd47efb68a5be0eee9055f400eb3bf2dbebf49f9ec4fe"},
+ {file = "black-26.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b92983a6674c133ca61d6b4fea17f76cbbaac582ea583002792ee1094dbece49"},
+ {file = "black-26.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1f80998e73fcfc67fc1d222060cf34ab213f1ae7e131b5c8199d93405890c13a"},
+ {file = "black-26.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:081df4dc908702e2becd66d714f125a954cbf1c6dbe2ad83a6be313368c7c2db"},
+ {file = "black-26.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf015b38829ca32a699312fdcfb8c15bd0b156192f5400bd0b559c6bfef25236"},
+ {file = "black-26.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:828db2292848cf427592fcd162f02d770849d20ea4bdda2806e9494b3a15d481"},
+ {file = "black-26.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2b64ce9841e8b8254c3d702ebccdaf5c520607df8aa4176f5732b7f9af1e6f6"},
+ {file = "black-26.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0a789a41b386f0f83711785f182f2977138ba9cc1f41ad0f6fbc8faac4d2639e"},
+ {file = "black-26.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f69837f7e26d67b1d1e9d0ed49231a14a0469f266e44cd142873e0552f325395"},
+ {file = "black-26.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:c5b08371561dae9c90391fe7f2138fe7fa495437d3bb134eb865839036e65784"},
+ {file = "black-26.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:3968ce82ca0bd4914769518490d91a9b0ef2ff2fc68e2122d22b5915a0342eaa"},
+ {file = "black-26.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea8a0c4505486c132c6640e4e108d25f41360a06d844db5a76477c3dbae1b616"},
+ {file = "black-26.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2178a70e7c45fb85999b687d8326abceef1e7227463d5d7e07ef125c9fbb9c5c"},
+ {file = "black-26.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3ad14d7c24c40eafecf4fb212d9c01e7c7b2ab05c8646b351c93728f499c555"},
+ {file = "black-26.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:8ea767bae9c4f331ea9ad2e08895c951e600dffd550a42624d5210a908720b39"},
+ {file = "black-26.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:d658f4ee6167797b08be07ee4bbf6045753ddabfc676c3cb0eec23752ca83eff"},
+ {file = "black-26.5.0-py3-none-any.whl", hash = "sha256:241f25bf59f5ca17f5121031e310e089b84cd22bb4eca47360099ea825544f17"},
+ {file = "black-26.5.0.tar.gz", hash = "sha256:5cbe4cc4037ffca34cdb0a6a9a046f104b262d0bd63c30fd4a88c7adc2049b1d"},
+]
+
+[package.dependencies]
+click = ">=8.0.0"
+mypy-extensions = ">=0.4.3"
+packaging = ">=22.0"
+pathspec = ">=1.0.0"
+platformdirs = ">=2"
+pytokens = ">=0.4.0,<0.5.0"
+
+[package.extras]
+colorama = ["colorama (>=0.4.3)"]
+d = ["aiohttp (>=3.10)"]
+jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
+uvloop = ["uvloop (>=0.15.2) ; sys_platform != \"win32\"", "winloop (>=0.5.0) ; sys_platform == \"win32\""]
+
+[[package]]
+name = "breathe"
+version = "4.36.0"
+description = "Sphinx Doxygen renderer"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "breathe-4.36.0-py3-none-any.whl", hash = "sha256:af85436f1f09e842bd1fd95617281211c635f8768d245ff830c59b979888d1d5"},
+ {file = "breathe-4.36.0.tar.gz", hash = "sha256:14860b73118ac140b7a3f55446890c777d1b67149cb024279fe3710dad7f535c"},
+]
+
+[package.dependencies]
+Sphinx = ">=7.2"
+
+[package.extras]
+docs = ["furo", "sphinx-copybutton", "sphinxcontrib-spelling"]
+lint = ["mypy (>=1)", "pytest (>=8.0)", "ruff (==0.9.2)", "sphinx-csharp", "sphinxcontrib-phpdomain", "types-Pygments", "types-docutils"]
+test = ["pytest (>=8.0)"]
+
+[[package]]
+name = "certifi"
+version = "2026.4.22"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"},
+ {file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.7"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943"},
+ {file = "charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00"},
+ {file = "charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6"},
+ {file = "charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110"},
+ {file = "charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c"},
+ {file = "charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e5f4d355f0a2b1a31bc3edec6795b46324349c9cb25eed068049e4f472fb4259"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16d971e29578a5e97d7117866d15889a4a07befe0e87e703ed63cd90cb348c01"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dca4bbc466a95ba9c0234ef56d7dd9509f63da22274589ebd4ed7f1f4d4c54e3"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e80c8378d8f3d83cd3164da1ad2df9e37a666cdde7b1cb2298ed0b558064be30"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36836d6ff945a00b88ba1e4572d721e60b5b8c98c155d465f56ad19d68f23734"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:bd9b23791fe793e4968dba0c447e12f78e425c59fc0e3b97f6450f4781f3ee60"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aef65cd602a6d0e0ff6f9930fcb1c8fec60dd2cfcb6facaf4bdb0e5873042db0"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:82b271f5137d07749f7bf32f70b17ab6eaabedd297e75dce75081a24f76eb545"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:1efde3cae86c8c273f1eb3b287be7d8499420cf2fe7585c41d370d3e790054a5"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:c593052c465475e64bbfe5dbd81680f64a67fdc752c56d7a0ae205dc8aeefe0f"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:af21eb4409a119e365397b2adbaca4c9ccab56543a65d5dbd9f920d6ac29f686"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:84c018e49c3bf790f9c2771c45e9313a08c2c2a6342b162cd650258b57817706"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dd915403e231e6b1809fe9b6d9fc55cf8fb5e02765ac625d9cd623342a7905d7"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-win32.whl", hash = "sha256:320ade88cfb846b8cd6b4ddf5ee9e80ee0c1f52401f2456b84ae1ae6a1a5f207"},
+ {file = "charset_normalizer-3.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:1dc8b0ea451d6e69735094606991f32867807881400f808a106ee1d963c46a83"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:177a0ba5f0211d488e295aaf82707237e331c24788d8d76c96c5a41594723217"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e0d51f618228538a3e8f46bd246f87a6cd030565e015803691603f55e12afb5"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14265bfe1f09498b9d8ec91e9ec9fa52775edf90fcbde092b25f4a33d444fea9"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87fad7d9ba98c86bcb41b2dc8dbb326619be2562af1f8ff50776a39e55721c5a"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f22dec1690b584cea26fade98b2435c132c1b5f68e39f5a0b7627cd7ae31f1dc"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:d61f00a0869d77422d9b2aba989e2d24afa6ffd552af442e0e58de4f35ea6d00"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6370e8686f662e6a3941ee48ed4742317cafbe5707e36406e9df792cdb535776"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a6c5863edfbe888d9eff9c8b8087354e27618d9da76425c119293f11712a6319"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ed065083d0898c9d5b4bbec7b026fd755ff7454e6e8b73a67f8c744b13986e24"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2cd4a60d0e2fb04537162c62bbbb4182f53541fe0ede35cdf270a1c1e723cc42"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:813c0e0132266c08eb87469a642cb30aaff57c5f426255419572aaeceeaa7bf4"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:07d9e39b01743c3717745f4c530a6349eadbfa043c7577eef86c502c15df2c67"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c0f081d69a6e58272819b70288d3221a6ee64b98df852631c80f293514d3b274"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-win32.whl", hash = "sha256:8751d2787c9131302398b11e6c8068053dcb55d5a8964e114b6e196cf16cb366"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:12a6fff75f6bc66711b73a2f0addfc4c8c15a20e805146a02d147a318962c444"},
+ {file = "charset_normalizer-3.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:bb8cc7534f51d9a017b93e3e85b260924f909601c3df002bcdb58ddb4dc41a5c"},
+ {file = "charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d"},
+ {file = "charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5"},
+]
+
+[[package]]
+name = "click"
+version = "8.4.0"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "click-8.4.0-py3-none-any.whl", hash = "sha256:40c50b7c6c6adac2823d411041ec84f3f103f1b280d5e9ce0d7f998995832f81"},
+ {file = "click-8.4.0.tar.gz", hash = "sha256:638f1338fe1235c8f4e008e4a8a254fb5c5fbdcbb40ece3c9142ebb78e792973"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main"]
+markers = "sys_platform == \"win32\" or platform_system == \"Windows\""
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "docutils"
+version = "0.22.4"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de"},
+ {file = "docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968"},
+]
+
+[[package]]
+name = "idna"
+version = "3.15"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"},
+ {file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"},
+]
+
+[package.extras]
+all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+
+[[package]]
+name = "imagesize"
+version = "1.5.0"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+groups = ["main"]
+files = [
+ {file = "imagesize-1.5.0-py2.py3-none-any.whl", hash = "sha256:32677681b3f434c2cb496f00e89c5a291247b35b1f527589909e008057da5899"},
+ {file = "imagesize-1.5.0.tar.gz", hash = "sha256:8bfc5363a7f2133a89f0098451e0bcb1cd71aba4dc02bbcecb39d99d40e1b94f"},
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
+ {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "lance-namespace"
+version = "0.6.1"
+description = "Lance Namespace interface and plugin registry"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "lance_namespace-0.6.1-py3-none-any.whl", hash = "sha256:9699c9e3f12236e5e08ea979cc4e036a8e3c67ed2f37ae6f25c5353ab908e1be"},
+ {file = "lance_namespace-0.6.1.tar.gz", hash = "sha256:f0deea442bd3f1056a8e2fed056ae2778e3356517ec2e680db049058b824d131"},
+]
+
+[package.dependencies]
+lance-namespace-urllib3-client = "0.6.1"
+
+[[package]]
+name = "lance-namespace-urllib3-client"
+version = "0.6.1"
+description = "Lance Namespace Specification"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "lance_namespace_urllib3_client-0.6.1-py3-none-any.whl", hash = "sha256:b9c103e1377ad46d2bd70eec894bfec0b1e2133dae0964d7e4de543c6e16293b"},
+ {file = "lance_namespace_urllib3_client-0.6.1.tar.gz", hash = "sha256:31fbd058ce1ea0bf49045cdeaa756360ece0bc61e9e10276f41af6d217debe87"},
+]
+
+[package.dependencies]
+pydantic = ">=2"
+python-dateutil = ">=2.8.2"
+typing-extensions = ">=4.7.1"
+urllib3 = ">=1.25.3,<3.0.0"
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"},
+ {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"},
+ {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"},
+ {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"},
+ {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"},
+ {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"},
+ {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"},
+ {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"},
+ {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"},
+ {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"},
+ {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"},
+ {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"},
+ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"},
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
+]
+
+[[package]]
+name = "numpy"
+version = "2.4.5"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.11"
+groups = ["main"]
+files = [
+ {file = "numpy-2.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288"},
+ {file = "numpy-2.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7"},
+ {file = "numpy-2.4.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6"},
+ {file = "numpy-2.4.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8"},
+ {file = "numpy-2.4.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772"},
+ {file = "numpy-2.4.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd"},
+ {file = "numpy-2.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059"},
+ {file = "numpy-2.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55"},
+ {file = "numpy-2.4.5-cp311-cp311-win32.whl", hash = "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf"},
+ {file = "numpy-2.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864"},
+ {file = "numpy-2.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b"},
+ {file = "numpy-2.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef"},
+ {file = "numpy-2.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862"},
+ {file = "numpy-2.4.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507"},
+ {file = "numpy-2.4.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa"},
+ {file = "numpy-2.4.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72"},
+ {file = "numpy-2.4.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912"},
+ {file = "numpy-2.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef"},
+ {file = "numpy-2.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7"},
+ {file = "numpy-2.4.5-cp312-cp312-win32.whl", hash = "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5"},
+ {file = "numpy-2.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6"},
+ {file = "numpy-2.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453"},
+ {file = "numpy-2.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277"},
+ {file = "numpy-2.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0"},
+ {file = "numpy-2.4.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419"},
+ {file = "numpy-2.4.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685"},
+ {file = "numpy-2.4.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c"},
+ {file = "numpy-2.4.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83"},
+ {file = "numpy-2.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566"},
+ {file = "numpy-2.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097"},
+ {file = "numpy-2.4.5-cp313-cp313-win32.whl", hash = "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33"},
+ {file = "numpy-2.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42"},
+ {file = "numpy-2.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97"},
+ {file = "numpy-2.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb"},
+ {file = "numpy-2.4.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4"},
+ {file = "numpy-2.4.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed"},
+ {file = "numpy-2.4.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6"},
+ {file = "numpy-2.4.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976"},
+ {file = "numpy-2.4.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222"},
+ {file = "numpy-2.4.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656"},
+ {file = "numpy-2.4.5-cp313-cp313t-win32.whl", hash = "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb"},
+ {file = "numpy-2.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc"},
+ {file = "numpy-2.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db"},
+ {file = "numpy-2.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61"},
+ {file = "numpy-2.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77"},
+ {file = "numpy-2.4.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361"},
+ {file = "numpy-2.4.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29"},
+ {file = "numpy-2.4.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce"},
+ {file = "numpy-2.4.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a"},
+ {file = "numpy-2.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23"},
+ {file = "numpy-2.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25"},
+ {file = "numpy-2.4.5-cp314-cp314-win32.whl", hash = "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080"},
+ {file = "numpy-2.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f"},
+ {file = "numpy-2.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957"},
+ {file = "numpy-2.4.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3"},
+ {file = "numpy-2.4.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be"},
+ {file = "numpy-2.4.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7"},
+ {file = "numpy-2.4.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40"},
+ {file = "numpy-2.4.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3"},
+ {file = "numpy-2.4.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c"},
+ {file = "numpy-2.4.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963"},
+ {file = "numpy-2.4.5-cp314-cp314t-win32.whl", hash = "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5"},
+ {file = "numpy-2.4.5-cp314-cp314t-win_amd64.whl", hash = "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a"},
+ {file = "numpy-2.4.5-cp314-cp314t-win_arm64.whl", hash = "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198"},
+ {file = "numpy-2.4.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20"},
+ {file = "numpy-2.4.5.tar.gz", hash = "sha256:ca670567a5683b7c1670ec03e0ddd5862e10934e92a70751d68d7b7b74ca7f9f"},
+]
+
+[[package]]
+name = "packaging"
+version = "26.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"},
+ {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"},
+]
+
+[[package]]
+name = "pathspec"
+version = "1.1.1"
+description = "Utility library for gitignore style pattern matching of file paths."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"},
+ {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"},
+]
+
+[package.extras]
+hyperscan = ["hyperscan (>=0.7)"]
+optional = ["typing-extensions (>=4)"]
+re2 = ["google-re2 (>=1.1)"]
+
+[[package]]
+name = "platformdirs"
+version = "4.9.6"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917"},
+ {file = "platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a"},
+]
+
+[[package]]
+name = "pyarrow"
+version = "24.0.0"
+description = "Python library for Apache Arrow"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "pyarrow-24.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c2b98645d576a0b9616892ead22b64a83a5f043c5e2ca15ebcefcb5b70c80cb"},
+ {file = "pyarrow-24.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:644a246325b8c69c595ad1dd4b463eba4b0cdb731370e4a86137d433208d6147"},
+ {file = "pyarrow-24.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3a577bd840ca83f646f0a625dbc571dba7044c43c2d1503afc378b570954345c"},
+ {file = "pyarrow-24.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e3268e43984d0b1a185c89b4cfff282a7ead12fc93f56cfd7088bdbcbe727041"},
+ {file = "pyarrow-24.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2392d954fcb920f42d230284b677605e4e2fbb11f2821e823e642abd67fbb491"},
+ {file = "pyarrow-24.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec9373df11544592b0ba7ec2af0e35059e5f0e7647c6183a854dedd193298f1"},
+ {file = "pyarrow-24.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c42ab9439498270139cc63e18847a02afe5c8b3ed9c931266533cfe378bd3591"},
+ {file = "pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74"},
+ {file = "pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3"},
+ {file = "pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868"},
+ {file = "pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e"},
+ {file = "pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57"},
+ {file = "pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c"},
+ {file = "pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981"},
+ {file = "pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810"},
+ {file = "pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a"},
+ {file = "pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66"},
+ {file = "pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb"},
+ {file = "pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e"},
+ {file = "pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6"},
+ {file = "pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826"},
+ {file = "pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba"},
+ {file = "pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68"},
+ {file = "pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2"},
+ {file = "pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0"},
+ {file = "pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495"},
+ {file = "pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f"},
+ {file = "pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca"},
+ {file = "pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d"},
+ {file = "pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838"},
+ {file = "pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b"},
+ {file = "pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795"},
+ {file = "pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26"},
+ {file = "pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde"},
+ {file = "pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76"},
+ {file = "pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136"},
+ {file = "pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19"},
+ {file = "pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.13.4"
+description = "Data validation using Python type hints"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba"},
+ {file = "pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6"},
+]
+
+[package.dependencies]
+annotated-types = ">=0.6.0"
+pydantic-core = "2.46.4"
+typing-extensions = ">=4.14.1"
+typing-inspection = ">=0.4.2"
+
+[package.extras]
+email = ["email-validator (>=2.0.0)"]
+timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""]
+
+[[package]]
+name = "pydantic-core"
+version = "2.46.4"
+description = "Core functionality for Pydantic validation and serialization"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d"},
+ {file = "pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33"},
+ {file = "pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89"},
+ {file = "pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292"},
+ {file = "pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac"},
+ {file = "pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5"},
+ {file = "pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1"},
+ {file = "pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b"},
+ {file = "pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526"},
+ {file = "pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc"},
+ {file = "pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983"},
+ {file = "pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.14.1"
+
+[[package]]
+name = "pydata-sphinx-theme"
+version = "0.16.1"
+description = "Bootstrap-based Sphinx theme from the PyData community"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde"},
+ {file = "pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7"},
+]
+
+[package.dependencies]
+accessible-pygments = "*"
+Babel = "*"
+beautifulsoup4 = "*"
+docutils = "!=0.17.0"
+pygments = ">=2.7"
+sphinx = ">=6.1"
+typing-extensions = "*"
+
+[package.extras]
+a11y = ["pytest-playwright"]
+dev = ["pandoc", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml", "sphinx-theme-builder[cli]", "tox"]
+doc = ["ablog (>=0.11.8)", "colorama", "graphviz", "ipykernel", "ipyleaflet", "ipywidgets", "jupyter_sphinx", "jupyterlite-sphinx", "linkify-it-py", "matplotlib", "myst-parser", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-autoapi (>=3.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube (>=1.4.1)", "sphinxext-rediraffe", "xarray"]
+i18n = ["Babel", "jinja2"]
+test = ["pytest", "pytest-cov", "pytest-regressions", "sphinx[test]"]
+
+[[package]]
+name = "pygments"
+version = "2.20.0"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"},
+ {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pylance"
+version = "4.0.1"
+description = "python wrapper for Lance columnar format"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "pylance-4.0.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:9887930ff174851ef2b0471ad89675cb27b40f20ba048ceb4c09664eea778076"},
+ {file = "pylance-4.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:400aa25ee82efd09ed8184f669f1150073c701330c614c63c4ed377b076a79e7"},
+ {file = "pylance-4.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53b64cee1970d273642b70b0a5d337e2421402730dd1d15cbf488924fbcda0c7"},
+ {file = "pylance-4.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c8459ffedae783941c73ae0e8cb2d453316b91b2b100c74c3825c44b6f7bd75c"},
+ {file = "pylance-4.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b467f096079a805f568738548ed72f0951bc9c07a6786c76e1c3052b73e5ae69"},
+ {file = "pylance-4.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:15c1b9d215d9a4cc15538faca192497f97c807128c759a37d28ea0a6780cce1c"},
+]
+
+[package.dependencies]
+lance-namespace = ">=0.5.2,<0.7"
+numpy = ">=1.22"
+pyarrow = ">=14"
+
+[package.extras]
+benchmarks = ["pytest-benchmark"]
+dev = ["pyright", "ruff (==0.11.2)"]
+geo = ["geoarrow-rust-core", "geoarrow-rust-io"]
+tests = ["boto3", "datafusion (>=52,<53) ; python_full_version >= \"3.10.0\"", "datasets", "duckdb", "ml-dtypes", "pandas", "pillow", "polars[pandas,pyarrow]", "psutil", "pytest", "tensorflow ; sys_platform == \"linux\"", "tqdm"]
+torch = ["torch (>=2.0)"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytokens"
+version = "0.4.1"
+description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons."
+optional = false
+python-versions = ">=3.8"
+groups = ["main"]
+files = [
+ {file = "pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5"},
+ {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe"},
+ {file = "pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c"},
+ {file = "pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7"},
+ {file = "pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2"},
+ {file = "pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440"},
+ {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc"},
+ {file = "pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d"},
+ {file = "pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16"},
+ {file = "pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6"},
+ {file = "pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083"},
+ {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1"},
+ {file = "pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1"},
+ {file = "pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9"},
+ {file = "pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68"},
+ {file = "pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b"},
+ {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f"},
+ {file = "pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1"},
+ {file = "pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4"},
+ {file = "pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78"},
+ {file = "pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321"},
+ {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa"},
+ {file = "pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d"},
+ {file = "pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324"},
+ {file = "pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9"},
+ {file = "pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb"},
+ {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3"},
+ {file = "pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975"},
+ {file = "pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a"},
+ {file = "pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918"},
+ {file = "pytokens-0.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:da5baeaf7116dced9c6bb76dc31ba04a2dc3695f3d9f74741d7910122b456edc"},
+ {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11edda0942da80ff58c4408407616a310adecae1ddd22eef8c692fe266fa5009"},
+ {file = "pytokens-0.4.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0fc71786e629cef478cbf29d7ea1923299181d0699dbe7c3c0f4a583811d9fc1"},
+ {file = "pytokens-0.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dcafc12c30dbaf1e2af0490978352e0c4041a7cde31f4f81435c2a5e8b9cabb6"},
+ {file = "pytokens-0.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:42f144f3aafa5d92bad964d471a581651e28b24434d184871bd02e3a0d956037"},
+ {file = "pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3"},
+ {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1"},
+ {file = "pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db"},
+ {file = "pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1"},
+ {file = "pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a"},
+ {file = "pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de"},
+ {file = "pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a"},
+]
+
+[package.extras]
+dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"]
+
+[[package]]
+name = "requests"
+version = "2.34.2"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0"},
+ {file = "requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed"},
+]
+
+[package.dependencies]
+certifi = ">=2023.5.7"
+charset_normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.26,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<8)"]
+
+[[package]]
+name = "roman-numerals"
+version = "4.1.0"
+description = "Manipulate well-formed Roman numerals"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7"},
+ {file = "roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2"},
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
+files = [
+ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*"
+groups = ["main"]
+files = [
+ {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"},
+ {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"},
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.8.3"
+description = "A modern CSS selector implementation for Beautiful Soup."
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95"},
+ {file = "soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349"},
+]
+
+[[package]]
+name = "sphinx"
+version = "9.1.0"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.12"
+groups = ["main"]
+files = [
+ {file = "sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978"},
+ {file = "sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7.14"
+babel = ">=2.13"
+colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""}
+docutils = ">=0.21,<0.23"
+imagesize = ">=1.3"
+Jinja2 = ">=3.1"
+packaging = ">=23.0"
+Pygments = ">=2.17"
+requests = ">=2.30.0"
+roman-numerals = ">=1.0.0"
+snowballstemmer = ">=2.2"
+sphinxcontrib-applehelp = ">=1.0.7"
+sphinxcontrib-devhelp = ">=1.0.6"
+sphinxcontrib-htmlhelp = ">=2.0.6"
+sphinxcontrib-jsmath = ">=1.0.1"
+sphinxcontrib-qthelp = ">=1.0.6"
+sphinxcontrib-serializinghtml = ">=1.1.9"
+
+[[package]]
+name = "sphinx-book-theme"
+version = "1.2.0"
+description = "A clean book theme for scientific explanations and documentation with Sphinx"
+optional = false
+python-versions = ">=3.11"
+groups = ["main"]
+files = [
+ {file = "sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa"},
+ {file = "sphinx_book_theme-1.2.0.tar.gz", hash = "sha256:4a7ebfc7da4395309ac942ddfc38fbec5c5254c3be22195e99ad12586fbda9e3"},
+]
+
+[package.dependencies]
+pydata-sphinx-theme = "0.16.1"
+sphinx = ">=7.0"
+
+[package.extras]
+code-style = ["pre-commit"]
+doc = ["ablog (>=0.11.13)", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"]
+test = ["beautifulsoup4", "coverage", "defusedxml", "myst-nb", "pytest", "pytest-cov", "pytest-regressions", "sphinx_thebe"]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"},
+ {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"},
+ {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"},
+ {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+groups = ["main"]
+files = [
+ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"},
+ {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["defusedxml (>=0.7.1)", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"},
+ {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"},
+]
+
+[package.extras]
+lint = ["mypy", "ruff (==0.5.5)", "types-docutils"]
+standalone = ["Sphinx (>=5)"]
+test = ["pytest"]
+
+[[package]]
+name = "types-gdb"
+version = "16.3.0.20260518"
+description = "Typing stubs for gdb"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "types_gdb-16.3.0.20260518-py3-none-any.whl", hash = "sha256:70a716562a123f9faaf40f9c4b42db39ab7674abadbfca58afe763fd4d925ea6"},
+ {file = "types_gdb-16.3.0.20260518.tar.gz", hash = "sha256:7ca5d70b95636cbd2ea32fcba5fe823506ccbe61e4add410b9224d1a3ee1e192"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+description = "Backported and Experimental Type Hints for Python 3.9+"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.2"
+description = "Runtime typing introspection tools"
+optional = false
+python-versions = ">=3.9"
+groups = ["main"]
+files = [
+ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"},
+ {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.12.0"
+
+[[package]]
+name = "urllib3"
+version = "2.7.0"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897"},
+ {file = "urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
+
+[metadata]
+lock-version = "2.1"
+python-versions = ">=3.14"
+content-hash = "23bdc37737ba63ebb598ec2a34feb857a6317504a3af5b5def8f654d2118090e"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..087a76f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,22 @@
+[project]
+name = "teachos"
+version = "0.1.0"
+description = ""
+authors = [{ name = "Your Name", email = "you@example.com" }]
+license = { text = "BSD-3-clause" }
+requires-python = ">=3.14"
+dependencies = [
+ "sphinx (>=9.1.0,<10.0.0)",
+ "sphinx-book-theme (>=1.2.0,<2.0.0)",
+ "breathe (>=4.36.0,<5.0.0)",
+ "types-gdb (>=16.3.0.20260408,<17.0.0.0)",
+ "black (>=26.3.1,<27.0.0)",
+ "pylance (>=4.0.1,<5.0.0)",
+]
+
+[tool.poetry]
+package-mode = false
+
+[build-system]
+requires = ["poetry-core>=2.0.0,<3.0.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/scripts/ci/parse_clang_tidy.py b/scripts/ci/parse_clang_tidy.py
new file mode 100644
index 0000000..ec51d77
--- /dev/null
+++ b/scripts/ci/parse_clang_tidy.py
@@ -0,0 +1,54 @@
+import hashlib
+import json
+import os
+import re
+import sys
+
+
+def parse_clang_tidy(log_file_path: str) -> list[dict]:
+ issues = []
+ pattern = re.compile(
+ r"^(.*?):(\d+):(\d+):\s+(warning|error|note):\s+(.*?)\s+\[(.*?)\]$"
+ )
+ repo_root = os.environ.get("CI_PROJECT_DIR", os.getcwd())
+
+ with open(log_file_path, "r") as f:
+ for line in f:
+ match = pattern.match(line)
+ if not match:
+ continue
+
+ path, line, column, severity, message, name = match.groups()
+ severity = "minor" if severity == "warning" else "major"
+
+ try:
+ path = os.path.relpath(path, repo_root)
+ except ValueError:
+ pass
+
+ fingerprint_data = f"{path}:{line}:{column}:{message}:{name}"
+ fingerprint = hashlib.sha256(fingerprint_data.encode()).hexdigest()
+
+ issues.append(
+ {
+ "description": f"{message} ({name})",
+ "fingerprint": fingerprint,
+ "severity": severity,
+ "location": {
+ "path": path,
+ "lines": {
+ "begin": int(line),
+ },
+ },
+ }
+ )
+
+ return issues
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ sys.exit("Usage: python3 parse_clang_tidy.py <CLANG_TIDY_LOG_PATH>")
+
+ issues = parse_clang_tidy(sys.argv[1])
+ print(json.dumps(issues, indent=2))
diff --git a/scripts/gdb/teachos.py b/scripts/gdb/teachos.py
new file mode 100644
index 0000000..355f6b9
--- /dev/null
+++ b/scripts/gdb/teachos.py
@@ -0,0 +1,47 @@
+import sys
+import os
+import gdb
+import importlib.util
+
+script_path = os.path.abspath(__file__)
+script_root = os.path.dirname(script_path)
+repo_root = os.path.dirname(os.path.dirname(script_root))
+
+if script_root not in sys.path:
+ sys.path.insert(0, script_root)
+
+from teachos.dump_mb2i import DumpMB2I
+
+components = {
+ "kstd": "libs/kstd/gdb",
+ "kapi": "kapi/gdb",
+}
+
+for component, path in components.items():
+ full_path = os.path.join(repo_root, *path.split("/"))
+ init_file = os.path.join(full_path, "__init__.py")
+
+ if os.path.isfile(init_file):
+ try:
+ spec = importlib.util.spec_from_file_location(component, init_file)
+ module = importlib.util.module_from_spec(spec)
+ sys.modules[component] = module
+ spec.loader.exec_module(module)
+
+ if hasattr(module, "register_printers"):
+ module.register_printers(gdb.current_objfile())
+ gdb.write(f"Info: Registered pretty printers for '{component}'.\n")
+ else:
+ gdb.write(
+ f"Warning: '{component}' does not have 'register_printers' function\n"
+ )
+ except Exception as e:
+ gdb.write(f"Warning: Failed to load '{component}' pretty printers: {e}\n")
+ else:
+ gdb.write(f"Warning: GDB extension init not found: '{init_file}'\n")
+
+gdb.write("Info: Loaded TeachOS pretty printers.\n")
+
+DumpMB2I()
+
+gdb.write("Info: Loaded TeachOS tools.\n") \ No newline at end of file
diff --git a/scripts/gdb/teachos/__init__.py b/scripts/gdb/teachos/__init__.py
new file mode 100644
index 0000000..a5eca92
--- /dev/null
+++ b/scripts/gdb/teachos/__init__.py
@@ -0,0 +1,8 @@
+def format_size(size):
+ for unit in ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"]:
+ if size < 1024.0:
+ if unit == "Bytes":
+ return f"{int(size)} {unit}"
+ return f"{size:.2f} {unit}"
+ size /= 1024.0
+ return f"{size:.2f} PiB"
diff --git a/scripts/gdb/teachos/dump_mb2i.py b/scripts/gdb/teachos/dump_mb2i.py
new file mode 100644
index 0000000..0657ebd
--- /dev/null
+++ b/scripts/gdb/teachos/dump_mb2i.py
@@ -0,0 +1,241 @@
+import gdb
+import struct
+from enum import IntEnum
+from teachos import format_size
+
+
+class TagType(IntEnum):
+ END = 0
+ CMDLINE = 1
+ BOOT_LOADER_NAME = 2
+ MODULE = 3
+ BASIC_MEMINFO = 4
+ BOOTDEV = 5
+ MMAP = 6
+ VBE = 7
+ FRAMEBUFFER = 8
+ ELF_SECTIONS = 9
+ APM = 10
+ EFI32_SYSTEM_TABLE_POINTER = 11
+ EFI64_SYSTEM_TABLE_POINTER = 12
+ SMBIOS_TABLES = 13
+ ACPI_OLD = 14
+ ACPI_NEW = 15
+ NETWORKING = 16
+ EFI_MEMORY_MAP = 17
+ EFI_BOOT_SERVICES_NOT_TERMINATED = 18
+ EFI32_IMAGE_HANDLE_POINTER = 19
+ EFI64_IMAGE_HANDLE_POINTER = 20
+ LOAD_BASE_ADDR = 21
+
+
+class MemoryType(IntEnum):
+ AVAILABLE = 1
+ RESERVED = 2
+ ACPI_RECLAIM = 3
+ NVS = 4
+ BAD = 5
+
+
+class FramebufferType(IntEnum):
+ INDEXED = 0
+ RGB = 1
+ TEXT = 2
+
+
+TAG_NAMES = {
+ TagType.END: "End of Tags",
+ TagType.CMDLINE: "Boot Command Line",
+ TagType.BOOT_LOADER_NAME: "Boot Loader Name",
+ TagType.MODULE: "Boot Module",
+ TagType.BASIC_MEMINFO: "Basic Memory Information",
+ TagType.BOOTDEV: "BIOS Boot Device",
+ TagType.MMAP: "Memory Map",
+ TagType.VBE: "VBE Info",
+ TagType.FRAMEBUFFER: "Framebuffer Info",
+ TagType.ELF_SECTIONS: "ELF Symbols",
+ TagType.APM: "APM Table",
+ TagType.EFI32_SYSTEM_TABLE_POINTER: "EFI 32-bit System Table Pointer",
+ TagType.EFI64_SYSTEM_TABLE_POINTER: "EFI 64-bit System Table Pointer",
+ TagType.SMBIOS_TABLES: "SMBIOS Tables",
+ TagType.ACPI_OLD: "ACPI old RSDP (1.0)",
+ TagType.ACPI_NEW: "ACPI new RSDP (2.0+)",
+ TagType.NETWORKING: "Networking Information",
+ TagType.EFI_MEMORY_MAP: "EFI Memory Map",
+ TagType.EFI_BOOT_SERVICES_NOT_TERMINATED: "EFI Boot Services Not Terminated",
+ TagType.EFI32_IMAGE_HANDLE_POINTER: "EFI 32-bit Image Handle Pointer",
+ TagType.EFI64_IMAGE_HANDLE_POINTER: "EFI 64-bit Image Handle Pointer",
+ TagType.LOAD_BASE_ADDR: "Image Load Base Physical Address",
+}
+
+MEMORY_TYPES = {
+ MemoryType.AVAILABLE: "Available",
+ MemoryType.RESERVED: "Reserved",
+ MemoryType.ACPI_RECLAIM: "ACPI Reclaim",
+ MemoryType.NVS: "Non-volatile storage",
+ MemoryType.BAD: "Bad Memory",
+}
+
+FRAMEBUFFER_TYPES = {
+ FramebufferType.INDEXED: "Indexed Color",
+ FramebufferType.RGB: "RGB",
+ FramebufferType.TEXT: "Text Mode",
+}
+
+
+INDENT = f"{' '*11}"
+
+
+class DumpMB2I(gdb.Command):
+ """
+ Dump the information provided by the Multiboot2 loader.
+ Usage: dump_mb2i <address_expression>
+ Example: dump_mb2i $rbx
+ dump_mb2i 0x8000
+ """
+
+ def __init__(self):
+ super(DumpMB2I, self).__init__("dump_mb2i", gdb.COMMAND_USER)
+
+ def invoke(self, arg, from_tty):
+ if not arg:
+ gdb.write(
+ "Error: please provide the address of the Multiboot2 information structure.\n"
+ )
+ return
+
+ try:
+ address = int(gdb.parse_and_eval(arg))
+ except gdb.error as e:
+ gdb.write(f"Error: Invalid address expression: {e}\n")
+ return
+
+ inferior = gdb.selected_inferior()
+
+ try:
+ header_data = inferior.read_memory(address, 8).tobytes()
+ total_size, reserved = struct.unpack("<II", header_data)
+ except gdb.MemoryError:
+ gdb.write(f"Error: Cannot read memory at {address:#018x}\n")
+ return
+
+ if total_size < 8 or total_size > 1024 * 1024:
+ gdb.write(
+ f"Warning: suspicious total size ({total_size} bytes). Aborting.\n"
+ )
+ return
+
+ gdb.write(f"+{'-'*70}+\n")
+ gdb.write(f"| Multiboot2 Boot Information Payload{' ':>34}|\n")
+ gdb.write(f"+{'-'*70}+\n")
+ gdb.write(f"Base Address: {address:#018x}\nTotal Size: {total_size} Bytes\n\n")
+
+ offset = 8
+
+ while offset < total_size:
+ tag_address = address + offset
+
+ try:
+ tag_header = inferior.read_memory(tag_address, 8).tobytes()
+ tag_type, tag_size = struct.unpack("<II", tag_header)
+ except gdb.MemoryError:
+ gdb.write(
+ f"Error: Cannot read memory at {tag_address:#018x} (offset {offset})\n"
+ )
+ break
+
+ if tag_type == TagType.END and tag_size == 8:
+ break
+
+ self._print_tag(inferior, tag_address, tag_type, tag_size)
+
+ offset += (tag_size + 7) & ~7
+
+ def _print_tag(self, inferior, tag_address, tag_type, tag_size):
+ name = TAG_NAMES.get(tag_type, f"Unknown Tag")
+ size = format_size(tag_size)
+ payload = format_size(tag_size - 8)
+ gdb.write(f"[Tag {tag_type:#04x}] {name} (size: {size} | payload: {payload})\n")
+
+ if tag_size <= 8 and tag_type != TagType.END:
+ return
+
+ try:
+ data = inferior.read_memory(tag_address + 8, tag_size - 8).tobytes()
+
+ handlers = {
+ TagType.CMDLINE: self._write_string_tag,
+ TagType.BOOT_LOADER_NAME: self._write_string_tag,
+ TagType.MODULE: self._write_module_tag,
+ TagType.BASIC_MEMINFO: self._write_basic_memory_info_tag,
+ TagType.BOOTDEV: self._write_boot_device_tag,
+ TagType.MMAP: self._write_memory_map_tag,
+ TagType.LOAD_BASE_ADDR: self._write_image_load_base_physical_address_tag,
+ TagType.FRAMEBUFFER: self._write_framebuffer_info_tag,
+ }
+
+ handler = handlers.get(tag_type, self._write_unknown_tag)
+ handler(data)
+
+ except Exception as e:
+ gdb.write(
+ f"{INDENT}<failed to decode tag: {e} ({tag_type:#04x} | {tag_size:#06x})>\n"
+ )
+
+ def _write_string_tag(self, data):
+ text = data.split(b"\x00")[0].decode("ascii", "replace")
+ gdb.write(f'{INDENT}"{text}"\n')
+
+ def _write_module_tag(self, data):
+ start, end = struct.unpack_from("<II", data)
+ string = data[8:].split(b"\x00")[0].decode("ascii", "replace")
+ gdb.write(
+ f"{INDENT}start: {start:#010x} | end: {end:#010x} | size: {format_size(end - start)}\n"
+ )
+ if string:
+ gdb.write(f'{INDENT}string: "{string}"\n')
+
+ def _write_basic_memory_info_tag(self, data):
+ lower, upper = struct.unpack_from("<II", data)
+ gdb.write(
+ f"{INDENT}upper memory: {format_size(upper * 1024)} | lower memory: {format_size(lower * 1024)}\n"
+ )
+
+ def _write_boot_device_tag(self, data):
+ device, partition, sub_partition = struct.unpack_from("<III", data)
+ gdb.write(
+ f"{INDENT}device: {device:#04x} | partition: {partition:#06x} | sub-partition: {sub_partition:#06x}\n"
+ )
+
+ def _write_memory_map_tag(self, data):
+ entry_size, entry_version = struct.unpack_from("<II", data)
+ if entry_size < 24:
+ return
+ num_entries = (len(data) - 8) // entry_size
+ for i in range(num_entries):
+ entry_offset = 8 + i * entry_size
+ base, length, memory_type = struct.unpack_from("<QQI", data, entry_offset)
+ type_string = MEMORY_TYPES.get(memory_type, "Unknown")
+ gdb.write(
+ f"{INDENT}[{i:02d}] {base:#018x} - {base + length:#018x} | {format_size(length)} | type: {type_string}\n"
+ )
+
+ def _write_image_load_base_physical_address_tag(self, data):
+ base = struct.unpack("<I", data)[0]
+ gdb.write(f"{INDENT}address: {base:#010x}\n")
+
+ def _write_framebuffer_info_tag(self, data):
+ address, pitch, width, height, bpp, type = struct.unpack_from("<QIIIBB", data)
+ type_string = FRAMEBUFFER_TYPES.get(type, "Unknown")
+ gdb.write(f"{INDENT}address: {address:#010x} | type: {type_string}\n")
+ gdb.write(f"{INDENT}depth: {bpp} | pitch: {format_size(pitch)}\n")
+ if type == FramebufferType.TEXT:
+ gdb.write(f"{INDENT}width: {width} chars | height: {height} chars\n")
+ else:
+ gdb.write(f"{INDENT}width: {width} px | height: {height} px\n")
+
+ def _write_unknown_tag(self, data):
+ for i in range(0, len(data), 16):
+ chunk = data[i : i + 16]
+ hex_chunk = " ".join([f"{b:02x}" for b in chunk])
+ gdb.write(f"{INDENT}{i:#010x}: {hex_chunk}\n")
diff --git a/scripts/gdb/toolchain.py b/scripts/gdb/toolchain.py
new file mode 100644
index 0000000..bbb7810
--- /dev/null
+++ b/scripts/gdb/toolchain.py
@@ -0,0 +1,35 @@
+import os
+import subprocess
+import sys
+
+
+def setup_toolchain_debugging():
+ try:
+ gcc_path = (
+ subprocess.check_output(["which", "x86_64-pc-elf-g++"])
+ .decode("utf-8")
+ .strip()
+ )
+ python_dir = None
+
+ share_path = os.path.join(os.path.dirname(os.path.dirname(gcc_path)), "share")
+ for root, dirs, _ in os.walk(share_path):
+ if "libstdcxx" in dirs:
+ python_dir = root
+ break
+
+ if python_dir:
+ sys.path.insert(0, python_dir)
+
+ from libstdcxx.v6.printers import register_libstdcxx_printers
+ from libstdcxx.v6.xmethods import register_libstdcxx_xmethods
+
+ register_libstdcxx_printers(None)
+ register_libstdcxx_xmethods(None)
+
+ print(f"Loaded Printers & Xmethods from: {python_dir}")
+ except Exception as e:
+ print(f"Debug setup failed: {e}")
+
+
+setup_toolchain_debugging()
diff --git a/scripts/qemu-wrapper.sh b/scripts/qemu-wrapper.sh
index fa89ac5..dd0200d 100755
--- a/scripts/qemu-wrapper.sh
+++ b/scripts/qemu-wrapper.sh
@@ -1,4 +1,30 @@
#!/bin/bash
+# scripts/qemu-wrapper.sh
-echo "QEMU WRAPPER"
-qemu-system-x86_64 $@
+WORKSPACE_DIR=${1:-.}
+BUILD_TYPE=${2:-Debug}
+ISO_PATH=$3
+IS_DEBUG=${4:-0}
+
+if [ -z "$ISO_PATH" ]; then
+ echo "Usage: $0 <workspace_dir> <build_type> <iso_path> [is_debug]"
+ exit 1
+fi
+
+ARGS=(
+ "-m" "64M"
+ "-machine" "q35"
+ "-smp" "4,sockets=1,cores=4,threads=1"
+ "-display" "curses"
+ "-debugcon" "file:${WORKSPACE_DIR}/qemu-debugcon-${BUILD_TYPE}.log"
+ "-cdrom" "${ISO_PATH}"
+)
+
+if [ "$IS_DEBUG" == "1" ]; then
+ ARGS=( "-s" "-no-reboot" "-d" "int,cpu_reset" "${ARGS[@]}" )
+fi
+
+echo "QEMU WRAPPER: Executing TeachOS"
+echo "Arguments: ${ARGS[@]}"
+
+qemu-system-x86_64 "${ARGS[@]}" 2> "${WORKSPACE_DIR}/qemu-stderr-${BUILD_TYPE}.log"