From 007696a98d84ae74ac95dc66c23c8770d6bff065 Mon Sep 17 00:00:00 2001 From: bbuerge Date: Mon, 5 Oct 2026 12:47:32 +0000 Subject: added fat32 img, wip parse bootsector --- arch/x86_64/support/grub.cfg.in | 1 + arch/x86_64/support/modules/fat32.img | 3 ++ kernel/CMakeLists.txt | 1 + kernel/kernel/filesystems/fat32/boot_sector.hpp | 18 ++++++++ kernel/kernel/filesystems/fat32/driver_state.hpp | 23 ++++++++++ kernel/kernel/filesystems/fat32/error.cpp | 58 ++++++++++++++++++++++++ kernel/kernel/filesystems/fat32/error.hpp | 33 ++++++++++++++ kernel/kernel/filesystems/fat32/filesystem.cpp | 29 +++++++++++- kernel/kernel/filesystems/fat32/filesystem.hpp | 9 ++++ kernel/kernel/main.cpp | 17 ++++++- scripts/qemu-wrapper.sh | 2 +- 11 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 arch/x86_64/support/modules/fat32.img create mode 100644 kernel/kernel/filesystems/fat32/boot_sector.hpp create mode 100644 kernel/kernel/filesystems/fat32/driver_state.hpp create mode 100644 kernel/kernel/filesystems/fat32/error.cpp create mode 100644 kernel/kernel/filesystems/fat32/error.hpp diff --git a/arch/x86_64/support/grub.cfg.in b/arch/x86_64/support/grub.cfg.in index 06701fdc..135dc107 100644 --- a/arch/x86_64/support/grub.cfg.in +++ b/arch/x86_64/support/grub.cfg.in @@ -6,5 +6,6 @@ menuentry "TeachOS" { module2 /modules/ext2_4KB_fs.img type=ramdisk module2 /modules/ext2_1KB_fs.img type=ramdisk module2 /modules/ext2_2KB_fs.img type=ramdisk + module2 /modules/fat32.img type=ramdisk boot } \ No newline at end of file diff --git a/arch/x86_64/support/modules/fat32.img b/arch/x86_64/support/modules/fat32.img new file mode 100644 index 00000000..ff9c6e08 --- /dev/null +++ b/arch/x86_64/support/modules/fat32.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1eea0fe2e0d9f93ac6a55a25c071329c038f111d085148d5f6a6d1d35e539672 +size 35651584 diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index e7464658..aacb5d97 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -86,6 +86,7 @@ target_sources("kernel_lib" PRIVATE "kernel/filesystems/ext2/write_batch.cpp" # fat32 Filesystem + "kernel/filesystems/fat32/error.cpp" "kernel/filesystems/fat32/filesystem.cpp" "kernel/filesystems/fat32/inode.cpp" "kernel/filesystems/fat32/module.cpp" diff --git a/kernel/kernel/filesystems/fat32/boot_sector.hpp b/kernel/kernel/filesystems/fat32/boot_sector.hpp new file mode 100644 index 00000000..c66e2fc5 --- /dev/null +++ b/kernel/kernel/filesystems/fat32/boot_sector.hpp @@ -0,0 +1,18 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_FAT32_BOOT_SECTOR_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_FAT32_BOOT_SECTOR_HPP + +#include + +#include + +namespace kernel::filesystems::fat32 +{ + //! The boot-sector in the fat32 filesystem. + struct boot_sector // todo gnu::packed? + { + std::uint16_t bytes_per_sector; + std::uint16_t boot_signature; + }; +} // namespace kernel::filesystems::fat32 + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/fat32/driver_state.hpp b/kernel/kernel/filesystems/fat32/driver_state.hpp new file mode 100644 index 00000000..55b90724 --- /dev/null +++ b/kernel/kernel/filesystems/fat32/driver_state.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_FAT32_DRIVER_STATE_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_FAT32_DRIVER_STATE_HPP + +#include +#include +#include + +#include + +#include +#include + +namespace kernel::filesystems::fat32 +{ + + struct driver_state : vfs::driver_state + { + struct boot_sector boot_sector{}; + }; + +} // namespace kernel::filesystems::fat32 + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/fat32/error.cpp b/kernel/kernel/filesystems/fat32/error.cpp new file mode 100644 index 00000000..4a18eb9e --- /dev/null +++ b/kernel/kernel/filesystems/fat32/error.cpp @@ -0,0 +1,58 @@ +#include + +#include + +#include + +#include + +namespace kernel::filesystems::fat32 +{ + + namespace + { + struct category_t final : kstd::error_category + { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override + { + return "fat32"; + } + + [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override + { + switch (static_cast(value)) + { + case errc::invalid_boot_signature: + return "invalid boot signature"; + default: + return "unknown fat32 error"; + }; + } + + [[nodiscard]] constexpr auto equivalent(int code, kstd::error_condition const & condition) const noexcept + -> bool override + { + switch (static_cast(code)) + { + case errc::invalid_boot_signature: + if (condition.category() == kernel::vfs::category()) + { + return condition.value() == static_cast(kernel::vfs::errc::invalid_filesystem); + } + else if (condition.category() == kstd::generic_category()) + { + return condition.value() == static_cast(kstd::errc::invalid_argument); + } + break; + } + return kstd::error_category::equivalent(code, condition); + } + } constexpr inline fat32_category_instance{}; + } // namespace + + [[nodiscard]] auto category() noexcept -> kstd::error_category const & + { + return fat32_category_instance; + } + +} // namespace kernel::filesystems::fat32 diff --git a/kernel/kernel/filesystems/fat32/error.hpp b/kernel/kernel/filesystems/fat32/error.hpp new file mode 100644 index 00000000..6955be3f --- /dev/null +++ b/kernel/kernel/filesystems/fat32/error.hpp @@ -0,0 +1,33 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_FAT32_ERROR_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_FAT32_ERROR_HPP + +#include + +#include + +#include + +namespace kernel::filesystems::fat32 +{ + enum struct errc : int + { + invalid_boot_signature = 1 + }; + + [[nodiscard]] auto category() noexcept -> kstd::error_category const &; + + [[nodiscard]] constexpr auto inline make_error_code(errc error) noexcept -> kstd::error_code + { + return {static_cast(error), category()}; + } +} // namespace kernel::filesystems::fat32 + +namespace kstd +{ + template<> + struct is_error_code_enum : std::true_type + { + }; +} // namespace kstd + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/fat32/filesystem.cpp b/kernel/kernel/filesystems/fat32/filesystem.cpp index a66a9b25..4899f0bd 100644 --- a/kernel/kernel/filesystems/fat32/filesystem.cpp +++ b/kernel/kernel/filesystems/fat32/filesystem.cpp @@ -1,5 +1,7 @@ #include +#include +#include #include #include #include @@ -7,8 +9,11 @@ #include #include +#include #include +#include #include +#include #include #include @@ -20,11 +25,31 @@ namespace kernel::filesystems::fat32 auto filesystem::probe(inode_ptr const & inode) const -> kstd::result { - if (inode) + auto boot_sector = fat32::boot_sector{}; + auto const boot_signature_bytes = kstd::raw_bytes(boot_sector.boot_signature); + auto boot_signature_result = inode->read(boot_signature_bytes, kstd::bytes{constants::boot_signature_offset}); + + if (!boot_signature_result) { - return kstd::failure(vfs::errc::invalid_argument); + return kstd::failure(boot_signature_result.error()); } + if (boot_sector.boot_signature != constants::boot_signature) + { + return kstd::failure(errc::invalid_boot_signature); + } + + auto const bytes_per_sector_bytes = kstd::raw_bytes(boot_sector.bytes_per_sector); + auto bytes_per_sector_result = inode->read(bytes_per_sector_bytes, kstd::bytes{constants::bytes_per_sector_offset}); + + if (!bytes_per_sector_result) + { + return kstd::failure(bytes_per_sector_result.error()); + } + + kstd::println("[OS:FAT32] bytes_per_sector: {}", boot_sector.bytes_per_sector); + kstd::println("[OS:FAT32] boot_signature: {:#06X}", boot_sector.boot_signature); + return 0; } diff --git a/kernel/kernel/filesystems/fat32/filesystem.hpp b/kernel/kernel/filesystems/fat32/filesystem.hpp index 8b0b4941..9c516ca9 100644 --- a/kernel/kernel/filesystems/fat32/filesystem.hpp +++ b/kernel/kernel/filesystems/fat32/filesystem.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,14 @@ namespace kernel::filesystems::fat32 { + //! Constants related to the fat32 filesystem. + namespace constants + { + constexpr kstd::bytes inline bytes_per_sector_offset = kstd::bytes{0x0B}; + constexpr kstd::bytes inline boot_signature_offset = kstd::bytes{0x1FE}; + constexpr std::uint16_t inline boot_signature = 0xAA55; + } // namespace constants + //! The File Allocation Table 32 Filesystem (fat32) struct filesystem : kernel::vfs::filesystem { diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp index bc192950..15952547 100644 --- a/kernel/kernel/main.cpp +++ b/kernel/kernel/main.cpp @@ -25,6 +25,21 @@ using namespace kstd::units_literals; +auto run_demo_fat32() -> void +{ + kstd::println("[OS:DEM] Running FAT32 Demo"); + + kstd::println("[OS:DEM] mount /dev/ram3 to /entrance"); + if (auto result = kapi::filesystem::mount("/dev/ram3", "/entrance")) + { + kstd::println("[OS:DEM] mounted /dev/ram3 to /entrance"); + } + else + { + kapi::system::panic("[OS:DEM] failed to mount /dev/ram3 on /entrance", result.error()); + } +} + auto run_demo() -> void { // 1) open a file @@ -312,7 +327,7 @@ auto main() -> int kstd::println("[OS] Virtual filesystem initialized."); // TODO BA-FS26 remove demo code? - run_demo(); + run_demo_fat32(); kapi::system::panic("[OS] Returning from kernel main!"); } diff --git a/scripts/qemu-wrapper.sh b/scripts/qemu-wrapper.sh index dd0200df..5e83f055 100755 --- a/scripts/qemu-wrapper.sh +++ b/scripts/qemu-wrapper.sh @@ -12,7 +12,7 @@ if [ -z "$ISO_PATH" ]; then fi ARGS=( - "-m" "64M" + "-m" "128M" "-machine" "q35" "-smp" "4,sockets=1,cores=4,threads=1" "-display" "curses" -- cgit v1.2.3 From a92f378780c457ae8120ef66e33ebed0169975ec Mon Sep 17 00:00:00 2001 From: manuel Date: Thu, 8 Oct 2026 08:30:26 +0000 Subject: kernel/filesystems: read entire boot sector fat32 and print --- kernel/kernel/filesystems/fat32/boot_sector.hpp | 46 +++++++++++++++-- kernel/kernel/filesystems/fat32/filesystem.cpp | 69 ++++++++++++++++++++----- kernel/kernel/filesystems/fat32/filesystem.hpp | 3 +- 3 files changed, 97 insertions(+), 21 deletions(-) diff --git a/kernel/kernel/filesystems/fat32/boot_sector.hpp b/kernel/kernel/filesystems/fat32/boot_sector.hpp index c66e2fc5..10f31672 100644 --- a/kernel/kernel/filesystems/fat32/boot_sector.hpp +++ b/kernel/kernel/filesystems/fat32/boot_sector.hpp @@ -1,18 +1,54 @@ #ifndef TEACHOS_KERNEL_FILESYSTEMS_FAT32_BOOT_SECTOR_HPP #define TEACHOS_KERNEL_FILESYSTEMS_FAT32_BOOT_SECTOR_HPP -#include - +#include #include namespace kernel::filesystems::fat32 { - //! The boot-sector in the fat32 filesystem. - struct boot_sector // todo gnu::packed? + //! The boot sector in the fat32 filesystem. + //! + //! The layout follows the "Microsoft Extensible Firmware Initiative FAT32 File System Specification" (fatgen103). + //! The sector consists of the BIOS Parameter Block (BPB) common to all FAT variants, followed by the FAT32 specific + //! extension. + // cspell:ignore fatgen + struct [[gnu::packed]] boot_sector { + // Common BIOS Parameter Block + std::array jump_boot; + std::array oem_name; std::uint16_t bytes_per_sector; + std::uint8_t sectors_per_cluster; + std::uint16_t reserved_sector_count; + std::uint8_t number_of_fats; + std::uint16_t root_entry_count; + std::uint16_t total_sectors_16; + std::uint8_t media; + std::uint16_t fat_size_16; + std::uint16_t sectors_per_track; + std::uint16_t number_of_heads; + std::uint32_t hidden_sectors; + std::uint32_t total_sectors_32; + + // FAT32 Extended BIOS Parameter Block + std::uint32_t fat_size_32; + std::uint16_t extended_flags; + std::uint16_t filesystem_version; + std::uint32_t root_cluster; + std::uint16_t filesystem_info_sector; + std::uint16_t backup_boot_sector; + std::array reserved; + std::uint8_t drive_number; + std::uint8_t reserved1; + std::uint8_t extended_boot_signature; + std::uint32_t volume_id; + std::array volume_label; // NOLINT + std::array filesystem_type; + std::array boot_code; // NOLINT std::uint16_t boot_signature; }; + + static_assert(sizeof(boot_sector) == 512, "The fat32 boot sector must be exactly 512 bytes in size."); } // namespace kernel::filesystems::fat32 -#endif \ No newline at end of file +#endif diff --git a/kernel/kernel/filesystems/fat32/filesystem.cpp b/kernel/kernel/filesystems/fat32/filesystem.cpp index 4899f0bd..30f1b286 100644 --- a/kernel/kernel/filesystems/fat32/filesystem.cpp +++ b/kernel/kernel/filesystems/fat32/filesystem.cpp @@ -15,6 +15,8 @@ #include #include +#include +#include #include #include #include @@ -23,15 +25,63 @@ namespace kernel::filesystems::fat32 { static_assert(sizeof(kernel::vfs::filesystem) == sizeof(fat32::filesystem)); + //! Hidden implementation details. + namespace + { + //! Convert a fixed size, space padded character array of the boot sector into a string view. + //! + //! @param array The array to convert. + //! @return A view of the array contents. + template + [[nodiscard]] constexpr auto to_string_view(std::array const & array) -> std::string_view + { + return {array.data(), array.size()}; + } + + //! Print all fields of a boot sector. + //! + //! @param boot_sector The boot sector to print. + auto print_boot_sector(boot_sector const & boot_sector) -> void + { + kstd::println("[OS:FAT32] jump_boot: {:#04X} {:#04X} {:#04X}", boot_sector.jump_boot[0], boot_sector.jump_boot[1], + boot_sector.jump_boot[2]); + kstd::println("[OS:FAT32] oem_name: '{}'", to_string_view(boot_sector.oem_name)); + kstd::println("[OS:FAT32] bytes_per_sector: {}", boot_sector.bytes_per_sector); + kstd::println("[OS:FAT32] sectors_per_cluster: {}", boot_sector.sectors_per_cluster); + kstd::println("[OS:FAT32] reserved_sector_count: {}", boot_sector.reserved_sector_count); + kstd::println("[OS:FAT32] number_of_fats: {}", boot_sector.number_of_fats); + kstd::println("[OS:FAT32] root_entry_count: {}", boot_sector.root_entry_count); + kstd::println("[OS:FAT32] total_sectors_16: {}", boot_sector.total_sectors_16); + kstd::println("[OS:FAT32] media: {:#04X}", boot_sector.media); + kstd::println("[OS:FAT32] fat_size_16: {}", boot_sector.fat_size_16); + kstd::println("[OS:FAT32] sectors_per_track: {}", boot_sector.sectors_per_track); + kstd::println("[OS:FAT32] number_of_heads: {}", boot_sector.number_of_heads); + kstd::println("[OS:FAT32] hidden_sectors: {}", boot_sector.hidden_sectors); + kstd::println("[OS:FAT32] total_sectors_32: {}", boot_sector.total_sectors_32); + kstd::println("[OS:FAT32] fat_size_32: {}", boot_sector.fat_size_32); + kstd::println("[OS:FAT32] extended_flags: {:#06X}", boot_sector.extended_flags); + kstd::println("[OS:FAT32] filesystem_version: {:#06X}", boot_sector.filesystem_version); + kstd::println("[OS:FAT32] root_cluster: {}", boot_sector.root_cluster); + kstd::println("[OS:FAT32] filesystem_info_sector: {}", boot_sector.filesystem_info_sector); + kstd::println("[OS:FAT32] backup_boot_sector: {}", boot_sector.backup_boot_sector); + kstd::println("[OS:FAT32] drive_number: {:#04X}", boot_sector.drive_number); + kstd::println("[OS:FAT32] extended_boot_signature: {:#04X}", boot_sector.extended_boot_signature); + kstd::println("[OS:FAT32] volume_id: {:#010X}", boot_sector.volume_id); + kstd::println("[OS:FAT32] volume_label: '{}'", to_string_view(boot_sector.volume_label)); + kstd::println("[OS:FAT32] filesystem_type: '{}'", to_string_view(boot_sector.filesystem_type)); + kstd::println("[OS:FAT32] boot_signature: {:#06X}", boot_sector.boot_signature); + } + } // namespace + auto filesystem::probe(inode_ptr const & inode) const -> kstd::result { auto boot_sector = fat32::boot_sector{}; - auto const boot_signature_bytes = kstd::raw_bytes(boot_sector.boot_signature); - auto boot_signature_result = inode->read(boot_signature_bytes, kstd::bytes{constants::boot_signature_offset}); + auto const bytes = kstd::raw_bytes(boot_sector); + auto const read_result = inode->read(bytes, constants::boot_sector_offset); - if (!boot_signature_result) + if (!read_result) { - return kstd::failure(boot_signature_result.error()); + return kstd::failure(read_result.error()); } if (boot_sector.boot_signature != constants::boot_signature) @@ -39,16 +89,7 @@ namespace kernel::filesystems::fat32 return kstd::failure(errc::invalid_boot_signature); } - auto const bytes_per_sector_bytes = kstd::raw_bytes(boot_sector.bytes_per_sector); - auto bytes_per_sector_result = inode->read(bytes_per_sector_bytes, kstd::bytes{constants::bytes_per_sector_offset}); - - if (!bytes_per_sector_result) - { - return kstd::failure(bytes_per_sector_result.error()); - } - - kstd::println("[OS:FAT32] bytes_per_sector: {}", boot_sector.bytes_per_sector); - kstd::println("[OS:FAT32] boot_signature: {:#06X}", boot_sector.boot_signature); + print_boot_sector(boot_sector); return 0; } diff --git a/kernel/kernel/filesystems/fat32/filesystem.hpp b/kernel/kernel/filesystems/fat32/filesystem.hpp index 9c516ca9..54c8035c 100644 --- a/kernel/kernel/filesystems/fat32/filesystem.hpp +++ b/kernel/kernel/filesystems/fat32/filesystem.hpp @@ -22,8 +22,7 @@ namespace kernel::filesystems::fat32 //! Constants related to the fat32 filesystem. namespace constants { - constexpr kstd::bytes inline bytes_per_sector_offset = kstd::bytes{0x0B}; - constexpr kstd::bytes inline boot_signature_offset = kstd::bytes{0x1FE}; + constexpr kstd::bytes inline boot_sector_offset = kstd::bytes{0}; constexpr std::uint16_t inline boot_signature = 0xAA55; } // namespace constants -- cgit v1.2.3 From 2d7acbf819aa1377c240c874539151001a5f96ce Mon Sep 17 00:00:00 2001 From: manuel Date: Sun, 11 Oct 2026 07:38:00 +0000 Subject: kernel/filesystems: add tests for probe and better NOLINT comments --- kernel/kernel/filesystems/fat32/boot_sector.hpp | 6 +-- .../kernel/filesystems/fat32/filesystem.tests.cpp | 51 ++++++++++++++++++++++ .../kernel/test_support/vfs/test_assets/fat32.img | 1 + 3 files changed, 55 insertions(+), 3 deletions(-) create mode 120000 kernel/kernel/test_support/vfs/test_assets/fat32.img diff --git a/kernel/kernel/filesystems/fat32/boot_sector.hpp b/kernel/kernel/filesystems/fat32/boot_sector.hpp index 10f31672..b5789b92 100644 --- a/kernel/kernel/filesystems/fat32/boot_sector.hpp +++ b/kernel/kernel/filesystems/fat32/boot_sector.hpp @@ -37,14 +37,14 @@ namespace kernel::filesystems::fat32 std::uint32_t root_cluster; std::uint16_t filesystem_info_sector; std::uint16_t backup_boot_sector; - std::array reserved; + std::array reserved; // NOLINT(readability-magic-numbers) std::uint8_t drive_number; std::uint8_t reserved1; std::uint8_t extended_boot_signature; std::uint32_t volume_id; - std::array volume_label; // NOLINT + std::array volume_label; // NOLINT(readability-magic-numbers) std::array filesystem_type; - std::array boot_code; // NOLINT + std::array boot_code; // NOLINT(readability-magic-numbers) std::uint16_t boot_signature; }; diff --git a/kernel/kernel/filesystems/fat32/filesystem.tests.cpp b/kernel/kernel/filesystems/fat32/filesystem.tests.cpp index cc35cd74..8e4bc8c1 100644 --- a/kernel/kernel/filesystems/fat32/filesystem.tests.cpp +++ b/kernel/kernel/filesystems/fat32/filesystem.tests.cpp @@ -1,15 +1,25 @@ #include +#include +#include +#include +#include +#include #include #include #include #include +#include #include #include +#include + +using namespace kstd::units_literals; + SCENARIO("fat32 filesystem mount and lookup", "[filesystem][fat32][filesystem]") { GIVEN("a mounted fat32 filesystem") @@ -46,3 +56,44 @@ SCENARIO("fat32 filesystem mount and lookup", "[filesystem][fat32][filesystem]") } } } + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, "Fat32 filesystem probe with real image", + "[filesystem][fat32][filesystem][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "fat32.img"; + + GIVEN("a block device containing a fat32 image") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::determine_boot_device(); + REQUIRE(boot_device); + + auto dev_inode = kstd::make_shared(boot_device); + auto fs = kstd::make_shared(); + + THEN("probe accepts the boot sector") + { + REQUIRE(fs->probe(dev_inode)); + } + } +} + +SCENARIO("Fat32 filesystem rejects invalid boot signature", "[filesystem][fat32][filesystem]") +{ + auto const block_size = 512_B; + GIVEN("a block device that does not contain a fat32 boot sector") + { + auto device = kstd::make_shared("mock", block_size, 2 * block_size); + REQUIRE(device); + + auto dev_inode = kstd::make_shared(device); + auto fs = kstd::make_shared(); + + THEN("probe fails with invalid_boot_signature") + { + REQUIRE(fs->probe(dev_inode).error() == kernel::filesystems::fat32::errc::invalid_boot_signature); + } + } +} diff --git a/kernel/kernel/test_support/vfs/test_assets/fat32.img b/kernel/kernel/test_support/vfs/test_assets/fat32.img new file mode 120000 index 00000000..31031c9f --- /dev/null +++ b/kernel/kernel/test_support/vfs/test_assets/fat32.img @@ -0,0 +1 @@ +../../../../../arch/x86_64/support/modules/fat32.img \ No newline at end of file -- cgit v1.2.3