Files
StarForth/src/starkernel/boot/cmdline.c
T

225 lines
8.6 KiB
C

/*
* 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=@<size@> and
* @c --heap=@<size@> 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=@<level@> — sets @c args->log_level via
* @c parse_log_level().
* - @c --stack=@<size@> — sets @c args->stack_size via
* @c cmdline_parse_size().
* - @c --heap=@<size@> — 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=<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=<size> (prefix = 8 chars) */
} else if (tok_len > 8 && sf_strncmp(tok, "--stack=", 8) == 0) {
args->stack_size = cmdline_parse_size(tok + 8);
/* --heap=<size> (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) */
}
}