diff --git a/Makefile b/Makefile index 3c5de23..6ec10bd 100644 --- a/Makefile +++ b/Makefile @@ -166,6 +166,7 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) done @if [ -f README.md ]; then cp README.md $(BUILD_DIR)/initrd/; fi @if [ -f LICENSE ]; then cp LICENSE $(BUILD_DIR)/initrd/; fi + @if [ -f limine.conf ]; then cp limine.conf $(BUILD_DIR)/initrd/; fi cd $(BUILD_DIR)/initrd && COPYFILE_DISABLE=1 tar --exclude="._*" -cf ../initrd.tar * diff --git a/src/core/main.c b/src/core/main.c index 777dc40..1bbe5e3 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -19,6 +19,7 @@ #include "tar.h" #include "vfs.h" #include "core/kconsole.h" +#include "core/kutils.h" #include "memory_manager.h" #include "platform.h" #include "wallpaper.h" @@ -27,8 +28,10 @@ #include "lapic.h" #include "fs/sysfs.h" #include "fs/procfs.h" +#include "fs/bootfs.h" #include "sys/kernel_subsystem.h" #include "sys/module_manager.h" +#include "sys/bootfs_state.h" extern void sysfs_init_subsystems(void); @@ -61,12 +64,25 @@ static volatile struct limine_smp_request smp_request = { .flags = 0 }; +__attribute__((used, section(".requests"))) +static volatile struct limine_bootloader_info_request bootloader_info_request = { + .id = LIMINE_BOOTLOADER_INFO_REQUEST, + .revision = 0 +}; + +static volatile struct limine_kernel_file_request kernel_file_request = { + .id = LIMINE_KERNEL_FILE_REQUEST, + .revision = 0 +}; + __attribute__((used, section(".requests_start"))) static volatile struct limine_request *const requests_start_marker[] = { (struct limine_request *)&framebuffer_request, (struct limine_request *)&memmap_request, (struct limine_request *)&module_request, (struct limine_request *)&smp_request, + (struct limine_request *)&bootloader_info_request, + (struct limine_request *)&kernel_file_request, NULL }; @@ -258,6 +274,63 @@ void kmain(void) { sysfs_init_subsystems(); vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL); vfs_mount("/proc", "procfs", "procfs", procfs_get_ops(), NULL); + + // Initialize bootfs with default values + bootfs_init(); + + // Populate bootfs with real Limine bootloader information BEFORE mounting + if (bootloader_info_request.response != NULL) { + if (bootloader_info_request.response->name) { + k_strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name); + } + if (bootloader_info_request.response->version) { + k_strcpy(g_bootfs_state.bootloader_version, bootloader_info_request.response->version); + } + } + + // Get kernel size from kernel file request + if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) { + g_bootfs_state.kernel_size = kernel_file_request.response->kernel_file->size; + serial_write("[INIT] Kernel size from bootloader: "); + serial_write_hex(g_bootfs_state.kernel_size); + serial_write(" bytes\n"); + } + + // Set boot time to current ticks + extern uint32_t wm_get_ticks(void); + g_bootfs_state.boot_time_ms = wm_get_ticks(); + + // BEFORE mounting bootfs, capture initrd from Limine modules + if (module_request.response != NULL) { + g_bootfs_state.num_modules = module_request.response->module_count; + + serial_write("[INIT] Scanning modules for bootfs state...\n"); + // Scan modules to find initrd + for (uint64_t i = 0; i < module_request.response->module_count; i++) { + struct limine_file *mod = module_request.response->modules[i]; + const char *path = mod->path; + + if (fs_starts_with(path, "boot():")) path += 7; + else if (fs_starts_with(path, "boot:///")) path += 8; + + int path_len = 0; + while (path[path_len]) path_len++; + + serial_write("[INIT] Module: "); + serial_write(path); + serial_write(" ("); + serial_write_hex(mod->size); + serial_write(" bytes)\n"); + + if (path_len >= 5 && path[path_len-4] == '.' && path[path_len-3] == 't' && + path[path_len-2] == 'a' && path[path_len-1] == 'r') { + g_bootfs_state.initrd_size = mod->size; + serial_write("[INIT] -> Initrd detected\n"); + } + } + } + + vfs_mount("/boot", "bootfs", "bootfs", bootfs_get_ops(), NULL); if (module_request.response == NULL) { log_fail("Limine module response NULL"); @@ -329,6 +402,9 @@ void kmain(void) { wm_init(); asm volatile("sti"); + + extern void bootfs_refresh_from_disk(void); + bootfs_refresh_from_disk(); while (1) { wm_process_input(); diff --git a/src/fs/bootfs.c b/src/fs/bootfs.c new file mode 100644 index 0000000..ec4988a --- /dev/null +++ b/src/fs/bootfs.c @@ -0,0 +1,541 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. + +#include "bootfs.h" +#include "../sys/bootfs_state.h" +#include "vfs.h" +#include "core/kutils.h" +#include "core/platform.h" +#include "core/kconsole.h" +#include "memory_manager.h" + +extern void serial_write(const char *str); +extern void serial_write_hex(uint64_t value); + +typedef struct { + char path[512]; + int offset; + bool is_root; + bool is_metadata_dir; +} bootfs_handle_t; + +static void* bootfs_open(void *fs_private, const char *path, const char *mode); +static void bootfs_close(void *fs_private, void *handle); +static int bootfs_read(void *fs_private, void *handle, void *buf, int size); +static int bootfs_write(void *fs_private, void *handle, const void *buf, int size); +static int bootfs_seek(void *fs_private, void *handle, int offset, int whence); +static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max); +static bool bootfs_mkdir(void *fs_private, const char *rel_path); +static bool bootfs_rmdir(void *fs_private, const char *rel_path); +static bool bootfs_unlink(void *fs_private, const char *rel_path); +static bool bootfs_rename(void *fs_private, const char *old_path, const char *new_path); +static bool bootfs_exists(void *fs_private, const char *rel_path); +static bool bootfs_is_dir(void *fs_private, const char *rel_path); +static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t *info); +static uint32_t bootfs_get_position(void *file_handle); +static uint32_t bootfs_get_size(void *file_handle); + +static vfs_fs_ops_t bootfs_ops = { + .open = bootfs_open, + .close = bootfs_close, + .read = bootfs_read, + .write = bootfs_write, + .seek = bootfs_seek, + .readdir = bootfs_readdir, + .mkdir = bootfs_mkdir, + .rmdir = bootfs_rmdir, + .unlink = bootfs_unlink, + .rename = bootfs_rename, + .exists = bootfs_exists, + .is_dir = bootfs_is_dir, + .get_info = bootfs_get_info, + .get_position = bootfs_get_position, + .get_size = bootfs_get_size, +}; + +bootfs_state_t g_bootfs_state = {0}; + +static bool is_metadata_path(const char *path) { + if (!path) return false; + return k_strncmp(path, "metadata", 8) == 0; +} + +static bool is_metadata_file(const char *path) { + if (k_strcmp(path, "metadata/boot_time") == 0) return true; + if (k_strcmp(path, "metadata/boot_flags") == 0) return true; + if (k_strcmp(path, "metadata/version") == 0) return true; + return false; +} + +static void* bootfs_open(void *fs_private, const char *path, const char *mode) { + if (!path) path = ""; + if (path[0] == '/') path++; + + bootfs_handle_t *h = (bootfs_handle_t*)kmalloc(sizeof(bootfs_handle_t)); + if (!h) return NULL; + + k_memset(h, 0, sizeof(bootfs_handle_t)); + k_strcpy(h->path, path); + h->offset = 0; + + if (path[0] == '\0') { + h->is_root = true; + } else if (is_metadata_path(path) && path[8] == '\0') { + h->is_metadata_dir = true; + } + + return h; +} + +static void bootfs_close(void *fs_private, void *handle) { + if (handle) kfree(handle); +} + +static int generate_metadata_content(const char *file, char *buffer, int max_size) { + if (!buffer || max_size <= 0) return 0; + + buffer[0] = '\0'; + int len = 0; + + if (k_strcmp(file, "metadata/boot_time") == 0) { + extern uint32_t wm_get_ticks(void); + uint32_t ticks = wm_get_ticks(); + + k_strcpy(buffer, "Boot time: "); + char time_buf[32]; + k_itoa(g_bootfs_state.boot_time_ms, time_buf); + k_strcpy(buffer + k_strlen(buffer), time_buf); + k_strcpy(buffer + k_strlen(buffer), " ms\nTicks: "); + k_itoa(ticks, time_buf); + k_strcpy(buffer + k_strlen(buffer), time_buf); + k_strcpy(buffer + k_strlen(buffer), "\n"); + len = k_strlen(buffer); + } else if (k_strcmp(file, "metadata/version") == 0) { + k_strcpy(buffer, "Bootloader: "); + k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_name); + k_strcpy(buffer + k_strlen(buffer), "\nVersion: "); + k_strcpy(buffer + k_strlen(buffer), g_bootfs_state.bootloader_version); + k_strcpy(buffer + k_strlen(buffer), "\n"); + len = k_strlen(buffer); + } else if (k_strcmp(file, "metadata/boot_flags") == 0) { + k_strcpy(buffer, "Boot flags: 0x"); + char flags_buf[8]; + uint8_t flags = g_bootfs_state.boot_flags; + int hex_digit = (flags >> 4) & 0xF; + flags_buf[0] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10); + hex_digit = flags & 0xF; + flags_buf[1] = hex_digit < 10 ? '0' + hex_digit : 'a' + (hex_digit - 10); + flags_buf[2] = '\n'; + flags_buf[3] = '\0'; + k_strcpy(buffer + k_strlen(buffer), flags_buf); + len = k_strlen(buffer); + } + + return len; +} + +static int bootfs_read(void *fs_private, void *handle, void *buf, int size) { + bootfs_handle_t *h = (bootfs_handle_t*)handle; + if (!h || !buf || size <= 0) return -1; + + char *content_buffer = (char*)kmalloc(4096); + if (!content_buffer) return -1; + + int content_len = 0; + + if (k_strcmp(h->path, "limine.conf") == 0) { + k_memcpy(content_buffer, g_bootfs_state.limine_conf, + g_bootfs_state.limine_conf_len); + content_len = g_bootfs_state.limine_conf_len; + } else if (k_strcmp(h->path, "kernel") == 0) { + k_strcpy(content_buffer, "Kernel reference\nSize: "); + char size_buf[32]; + k_itoa(g_bootfs_state.kernel_size, size_buf); + k_strcpy(content_buffer + k_strlen(content_buffer), size_buf); + k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n"); + content_len = k_strlen(content_buffer); + } else if (k_strcmp(h->path, "initrd") == 0) { + k_strcpy(content_buffer, "Initial ramdisk reference\nSize: "); + char size_buf[32]; + k_itoa(g_bootfs_state.initrd_size, size_buf); + k_strcpy(content_buffer + k_strlen(content_buffer), size_buf); + k_strcpy(content_buffer + k_strlen(content_buffer), " bytes\n"); + content_len = k_strlen(content_buffer); + } else if (is_metadata_file(h->path)) { + content_len = generate_metadata_content(h->path, content_buffer, 4096); + } else { + kfree(content_buffer); + return -1; + } + + // Handle offset and reading + if (h->offset >= content_len) { + kfree(content_buffer); + return 0; + } + + int available = content_len - h->offset; + int read_size = (available < size) ? available : size; + + k_memcpy(buf, content_buffer + h->offset, read_size); + h->offset += read_size; + + kfree(content_buffer); + return read_size; +} + +static int bootfs_write(void *fs_private, void *handle, const void *buf, int size) { + bootfs_handle_t *h = (bootfs_handle_t*)handle; + if (!h || !buf || size <= 0) return -1; + + if (k_strcmp(h->path, "limine.conf") != 0) { + return -1; + } + + int max_write = 2048 - h->offset; + if (max_write <= 0) return -1; + + int write_size = (size < max_write) ? size : max_write; + k_memcpy(g_bootfs_state.limine_conf + h->offset, buf, write_size); + h->offset += write_size; + + if (h->offset > g_bootfs_state.limine_conf_len) { + g_bootfs_state.limine_conf_len = h->offset; + } + + extern vfs_file_t* vfs_open(const char *path, const char *mode); + extern int vfs_write(vfs_file_t *file, const void *buf, int size); + extern void vfs_close(vfs_file_t *file); + + vfs_file_t *fat_conf = vfs_open("/limine.conf", "w"); + if (fat_conf) { + vfs_write(fat_conf, g_bootfs_state.limine_conf, g_bootfs_state.limine_conf_len); + vfs_close(fat_conf); + } + + return write_size; +} + +static int bootfs_seek(void *fs_private, void *handle, int offset, int whence) { + bootfs_handle_t *h = (bootfs_handle_t*)handle; + if (!h) return -1; + + switch (whence) { + case 0: // SEEK_SET + h->offset = offset; + break; + case 1: // SEEK_CUR + h->offset += offset; + break; + case 2: // SEEK_END + return -1; + default: + return -1; + } + + return h->offset; +} + +static int bootfs_readdir(void *fs_private, const char *rel_path, vfs_dirent_t *entries, int max) { + if (!entries || max <= 0) return 0; + + if (!rel_path) rel_path = ""; + if (rel_path[0] == '/') rel_path++; + + int count = 0; + + if (rel_path[0] == '\0') { + if (count < max) { + k_strcpy(entries[count].name, "limine.conf"); + entries[count].size = g_bootfs_state.limine_conf_len; + entries[count].is_directory = 0; + count++; + } + + if (count < max) { + k_strcpy(entries[count].name, "kernel"); + entries[count].size = g_bootfs_state.kernel_size; + entries[count].is_directory = 0; + count++; + } + + if (count < max) { + k_strcpy(entries[count].name, "initrd"); + entries[count].size = g_bootfs_state.initrd_size; + entries[count].is_directory = 0; + count++; + } + + if (count < max) { + k_strcpy(entries[count].name, "metadata"); + entries[count].size = 0; + entries[count].is_directory = 1; + count++; + } + } + else if (k_strcmp(rel_path, "metadata") == 0) { + const char *meta_files[] = { + "boot_time", + "boot_flags", + "version" + }; + + for (int i = 0; i < 3 && count < max; i++) { + k_strcpy(entries[count].name, meta_files[i]); + entries[count].size = 0; + entries[count].is_directory = 0; + count++; + } + } + + return count; +} + +static bool bootfs_mkdir(void *fs_private, const char *rel_path) { + return false; +} + +static bool bootfs_rmdir(void *fs_private, const char *rel_path) { + if (!rel_path) rel_path = ""; + if (rel_path[0] == '/') rel_path++; + + if (k_strcmp(rel_path, "metadata") == 0) { + return false; /* metadata directory is protected */ + } + + return false; /* no other directories to remove */ +} + +static bool bootfs_unlink(void *fs_private, const char *rel_path) { + if (!rel_path) return false; + if (rel_path[0] == '/') rel_path++; + + /* Only limine.conf can be deleted */ + if (k_strcmp(rel_path, "limine.conf") != 0) { + return false; + } + + /* Clear the bootfs state */ + g_bootfs_state.limine_conf[0] = '\0'; + g_bootfs_state.limine_conf_len = 0; + + /* Delete from partition */ + extern bool vfs_delete(const char *path); + + bool result = vfs_delete("/limine.conf"); + + if (result) { + serial_write("[BOOTFS] Deleted limine.conf from partition\n"); + } else { + serial_write("[BOOTFS] Warning: Could not delete limine.conf from partition\n"); + } + + return result; +} + +static bool bootfs_rename(void *fs_private, const char *old_path, const char *new_path) { + if (!old_path || !new_path) return false; + + const char *old_rel = old_path; + const char *new_rel = new_path; + + if (old_rel[0] == '/') old_rel++; + if (new_rel[0] == '/') new_rel++; + + /* Only limine.conf can be renamed */ + if (k_strcmp(old_rel, "limine.conf") != 0) { + return false; + } + + /* kernel and initrd are protected */ + if (k_strcmp(new_rel, "kernel") == 0 || k_strcmp(new_rel, "initrd") == 0) { + return false; + } + + /* metadata directory is protected */ + if (k_strncmp(new_rel, "metadata", 8) == 0) { + return false; + } + + extern bool vfs_rename(const char *old_path, const char *new_path); + + char new_partition_path[256]; + k_strcpy(new_partition_path, "/"); + + /* Manually append new_rel to new_partition_path */ + int path_len = 0; + while (new_partition_path[path_len]) path_len++; + + int rel_len = 0; + while (new_rel[rel_len]) rel_len++; + + if (path_len + rel_len >= 256) { + serial_write("[BOOTFS] Error: new path too long\n"); + return false; + } + + k_memcpy(new_partition_path + path_len, new_rel, rel_len + 1); + + /* Rename on partition filesystem */ + bool result = vfs_rename("/limine.conf", new_partition_path); + + if (result) { + serial_write("[BOOTFS] Renamed limine.conf to "); + serial_write(new_rel); + serial_write("\n"); + } else { + serial_write("[BOOTFS] Warning: Could not rename limine.conf to "); + serial_write(new_rel); + serial_write("\n"); + } + + return result; +} + +static bool bootfs_exists(void *fs_private, const char *rel_path) { + if (!rel_path) rel_path = ""; + if (rel_path[0] == '/') rel_path++; + + if (rel_path[0] == '\0') return true; + + if (k_strcmp(rel_path, "limine.conf") == 0) return true; + if (k_strcmp(rel_path, "kernel") == 0) return true; + if (k_strcmp(rel_path, "initrd") == 0) return true; + + if (k_strcmp(rel_path, "metadata") == 0) return true; + if (is_metadata_file(rel_path)) return true; + + return false; +} + +static bool bootfs_is_dir(void *fs_private, const char *rel_path) { + if (!rel_path) rel_path = ""; + if (rel_path[0] == '/') rel_path++; + + if (rel_path[0] == '\0') return true; + if (k_strcmp(rel_path, "metadata") == 0) return true; + + return false; +} + +static int bootfs_get_info(void *fs_private, const char *rel_path, vfs_dirent_t *info) { + if (!info) return -1; + if (!rel_path) rel_path = ""; + if (rel_path[0] == '/') rel_path++; + + k_memset(info, 0, sizeof(vfs_dirent_t)); + + if (rel_path[0] == '\0') { + k_strcpy(info->name, "/"); + info->is_directory = 1; + return 0; + } + + if (k_strcmp(rel_path, "limine.conf") == 0) { + k_strcpy(info->name, "limine.conf"); + info->size = g_bootfs_state.limine_conf_len; + info->is_directory = 0; + return 0; + } + + if (k_strcmp(rel_path, "kernel") == 0) { + k_strcpy(info->name, "kernel"); + info->size = g_bootfs_state.kernel_size; + info->is_directory = 0; + return 0; + } + + if (k_strcmp(rel_path, "initrd") == 0) { + k_strcpy(info->name, "initrd"); + info->size = g_bootfs_state.initrd_size; + info->is_directory = 0; + return 0; + } + + if (k_strcmp(rel_path, "metadata") == 0) { + k_strcpy(info->name, "metadata"); + info->is_directory = 1; + return 0; + } + + if (is_metadata_file(rel_path)) { + char temp_buf[4096]; + int len = generate_metadata_content(rel_path, temp_buf, 4096); + k_strcpy(info->name, rel_path + 9); + info->size = len; + info->is_directory = 0; + return 0; + } + + return -1; +} + +static uint32_t bootfs_get_position(void *file_handle) { + bootfs_handle_t *h = (bootfs_handle_t*)file_handle; + if (!h) return 0; + return h->offset; +} + +static uint32_t bootfs_get_size(void *file_handle) { + bootfs_handle_t *h = (bootfs_handle_t*)file_handle; + if (!h) return 0; + + if (k_strcmp(h->path, "limine.conf") == 0) { + return g_bootfs_state.limine_conf_len; + } else if (k_strcmp(h->path, "kernel") == 0) { + return g_bootfs_state.kernel_size; + } else if (k_strcmp(h->path, "initrd") == 0) { + return g_bootfs_state.initrd_size; + } else if (is_metadata_file(h->path)) { + char temp_buf[4096]; + return generate_metadata_content(h->path, temp_buf, 4096); + } + + return 0; +} + +vfs_fs_ops_t* bootfs_get_ops(void) { + return &bootfs_ops; +} + +void bootfs_state_init(void) { + k_memset(&g_bootfs_state, 0, sizeof(bootfs_state_t)); + + k_strcpy(g_bootfs_state.bootloader_name, "Limine"); + k_strcpy(g_bootfs_state.bootloader_version, "6.0.0"); + + + g_bootfs_state.limine_conf[0] = '\0'; + g_bootfs_state.limine_conf_len = 0; + + g_bootfs_state.kernel_size = 0; + g_bootfs_state.initrd_size = 0; + g_bootfs_state.boot_time_ms = 0; +} + +void bootfs_init(void) { + bootfs_state_init(); +} + +void bootfs_refresh_from_disk(void) { + extern vfs_file_t* vfs_open(const char *path, const char *mode); + extern int vfs_read(vfs_file_t *file, void *buf, int size); + extern void vfs_close(vfs_file_t *file); + + vfs_file_t *boot_conf = vfs_open("/limine.conf", "r"); + if (boot_conf) { + int bytes_read = vfs_read(boot_conf, g_bootfs_state.limine_conf, 2047); + if (bytes_read > 0) { + g_bootfs_state.limine_conf[bytes_read] = '\0'; + g_bootfs_state.limine_conf_len = bytes_read; + serial_write("[BOOTFS] Loaded limine.conf from partition: "); + extern void serial_write_hex(uint64_t value); + serial_write_hex(bytes_read); + serial_write(" bytes\n"); + } + vfs_close(boot_conf); + } else { + serial_write("[BOOTFS] Warning: /limine.conf not found on partition\n"); + } +} diff --git a/src/fs/bootfs.h b/src/fs/bootfs.h new file mode 100644 index 0000000..2ea71a8 --- /dev/null +++ b/src/fs/bootfs.h @@ -0,0 +1,13 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +#ifndef BOOTFS_H +#define BOOTFS_H + +#include "vfs.h" + +void bootfs_init(void); +void bootfs_refresh_from_disk(void); +vfs_fs_ops_t* bootfs_get_ops(void); + +#endif diff --git a/src/sys/bootfs_state.h b/src/sys/bootfs_state.h new file mode 100644 index 0000000..93cbd3d --- /dev/null +++ b/src/sys/bootfs_state.h @@ -0,0 +1,24 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +#ifndef BOOTFS_STATE_H +#define BOOTFS_STATE_H + +#include +#include + +typedef struct { + char bootloader_name[64]; + char bootloader_version[64]; + uint64_t boot_time_ms; + uint8_t boot_flags; + char limine_conf[2048]; + int limine_conf_len; + uint32_t num_modules; + uint32_t kernel_size; + uint32_t initrd_size; + +} bootfs_state_t; +extern bootfs_state_t g_bootfs_state; +void bootfs_state_init(void); + +#endif