/* * cmdline.c — kernel boot command-line parser * * Pure C99 / freestanding. No UEFI types, no libc. * The caller (uefi_loader.c) converts LoadOptions from UCS-2 to ASCII * before passing the string here. */ #include "starkernel/cmdline.h" #include "starkernel/kernel_args.h" /* ---- freestanding string helpers --------------------------------------- */ /** * @brief Test whether a character is ASCII whitespace. * * Returns non-zero for space (@c ' '), horizontal tab (@c '\t'), * carriage return (@c '\r'), and line feed (@c '\n'). Used by * @c cmdline_parse_ascii() to skip inter-token whitespace and by * @c load_cfg_from_esp() to strip trailing CR/LF from the config file. * Replaces the libc @c isspace() which is not available in a freestanding * build; this version does not consider locale. * * @param c Character to test. * @return Non-zero if @p c is ASCII whitespace, 0 otherwise. */ static int sf_isspace(char c) { return c == ' ' || c == '\t' || c == '\r' || c == '\n'; } /** * @brief Compare at most @p n characters of two strings (freestanding). * * Iterates up to @p n bytes, stopping at the first differing character * or when either string's null terminator is reached. Returns 0 if the * first @p n characters are equal (or both strings end before @p n), * positive if @p a is lexicographically greater, negative if @p b is. * Comparison is on @c unsigned @c char values per C standard convention. * * Used exclusively by @c cmdline_parse_ascii() and @c parse_log_level() * to match fixed-length flag prefixes without a libc dependency. * * @param a First string. * @param b Second string. * @param n Maximum number of characters to compare. * @return 0 if equal, positive if @p a > @p b, negative if @p a < @p b. */ static int sf_strncmp(const char *a, const char *b, unsigned int n) { while (n && *a && *a == *b) { a++; b++; n--; } if (!n) return 0; return (unsigned char)*a - (unsigned char)*b; } /** * @brief Copy a string into a fixed-size buffer with guaranteed NUL termination. * * Copies at most @p n-1 bytes from @p src into @p dst and always writes * a NUL terminator at @c dst[@p n-1] (if @p n > 0). This is the * BSD @c strlcpy() semantics without returning the source length. * If @p n is 0 the function is a no-op (no write, no UB). * * Used by @c cmdline_parse_ascii() to copy the raw command-line string * into @c KernelArgs.raw with a guaranteed NUL, avoiding the truncation * risk of @c strncpy(). * * @param dst Destination buffer; receives at most @p n-1 bytes of @p src * followed by a NUL terminator. * @param src NUL-terminated source string. * @param n Size of @p dst in bytes (including the NUL slot). */ static void sf_strlcpy(char *dst, const char *src, unsigned int n) { unsigned int i; for (i = 0; i + 1 < n && src[i]; i++) dst[i] = src[i]; if (n) dst[i] = '\0'; } /* ---- Size-suffix parser ------------------------------------------------ */ /** * @brief Parse a decimal integer with optional K/M/G size suffix. * * Reads a non-negative decimal integer from @p s and multiplies it by: * - 1024 for 'K' or 'k' (kibibytes) * - 1024² for 'M' or 'm' (mebibytes) * - 1024³ for 'G' or 'g' (gibibytes) * - 1 for any other or absent suffix * * Returns 0 if @p s is NULL, empty, or does not begin with a digit. * Overflow is not checked; caller must validate the resulting value * is sensible for its intended use (stack size, heap size, etc.). * * Used by @c cmdline_parse_ascii() to decode the @c --stack=@ and * @c --heap=@ arguments from the boot command line. * * @param s NUL-terminated string beginning with a decimal integer * and optionally followed by a size suffix character. * @return Parsed byte count (0 on parse failure or zero input). */ uint64_t cmdline_parse_size(const char *s) { uint64_t val = 0; if (!s || !(*s >= '0' && *s <= '9')) return 0; while (*s >= '0' && *s <= '9') { val = val * 10U + (uint64_t)(*s - '0'); s++; } if (*s == 'K' || *s == 'k') val *= 1024ULL; else if (*s == 'M' || *s == 'm') val *= 1024ULL * 1024ULL; else if (*s == 'G' || *s == 'g') val *= 1024ULL * 1024ULL * 1024ULL; return val; } /* ---- Log-level parser -------------------------------------------------- */ /** * @brief Map a log-level name string to a @c KARGS_LOG_* constant. * * Performs case-insensitive prefix matching against the known level names: * - "debug" / "DEBUG" → @c KARGS_LOG_DEBUG * - "info" / "INFO" → @c KARGS_LOG_INFO * - "warn" / "WARN" → @c KARGS_LOG_WARN * - "error" / "ERROR" → @c KARGS_LOG_ERROR * * Any unrecognised string (including empty) maps to @c KARGS_LOG_INFO * as a safe default. Called from @c cmdline_parse_ascii() after * recognising the @c --log-level= prefix. * * @param s String beginning at the first character of the level name * (i.e., after the @c "=" separator has been skipped by the caller). * @return One of the @c KARGS_LOG_* constants defined in @c kernel_args.h; * defaults to @c KARGS_LOG_INFO for unknown input. */ static int parse_log_level(const char *s) { if (sf_strncmp(s, "debug", 5) == 0 || sf_strncmp(s, "DEBUG", 5) == 0) return KARGS_LOG_DEBUG; if (sf_strncmp(s, "info", 4) == 0 || sf_strncmp(s, "INFO", 4) == 0) return KARGS_LOG_INFO; if (sf_strncmp(s, "warn", 4) == 0 || sf_strncmp(s, "WARN", 4) == 0) return KARGS_LOG_WARN; if (sf_strncmp(s, "error", 5) == 0 || sf_strncmp(s, "ERROR", 5) == 0) return KARGS_LOG_ERROR; return KARGS_LOG_INFO; /* unknown → info (safe default) */ } /* ---- Main parser ------------------------------------------------------- */ /** * @brief Parse a NUL-terminated ASCII command-line string into @c KernelArgs. * * Populates @p args from space-separated tokens in @p cmdline. Always * initialises all fields to their defaults first (so partial or empty * command lines produce safe values): * - @c stack_size = 0 (use BSS static stack) * - @c heap_size = 0 (use built-in kmalloc arena) * - @c run_doe = @c KARGS_DEFAULT_RUN_DOE * - @c log_level = @c KARGS_DEFAULT_LOG_LEVEL * - @c raw[0] = '\0' * * Recognised flags: * - @c --doe — sets @c args->run_doe = 1. * - @c --log-level=@ — sets @c args->log_level via * @c parse_log_level(). * - @c --stack=@ — sets @c args->stack_size via * @c cmdline_parse_size(). * - @c --heap=@ — sets @c args->heap_size via * @c cmdline_parse_size(). * * Unknown flags are silently ignored for forward-compatibility with * flags introduced in future kernel versions. The raw command-line string * is copied verbatim into @c args->raw (truncated to * @c KERNEL_ARGS_CMDLINE_MAX - 1) for diagnostic logging. * * Called from @c efi_main() (in @c uefi_loader.c) after assembling the * ASCII command line from the three priority sources: NVRAM variable, * @c starforth.cfg on the ESP, and EFI @c LoadOptions. * * @param cmdline NUL-terminated ASCII command line; may be NULL or empty. * @param args Output @c KernelArgs structure; must not be NULL. */ void cmdline_parse_ascii(const char *cmdline, KernelArgs *args) { const char *p; const char *tok; unsigned int tok_len; /* Fill defaults first */ args->stack_size = 0; args->heap_size = 0; args->run_doe = KARGS_DEFAULT_RUN_DOE; args->log_level = KARGS_DEFAULT_LOG_LEVEL; args->raw[0] = '\0'; if (!cmdline || !*cmdline) return; sf_strlcpy(args->raw, cmdline, KERNEL_ARGS_CMDLINE_MAX); p = cmdline; while (*p) { /* Skip leading whitespace */ while (*p && sf_isspace(*p)) p++; if (!*p) break; /* Measure token */ tok = p; while (*p && !sf_isspace(*p)) p++; tok_len = (unsigned int)(p - tok); /* --doe */ if (tok_len == 5 && sf_strncmp(tok, "--doe", 5) == 0) { args->run_doe = 1; /* --log-level= (prefix = 12 chars) */ } else if (tok_len > 12 && sf_strncmp(tok, "--log-level=", 12) == 0) { args->log_level = parse_log_level(tok + 12); /* --stack= (prefix = 8 chars) */ } else if (tok_len > 8 && sf_strncmp(tok, "--stack=", 8) == 0) { args->stack_size = cmdline_parse_size(tok + 8); /* --heap= (prefix = 7 chars) */ } else if (tok_len > 7 && sf_strncmp(tok, "--heap=", 7) == 0) { args->heap_size = cmdline_parse_size(tok + 7); } /* Unknown flags are silently ignored (forward-compatibility) */ } }