From: Tulio A M Mendes Date: Fri, 13 Feb 2026 09:15:09 +0000 (-0300) Subject: feat: Linux-like kernel command line parser with /proc/cmdline X-Git-Url: https://projects.tadryanom.me/docs/POSIX_ROADMAP.md?a=commitdiff_plain;h=91ac99ad5171ad84e6667cb0e993f7979f68d0f8;p=AdrOS.git feat: Linux-like kernel command line parser with /proc/cmdline Implement a proper kernel command line parsing system modeled after Linux's cmdline triaging: 1. Kernel params: recognized 'key=value' tokens (init=, root=, console=, loglevel=) are consumed by the kernel. 2. Kernel flags: recognized plain tokens (quiet, ring3, nokaslr, single, noapic, nosmp) are consumed by the kernel. 3. Init envp: unrecognized 'key=value' tokens become environment variables for the init process. 4. Init argv: unrecognized plain tokens (no '=' or '.') become command-line arguments for the init process. 5. '--' separator: everything after it goes to init untouched. 6. First token (kernel path) is always skipped. New files: - include/kernel/cmdline.h: API (cmdline_parse, cmdline_get, cmdline_has, cmdline_init_path, cmdline_init_argv/envp, cmdline_raw) - src/kernel/cmdline.c: implementation with static storage Changes: - init.c: calls cmdline_parse() early, uses cmdline_has('ring3') instead of the old cmdline_has_token() (removed) - arch_platform.c: uses cmdline_init_path() for init binary path (supports 'init=/path/to/init' from GRUB cmdline) - procfs.c: added /proc/cmdline file (readable by userspace) The 'ring3' parameter is no longer required for stable boot (the scheduler bug causing panics without it was fixed in the previous commit). It now only controls the inline ring3 test. Build: clean, cppcheck: clean, smoke: 19/19 pass --- diff --git a/include/kernel/cmdline.h b/include/kernel/cmdline.h new file mode 100644 index 0000000..5f7d537 --- /dev/null +++ b/include/kernel/cmdline.h @@ -0,0 +1,54 @@ +#ifndef KERNEL_CMDLINE_H +#define KERNEL_CMDLINE_H + +#include + +/* + * Linux-like kernel command line parser. + * + * The bootloader (GRUB) passes a command line string like: + * "/boot/adros-x86.bin init=/bin/init.elf quiet -- custom_arg" + * + * Parsing rules: + * 1. First token is the kernel path — skipped. + * 2. Recognized kernel params (e.g. "init=", "root=", "quiet") are + * consumed by the kernel. + * 3. The separator "--" marks the boundary: everything after it is + * forwarded to the init process untouched. + * 4. Before "--": unrecognized "key=value" tokens become init + * environment variables (envp[]). + * 5. Before "--": unrecognized plain tokens (no '=' or '.') become + * init command-line arguments (argv[]). + * 6. After "--": "key=value" → envp[], plain → argv[]. + */ + +#define CMDLINE_MAX 512 +#define CMDLINE_MAX_ARGS 16 +#define CMDLINE_MAX_ENVS 16 + +/* Call once during early init to parse the raw cmdline string. */ +void cmdline_parse(const char* raw); + +/* ---- Kernel parameter accessors ---- */ + +/* Return value of a "key=value" kernel param, or NULL if absent. */ +const char* cmdline_get(const char* key); + +/* Return 1 if a kernel flag (no value) is present. */ +int cmdline_has(const char* flag); + +/* Return the init binary path (from "init=" or default). */ +const char* cmdline_init_path(void); + +/* Return the full raw cmdline (for /proc/cmdline). */ +const char* cmdline_raw(void); + +/* ---- Init process argv / envp ---- */ + +/* Return NULL-terminated argv array for init. argc_out receives count. */ +const char* const* cmdline_init_argv(int* argc_out); + +/* Return NULL-terminated envp array for init. envc_out receives count. */ +const char* const* cmdline_init_envp(int* envc_out); + +#endif diff --git a/src/arch/x86/arch_platform.c b/src/arch/x86/arch_platform.c index 6775767..4798d89 100644 --- a/src/arch/x86/arch_platform.c +++ b/src/arch/x86/arch_platform.c @@ -15,6 +15,7 @@ #include "hal/cpu.h" #include "hal/usermode.h" +#include "kernel/cmdline.h" #if defined(__i386__) #include "arch/x86/acpi.h" @@ -45,7 +46,8 @@ static void userspace_init_thread(void) { uintptr_t user_sp = 0; uintptr_t user_as = 0; uintptr_t heap_brk = 0; - if (elf32_load_user_from_initrd("/bin/init.elf", &entry, &user_sp, &user_as, &heap_brk) != 0) { + const char* init_path = cmdline_init_path(); + if (elf32_load_user_from_initrd(init_path, &entry, &user_sp, &user_as, &heap_brk) != 0) { process_exit_notify(1); schedule(); for (;;) hal_cpu_idle(); diff --git a/src/kernel/cmdline.c b/src/kernel/cmdline.c new file mode 100644 index 0000000..43469ae --- /dev/null +++ b/src/kernel/cmdline.c @@ -0,0 +1,256 @@ +#include "kernel/cmdline.h" +#include "utils.h" +#include "console.h" + +#include + +/* ---- Static storage ---- */ + +static char raw_copy[CMDLINE_MAX]; + +/* Kernel-recognized "key=value" parameters */ +#define KPARAM_MAX 16 + +struct kparam { + const char* key; + const char* value; /* points into raw_copy */ +}; + +static struct kparam kparams[KPARAM_MAX]; +static int kparam_count; + +/* Kernel-recognized flags (no value) */ +#define KFLAG_MAX 16 + +static const char* kflags[KFLAG_MAX]; +static int kflag_count; + +/* Init argv / envp (pointers into raw_copy) */ +static const char* init_argv[CMDLINE_MAX_ARGS + 1]; /* +1 for NULL */ +static int init_argc; + +static const char* init_envp[CMDLINE_MAX_ENVS + 1]; +static int init_envc; + +static const char* init_path_val; + +/* ---- Tables of recognized kernel tokens ---- */ + +static const char* const known_kv_keys[] = { + "init", "root", "console", "loglevel", NULL +}; + +static const char* const known_flags[] = { + "quiet", "ring3", "nokaslr", "single", "noapic", "nosmp", NULL +}; + +static int is_known_kv_key(const char* key, size_t keylen) { + for (int i = 0; known_kv_keys[i]; i++) { + if (strlen(known_kv_keys[i]) == keylen && + memcmp(known_kv_keys[i], key, keylen) == 0) + return 1; + } + return 0; +} + +static int is_known_flag(const char* tok) { + for (int i = 0; known_flags[i]; i++) { + if (strcmp(known_flags[i], tok) == 0) + return 1; + } + return 0; +} + +/* ---- Helpers ---- */ + +static int has_char(const char* s, char c) { + while (*s) { if (*s == c) return 1; s++; } + return 0; +} + +/* ---- Parser ---- */ + +void cmdline_parse(const char* raw) { + kparam_count = 0; + kflag_count = 0; + init_argc = 0; + init_envc = 0; + init_path_val = "/bin/init.elf"; + + if (!raw) { + raw_copy[0] = '\0'; + init_argv[0] = NULL; + init_envp[0] = NULL; + return; + } + + /* Copy raw cmdline (preserve original for /proc/cmdline) */ + strncpy(raw_copy, raw, CMDLINE_MAX - 1); + raw_copy[CMDLINE_MAX - 1] = '\0'; + + /* We'll tokenize a second working copy in-place. + * We can reuse raw_copy since we split by replacing ' ' with '\0'. */ + char work[CMDLINE_MAX]; + strncpy(work, raw_copy, CMDLINE_MAX - 1); + work[CMDLINE_MAX - 1] = '\0'; + + char* tokens[128]; + int ntokens = 0; + + /* Tokenize by whitespace */ + char* p = work; + while (*p && ntokens < 128) { + while (*p == ' ' || *p == '\t') p++; + if (!*p) break; + tokens[ntokens++] = p; + while (*p && *p != ' ' && *p != '\t') p++; + if (*p) { *p = '\0'; p++; } + } + + if (ntokens == 0) { + init_argv[0] = NULL; + init_envp[0] = NULL; + return; + } + + /* Token 0 is the kernel path — skip it. + * Process remaining tokens. */ + int after_separator = 0; + + for (int i = 1; i < ntokens; i++) { + const char* tok = tokens[i]; + + /* Check for "--" separator */ + if (tok[0] == '-' && tok[1] == '-' && tok[2] == '\0') { + after_separator = 1; + continue; + } + + /* Find '=' if present */ + const char* eq = NULL; + for (const char* c = tok; *c; c++) { + if (*c == '=') { eq = c; break; } + } + + if (!after_separator) { + /* Before "--": kernel tries to claim the token */ + if (eq) { + /* "key=value" form */ + size_t keylen = (size_t)(eq - tok); + if (is_known_kv_key(tok, keylen)) { + /* Kernel param: store in raw_copy so pointers remain valid */ + size_t off = (size_t)(tok - work); + if (kparam_count < KPARAM_MAX) { + /* Point key/value into raw_copy */ + raw_copy[off + keylen] = '\0'; /* split at '=' */ + kparams[kparam_count].key = &raw_copy[off]; + kparams[kparam_count].value = &raw_copy[off + keylen + 1]; + kparam_count++; + } + } else { + /* Unrecognized key=value → init envp */ + if (init_envc < CMDLINE_MAX_ENVS) { + size_t off = (size_t)(tok - work); + init_envp[init_envc++] = &raw_copy[off]; + } + } + } else { + /* Plain token (no '=') */ + if (is_known_flag(tok)) { + if (kflag_count < KFLAG_MAX) { + size_t off = (size_t)(tok - work); + kflags[kflag_count++] = &raw_copy[off]; + } + } else if (!has_char(tok, '.')) { + /* No '.' and not recognized → init argv */ + if (init_argc < CMDLINE_MAX_ARGS) { + size_t off = (size_t)(tok - work); + init_argv[init_argc++] = &raw_copy[off]; + } + } + /* Tokens with '.' but no '=' (like module params) are + * silently ignored for now. */ + } + } else { + /* After "--": everything goes to init */ + if (eq) { + if (init_envc < CMDLINE_MAX_ENVS) { + size_t off = (size_t)(tok - work); + init_envp[init_envc++] = &raw_copy[off]; + } + } else { + if (init_argc < CMDLINE_MAX_ARGS) { + size_t off = (size_t)(tok - work); + init_argv[init_argc++] = &raw_copy[off]; + } + } + } + } + + init_argv[init_argc] = NULL; + init_envp[init_envc] = NULL; + + /* Extract init= value if present */ + const char* init_val = cmdline_get("init"); + if (init_val && init_val[0] != '\0') { + init_path_val = init_val; + } + + /* Log parsed results */ + kprintf("[CMDLINE] \"%s\"\n", raw_copy); + if (kparam_count > 0) { + for (int i = 0; i < kparam_count; i++) + kprintf("[CMDLINE] kernel: %s=%s\n", kparams[i].key, kparams[i].value); + } + if (kflag_count > 0) { + for (int i = 0; i < kflag_count; i++) + kprintf("[CMDLINE] flag: %s\n", kflags[i]); + } + if (init_argc > 0) { + for (int i = 0; i < init_argc; i++) + kprintf("[CMDLINE] init argv[%d]: %s\n", i, init_argv[i]); + } + if (init_envc > 0) { + for (int i = 0; i < init_envc; i++) + kprintf("[CMDLINE] init envp[%d]: %s\n", i, init_envp[i]); + } + kprintf("[CMDLINE] init path: %s\n", init_path_val); +} + +/* ---- Accessors ---- */ + +const char* cmdline_get(const char* key) { + if (!key) return NULL; + for (int i = 0; i < kparam_count; i++) { + if (strcmp(kparams[i].key, key) == 0) + return kparams[i].value; + } + return NULL; +} + +int cmdline_has(const char* flag) { + if (!flag) return 0; + for (int i = 0; i < kflag_count; i++) { + if (strcmp(kflags[i], flag) == 0) + return 1; + } + return 0; +} + +const char* cmdline_init_path(void) { + return init_path_val; +} + +const char* cmdline_raw(void) { + return raw_copy; +} + +const char* const* cmdline_init_argv(int* argc_out) { + if (argc_out) *argc_out = init_argc; + return init_argv; +} + +const char* const* cmdline_init_envp(int* envc_out) { + if (envc_out) *envc_out = init_envc; + return init_envp; +} diff --git a/src/kernel/init.c b/src/kernel/init.c index feeb6b8..306face 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -26,6 +26,7 @@ #include "hal/mm.h" #include "heap.h" #include "utils.h" +#include "kernel/cmdline.h" #include @@ -160,27 +161,10 @@ static void init_parse_fstab(void) { kfree(buf); } -static int cmdline_has_token(const char* cmdline, const char* token) { - if (!cmdline || !token) return 0; - - for (size_t i = 0; cmdline[i] != 0; i++) { - size_t j = 0; - while (token[j] != 0 && cmdline[i + j] == token[j]) { - j++; - } - if (token[j] == 0) { - char before = (i == 0) ? ' ' : cmdline[i - 1]; - char after = cmdline[i + j]; - int before_ok = (before == ' ' || before == '\t'); - int after_ok = (after == 0 || after == ' ' || after == '\t'); - if (before_ok && after_ok) return 1; - } - } - - return 0; -} - int init_start(const struct boot_info* bi) { + /* Parse kernel command line (Linux-like triaging) */ + cmdline_parse(bi ? bi->cmdline : NULL); + if (bi && bi->initrd_start) { uintptr_t initrd_virt = 0; if (hal_mm_map_physical_range((uintptr_t)bi->initrd_start, (uintptr_t)bi->initrd_end, @@ -246,7 +230,7 @@ int init_start(const struct boot_info* bi) { int user_ret = arch_platform_start_userspace(bi); - if (bi && cmdline_has_token(bi->cmdline, "ring3")) { + if (cmdline_has("ring3")) { arch_platform_usermode_test_start(); } diff --git a/src/kernel/procfs.c b/src/kernel/procfs.c index 744a949..a775c79 100644 --- a/src/kernel/procfs.c +++ b/src/kernel/procfs.c @@ -5,6 +5,7 @@ #include "heap.h" #include "pmm.h" #include "timer.h" +#include "kernel/cmdline.h" #include @@ -13,6 +14,7 @@ static fs_node_t g_proc_self; static fs_node_t g_proc_self_status; static fs_node_t g_proc_uptime; static fs_node_t g_proc_meminfo; +static fs_node_t g_proc_cmdline; #define PID_NODE_POOL 8 static fs_node_t g_pid_dir[PID_NODE_POOL]; @@ -84,6 +86,21 @@ static uint32_t proc_self_status_read(fs_node_t* node, uint32_t offset, uint32_t return size; } +static uint32_t proc_cmdline_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) { + (void)node; + const char* raw = cmdline_raw(); + uint32_t len = (uint32_t)strlen(raw); + char tmp[CMDLINE_MAX + 1]; + memcpy(tmp, raw, len); + tmp[len] = '\n'; + len++; + if (offset >= len) return 0; + uint32_t avail = len - offset; + if (size > avail) size = avail; + memcpy(buffer, tmp + offset, size); + return size; +} + static uint32_t proc_uptime_read(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) { (void)node; uint32_t ticks = get_tick_count(); @@ -327,6 +344,7 @@ static fs_node_t* proc_root_finddir(fs_node_t* node, const char* name) { if (strcmp(name, "self") == 0) return &g_proc_self; if (strcmp(name, "uptime") == 0) return &g_proc_uptime; if (strcmp(name, "meminfo") == 0) return &g_proc_meminfo; + if (strcmp(name, "cmdline") == 0) return &g_proc_cmdline; if (is_numeric(name)) return proc_get_pid_dir(parse_uint(name)); return NULL; } @@ -339,8 +357,8 @@ static int proc_root_readdir(fs_node_t* node, uint32_t* inout_index, void* buf, uint32_t idx = *inout_index; struct vfs_dirent* d = (struct vfs_dirent*)buf; - static const char* fixed[] = { "self", "uptime", "meminfo" }; - if (idx < 3) { + static const char* fixed[] = { "self", "uptime", "meminfo", "cmdline" }; + if (idx < 4) { d->d_ino = 200 + idx; d->d_type = (idx == 0) ? FS_DIRECTORY : FS_FILE; d->d_reclen = sizeof(struct vfs_dirent); @@ -353,7 +371,7 @@ static int proc_root_readdir(fs_node_t* node, uint32_t* inout_index, void* buf, } /* After fixed entries, list numeric PIDs */ - uint32_t pi = idx - 3; + uint32_t pi = idx - 4; uint32_t count = 0; if (ready_queue_head) { struct process* it = ready_queue_head; @@ -407,5 +425,10 @@ fs_node_t* procfs_create_root(void) { g_proc_meminfo.flags = FS_FILE; g_proc_meminfo.read = proc_meminfo_read; + memset(&g_proc_cmdline, 0, sizeof(g_proc_cmdline)); + strcpy(g_proc_cmdline.name, "cmdline"); + g_proc_cmdline.flags = FS_FILE; + g_proc_cmdline.read = proc_cmdline_read; + return &g_proc_root; }