diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index dcc2276f..b23ebdb9 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -31,6 +31,7 @@ set(KERNEL_SOURCES kernel/FS/ProcFS/Inode.cpp kernel/FS/TmpFS/FileSystem.cpp kernel/FS/TmpFS/Inode.cpp + kernel/FS/USTARModule.cpp kernel/FS/VirtualFileSystem.cpp kernel/GDT.cpp kernel/IDT.cpp diff --git a/kernel/include/kernel/FS/USTARModule.h b/kernel/include/kernel/FS/USTARModule.h new file mode 100644 index 00000000..4abb7f50 --- /dev/null +++ b/kernel/include/kernel/FS/USTARModule.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace Kernel +{ + + bool is_ustar_boot_module(const BootModule&); + BAN::ErrorOr unpack_boot_module_into_filesystem(BAN::RefPtr, const BootModule&); + +} diff --git a/kernel/kernel/FS/USTARModule.cpp b/kernel/kernel/FS/USTARModule.cpp new file mode 100644 index 00000000..8d6138d8 --- /dev/null +++ b/kernel/kernel/FS/USTARModule.cpp @@ -0,0 +1,166 @@ +#include +#include + +#include + +namespace Kernel +{ + + bool is_ustar_boot_module(const BootModule& module) + { + if (module.start % PAGE_SIZE) + { + dprintln("ignoring non-page-aligned module"); + return false; + } + + if (module.size < 512) + return false; + + bool has_ustar_signature; + PageTable::with_fast_page(module.start, [&] { + has_ustar_signature = memcmp(PageTable::fast_page_as_ptr(257), "ustar", 5) == 0; + }); + + return has_ustar_signature; + } + + BAN::ErrorOr unpack_boot_module_into_filesystem(BAN::RefPtr filesystem, const BootModule& module) + { + ASSERT(is_ustar_boot_module(module)); + + auto root_inode = filesystem->root_inode(); + + uint8_t* temp_page = static_cast(kmalloc(PAGE_SIZE)); + if (temp_page == nullptr) + return BAN::Error::from_errno(ENOMEM); + BAN::ScopeGuard _([temp_page] { kfree(temp_page); }); + + size_t offset = 0; + while (offset + 512 <= module.size) + { + size_t file_size = 0; + mode_t file_mode = 0; + uid_t file_uid = 0; + gid_t file_gid = 0; + uint8_t file_type = 0; + char file_path[100 + 1 + 155 + 1] {}; + + PageTable::with_fast_page((module.start + offset) & PAGE_ADDR_MASK, [&] { + const size_t page_off = offset % PAGE_SIZE; + + const auto parse_octal = + [page_off](size_t offset, size_t length) -> size_t + { + size_t result = 0; + for (size_t i = 0; i < length; i++) + { + const char ch = PageTable::fast_page_as(page_off + offset + i); + if (ch == '\0') + break; + result = (result * 8) + (ch - '0'); + } + return result; + }; + + if (memcmp(PageTable::fast_page_as_ptr(page_off + 257), "ustar", 5)) { + file_size = SIZE_MAX; + return; + } + + memcpy(file_path, PageTable::fast_page_as_ptr(page_off + 345), 155); + const size_t prefix_len = strlen(file_path); + file_path[prefix_len] = '/'; + memcpy(file_path + prefix_len + 1, PageTable::fast_page_as_ptr(page_off), 100); + + file_mode = parse_octal(100, 8); + file_uid = parse_octal(108, 8); + file_gid = parse_octal(116, 8); + file_size = parse_octal(124, 12); + file_type = PageTable::fast_page_as(page_off + 156); + }); + + if (file_size == SIZE_MAX) + break; + if (offset + 512 + file_size > module.size) + break; + + auto parent_inode = filesystem->root_inode(); + + auto file_path_parts = TRY(BAN::StringView(file_path).split('/')); + for (size_t i = 0; i < file_path_parts.size() - 1; i++) + parent_inode = TRY(parent_inode->find_inode(file_path_parts[i])); + + switch (file_type) + { + case REGTYPE: + case AREGTYPE: file_mode |= Inode::Mode::IFREG; break; + case LNKTYPE: break; + case SYMTYPE: file_mode |= Inode::Mode::IFLNK; break; + case CHRTYPE: file_mode |= Inode::Mode::IFCHR; break; + case BLKTYPE: file_mode |= Inode::Mode::IFBLK; break; + case DIRTYPE: file_mode |= Inode::Mode::IFDIR; break; + case FIFOTYPE: file_mode |= Inode::Mode::IFIFO; break; + default: + ASSERT_NOT_REACHED(); + } + + auto file_name_sv = file_path_parts.back(); + + if (file_type == DIRTYPE) + { + TRY(parent_inode->create_directory(file_name_sv, file_mode, file_uid, file_gid)); + } + else if (file_type == LNKTYPE) + { + dwarnln("TODO: hardlink"); + } + else if (file_type == SYMTYPE) + { + TRY(parent_inode->create_file(file_name_sv, file_mode, file_uid, file_gid)); + + char link_target[101] {}; + const paddr_t paddr = module.start + offset; + PageTable::with_fast_page(paddr & PAGE_ADDR_MASK, [&] { + memcpy(link_target, PageTable::fast_page_as_ptr((paddr % PAGE_SIZE) + 157), 100); + }); + + if (link_target[0]) + { + auto inode = TRY(parent_inode->find_inode(file_name_sv)); + TRY(inode->set_link_target(link_target)); + } + } + else + { + TRY(parent_inode->create_file(file_name_sv, file_mode, file_uid, file_gid)); + + if (file_size) + { + auto inode = TRY(parent_inode->find_inode(file_name_sv)); + + size_t nwritten = 0; + while (nwritten < file_size) + { + const paddr_t paddr = module.start + offset + 512 + nwritten; + PageTable::with_fast_page(paddr & PAGE_ADDR_MASK, [&] { + memcpy(temp_page, PageTable::fast_page_as_ptr(), PAGE_SIZE); + }); + + const size_t page_off = paddr % PAGE_SIZE; + const size_t to_write = BAN::Math::min(file_size - nwritten, PAGE_SIZE - page_off); + TRY(inode->write(nwritten, { temp_page + page_off, to_write })); + nwritten += to_write; + } + } + } + + offset += 512 + file_size; + if (auto rem = offset % 512) + offset += 512 - rem; + } + + return {}; + } + +} diff --git a/kernel/kernel/FS/VirtualFileSystem.cpp b/kernel/kernel/FS/VirtualFileSystem.cpp index 60139480..1183cfd6 100644 --- a/kernel/kernel/FS/VirtualFileSystem.cpp +++ b/kernel/kernel/FS/VirtualFileSystem.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,31 @@ namespace Kernel return BAN::RefPtr(static_cast(device_inode.ptr())); } - static BAN::RefPtr find_root_device(BAN::StringView root_path) + static BAN::RefPtr load_fallback_root_filesystem() + { + if (g_boot_info.modules.empty()) + panic("No fallback boot modules given"); + + auto filesystem_or_error = TmpFileSystem::create(-1, 0755, 0, 0); + if (filesystem_or_error.is_error()) + panic("Failed to create fallback filesystem: {}", filesystem_or_error.error()); + + dwarnln("Attempting to load fallback filesystem from {} modules", g_boot_info.modules.size()); + + auto filesystem = BAN::RefPtr::adopt(filesystem_or_error.release_value()); + + for (const auto& module : g_boot_info.modules) + { + if (!is_ustar_boot_module(module)) + continue; + if (auto ret = unpack_boot_module_into_filesystem(filesystem, module); ret.is_error()) + dwarnln("Failed to unpack boot module: {}", ret.error()); + } + + return filesystem; + } + + static BAN::RefPtr load_root_filesystem(BAN::StringView root_path) { enum class RootType { @@ -66,19 +91,26 @@ namespace Kernel { entry = root_path.substring(9); if (entry.size() != 36) - panic("Invalid UUID '{}'", entry); + { + derrorln("Invalid UUID '{}'", entry); + return load_fallback_root_filesystem(); + } type = RootType::PartitionUUID; } else if (root_path.starts_with("/dev/"_sv)) { entry = root_path.substring(5); if (entry.empty() || entry.contains('/')) - panic("Invalid root path '{}'", root_path); + { + derrorln("Invalid root path '{}'", root_path); + return load_fallback_root_filesystem(); + } type = RootType::BlockDeviceName; } else { - panic("Unsupported root path format '{}'", root_path); + derrorln("Unsupported root path format '{}'", root_path); + return load_fallback_root_filesystem(); } constexpr size_t timeout_ms = 10'000; @@ -99,15 +131,30 @@ namespace Kernel } if (!ret.is_error()) - return ret.release_value(); + { + auto filesystem_or_error = FileSystem::from_block_device(ret.release_value()); + if (filesystem_or_error.is_error()) + { + derrorln("Could not create filesystem from '{}': {}", root_path, filesystem_or_error.error()); + return load_fallback_root_filesystem(); + } + return filesystem_or_error.release_value();; + } if (ret.error().get_error_code() != ENOENT) - panic("could not open root device '{}': {}", root_path, ret.error()); + { + derrorln("Could not open root device '{}': {}", root_path, ret.error()); + return load_fallback_root_filesystem(); + } + + if (i == 4) + dwarnln("Could not find specified root device, waiting for it to get loaded..."); SystemTimer::get().sleep_ms(sleep_ms); } - panic("could not find root device '{}' after {} ms", root_path, timeout_ms); + derrorln("Could not find root device '{}' after {} ms", root_path, timeout_ms); + return load_fallback_root_filesystem(); } void VirtualFileSystem::initialize(BAN::StringView root_path) @@ -115,13 +162,9 @@ namespace Kernel ASSERT(!s_instance); s_instance = MUST(BAN::RefPtr::create()); - auto root_device = find_root_device(root_path); - ASSERT(root_device); - - auto filesystem_result = FileSystem::from_block_device(root_device); - if (filesystem_result.is_error()) - panic("Could not create filesystem from '{}': {}", root_path, filesystem_result.error()); - s_instance->m_root_fs = filesystem_result.release_value(); + s_instance->m_root_fs = load_root_filesystem(root_path); + if (!s_instance->m_root_fs) + panic("Could not load root filesystem"); Credentials root_creds { 0, 0, 0, 0 }; MUST(s_instance->mount(root_creds, &DevFileSystem::get(), "/dev"_sv));