Kernel: Load USTAR from a boot module if loading root partition fails

This allows banan-os to boot on hardware where we don't have working
storage driver or the storage driver fails (pretty common with my usb
mass storage drivers...)
This commit is contained in:
Bananymous 2025-06-30 16:09:55 +03:00
parent 17f1ac10e3
commit a5b4cee298
4 changed files with 236 additions and 14 deletions

View File

@ -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

View File

@ -0,0 +1,12 @@
#pragma once
#include <kernel/BootInfo.h>
#include <kernel/FS/FileSystem.h>
namespace Kernel
{
bool is_ustar_boot_module(const BootModule&);
BAN::ErrorOr<void> unpack_boot_module_into_filesystem(BAN::RefPtr<FileSystem>, const BootModule&);
}

View File

@ -0,0 +1,166 @@
#include <BAN/ScopeGuard.h>
#include <kernel/FS/USTARModule.h>
#include <tar.h>
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<void> unpack_boot_module_into_filesystem(BAN::RefPtr<FileSystem> filesystem, const BootModule& module)
{
ASSERT(is_ustar_boot_module(module));
auto root_inode = filesystem->root_inode();
uint8_t* temp_page = static_cast<uint8_t*>(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<char>(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<char>(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 {};
}
}

View File

@ -3,6 +3,7 @@
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/FS/ProcFS/FileSystem.h>
#include <kernel/FS/TmpFS/FileSystem.h>
#include <kernel/FS/USTARModule.h>
#include <kernel/FS/VirtualFileSystem.h>
#include <kernel/Lock/LockGuard.h>
#include <kernel/Storage/Partition.h>
@ -51,7 +52,31 @@ namespace Kernel
return BAN::RefPtr<BlockDevice>(static_cast<BlockDevice*>(device_inode.ptr()));
}
static BAN::RefPtr<BlockDevice> find_root_device(BAN::StringView root_path)
static BAN::RefPtr<FileSystem> 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<FileSystem>::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<FileSystem> 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<VirtualFileSystem>::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));