Files
Operator-system/commands.c
2026-02-27 21:04:56 +00:00

662 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* commands.c Shell command registry and handler implementations.
*
* Each command is a { name, description, usage, handler } entry in the
* static `commands[]` table. execute_command() splits user input into
* command + arguments and dispatches to the matching handler.
*
* To add a new command:
* 1. Write a static handler cmd_foo(BootInfo *Boot, CHAR16 *Args)
* 2. Add a forward declaration above the table
* 3. Append an entry to commands[] (before the sentinel)
*/
#include "kernel_types.h"
#include "commands.h"
#include "string_utils.h"
#include "memory.h"
#include "task.h"
/* Null-safe print helper used throughout the kernel. */
#define SAFE_PRINT(Boot, ...) \
do { \
if ((Boot) != NULL && (Boot)->print != NULL) { \
(Boot)->print(__VA_ARGS__); \
} \
} while (0)
/* ================================================================
* Forward declarations
* ================================================================ */
static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args);
static void cmd_help(BootInfo *Boot, CHAR16 *Args);
static void cmd_man(BootInfo *Boot, CHAR16 *Args);
static void cmd_clear(BootInfo *Boot, CHAR16 *Args);
static void cmd_about(BootInfo *Boot, CHAR16 *Args);
static void cmd_mem(BootInfo *Boot, CHAR16 *Args);
static void cmd_ps(BootInfo *Boot, CHAR16 *Args);
static void cmd_spawn(BootInfo *Boot, CHAR16 *Args);
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args);
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args);
static void cmd_kusr(BootInfo *Boot, CHAR16 *Args);
/* Small helper struct used to pass arguments into per-command tasks. */
typedef struct {
BootInfo *Boot;
CommandHandlerFn handler;
CHAR16 args[128];
} CommandTaskContext;
static void command_task_entry(void *arg);
/* Local string copy helper (wide-char, bounded). */
static void wstrcpy16_local(CHAR16 *dst, const CHAR16 *src, UINTN max)
{
UINTN i = 0;
if (dst == NULL || max == 0) {
return;
}
while (src != NULL && src[i] != L'\0' && i < (max - 1)) {
dst[i] = src[i];
i++;
}
dst[i] = L'\0';
}
/* ================================================================
* Command registry
* ================================================================ */
static Command commands[] = {
{
L"shutdown",
L"Shutdown the system",
L"Usage: shutdown\n\r Initiates a system shutdown using UEFI runtime services.",
TASK_PRIV_KERNEL,
cmd_shutdown
},
{
L"help",
L"Display available commands",
L"Usage: help\n\r Lists all available commands with brief descriptions.",
TASK_PRIV_USER,
cmd_help
},
{
L"man",
L"Display manual page for a command",
L"Usage: man <command>\n\r Shows detailed help for the specified command.",
TASK_PRIV_USER,
cmd_man
},
{
L"clear",
L"Clear the screen",
L"Usage: clear\n\r Clears the console screen.",
TASK_PRIV_USER,
cmd_clear
},
{
L"about",
L"Display system information",
L"Usage: about\n\r Shows information about this operating system.",
TASK_PRIV_USER,
cmd_about
},
{
L"mem",
L"Display memory statistics",
L"Usage: mem\n\r Shows physical memory, heap, and paging information.",
TASK_PRIV_KERNEL,
cmd_mem
},
{
L"ps",
L"List running tasks",
L"Usage: ps\n\r Displays all active tasks with PID, state, and name.",
TASK_PRIV_DRIVER,
cmd_ps
},
{
L"spawn",
L"Spawn a demo background task",
L"Usage: spawn [name]\n\r Creates a cooperative demo task.\n\r Optional argument sets the task name.",
TASK_PRIV_DRIVER,
cmd_spawn
},
{
L"memtest",
L"Test memory allocation and deallocation",
L"Usage: memtest\n\r"
L" Run memory tests in four phases:\n\r"
L" 1) Heap allocation of 8 blocks (164096 bytes) via kmalloc()\n\r"
L" 2) Heap free and coalescing verification via kfree()\n\r"
L" 3) Single-page PMM allocate/free via pmm_alloc_page()/pmm_free_page()\n\r"
L" 4) Multi-page (4-page) PMM allocate/free via pmm_alloc_pages()/pmm_free_pages()\n\r",
TASK_PRIV_KERNEL,
cmd_memtest
},
{
L"tasktest",
L"Test task scheduler with multiple tasks",
L"Usage: tasktest\n\r"
L" Spawns three worker tasks (worker-A/B/C) that run cooperatively,\n\r"
L" each printing three progress steps and yielding between them.\n\r"
L" After the workers finish, prints the final task list to\n\r"
L" demonstrate the cooperative round-robin scheduler.",
TASK_PRIV_DRIVER,
cmd_tasktest
},
{
L"kusr",
L"Run a command with kernel privilege",
L"Usage: kusr <command> [args...]\n\r"
L" Temporarily elevates the current task to kernel privilege,\n\r"
L" executes the given command, then restores the original level.",
TASK_PRIV_USER,
cmd_kusr
},
{NULL, NULL, NULL, 0, NULL} /* sentinel */
};
/* ================================================================
* System control
* ================================================================ */
static void request_shutdown(BootInfo *Boot)
{
Task *caller;
if (Boot == NULL) {
return;
}
/* Subsystem-level privilege enforcement: shutdown requires KERNEL. */
caller = task_current();
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
SAFE_PRINT(Boot, L"Permission denied: shutdown requires kernel privilege.\n\r");
return;
}
if (Boot->shutdown != NULL) {
Boot->shutdown();
return;
}
SAFE_PRINT(Boot, L"Shutdown service unavailable.\n\r");
}
/* ================================================================
* Built-in command handlers
* ================================================================ */
static void cmd_shutdown(BootInfo *Boot, CHAR16 *Args)
{
(void)Args;
SAFE_PRINT(Boot, L"Shutting down...\n\r");
request_shutdown(Boot);
}
static void cmd_help(BootInfo *Boot, CHAR16 *Args)
{
(void)Args;
show_help(Boot);
}
static void cmd_man(BootInfo *Boot, CHAR16 *Args)
{
UINTN i = 0;
if (Args == NULL || Args[0] == L'\0') {
SAFE_PRINT(Boot, L"Usage: man <command>\n\r");
SAFE_PRINT(Boot, L"Try 'help' for a list of available commands.\n\r");
return;
}
trim_spaces_inplace(Args);
for (i = 0; commands[i].name != NULL; i++) {
if (ascii_streq_ci(Args, commands[i].name)) {
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"NAME\n\r");
SAFE_PRINT(Boot, L" %s - %s\n\r\n\r", commands[i].name, commands[i].description);
SAFE_PRINT(Boot, L"DESCRIPTION\n\r");
SAFE_PRINT(Boot, L" %s\n\r", commands[i].usage);
SAFE_PRINT(Boot, L"\n\r");
return;
}
}
SAFE_PRINT(Boot, L"No manual entry for '%s'\n\r", Args);
SAFE_PRINT(Boot, L"Try 'help' for a list of available commands.\n\r");
}
static void cmd_clear(BootInfo *Boot, CHAR16 *Args)
{
KSTATUS Status;
(void)Args;
if (Boot != NULL && Boot->clear_screen != NULL) {
Status = Boot->clear_screen();
if (Status != 0) {
SAFE_PRINT(Boot, L"Failed to clear screen (status=%ld)\n\r",
(UINT64)Status);
}
} else {
SAFE_PRINT(Boot, L"Clear screen function unavailable.\n\r");
}
}
static void cmd_about(BootInfo *Boot, CHAR16 *Args)
{
(void)Args;
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L" Simple 64-bit Operating System\n\r");
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"A minimal bootable operating system written in C.\n\r");
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Features:\n\r");
SAFE_PRINT(Boot, L" - Flexible bootloader interface\n\r");
SAFE_PRINT(Boot, L" - Console I/O\n\r");
SAFE_PRINT(Boot, L" - ELF64 Kernel Loader\n\r");
SAFE_PRINT(Boot, L" - Memory Management (PMM + Heap)\n\r");
SAFE_PRINT(Boot, L" - Cooperative Multitasking\n\r");
SAFE_PRINT(Boot, L" - Interactive Command Interface\n\r");
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Type 'help' for available commands.\n\r");
SAFE_PRINT(Boot, L"\n\r");
}
static void cmd_mem(BootInfo *Boot, CHAR16 *Args)
{
(void)Args;
memory_print_stats(Boot);
}
/* ----------------------------------------------------------------
* Demo task runs cooperatively, prints progress, then exits
* ---------------------------------------------------------------- */
static void demo_task_fn(void *arg)
{
BootInfo *Boot = (BootInfo *)arg;
Task *self = task_current();
UINTN i;
SAFE_PRINT(Boot, L"[%s:%d] started\n\r", self->name, self->pid);
for (i = 0; i < 5; i++) {
SAFE_PRINT(Boot, L"[%s:%d] tick %d/5\n\r",
self->name, self->pid, i + 1);
task_yield();
}
SAFE_PRINT(Boot, L"[%s:%d] finished\n\r", self->name, self->pid);
}
static void cmd_ps(BootInfo *Boot, CHAR16 *Args)
{
(void)Args;
task_print_list(Boot);
}
static void cmd_spawn(BootInfo *Boot, CHAR16 *Args)
{
Task *t;
const CHAR16 *name;
if (Args != NULL && Args[0] != L'\0') {
trim_spaces_inplace(Args);
name = Args;
} else {
name = L"demo";
}
t = task_create(name, demo_task_fn, Boot);
if (t == NULL) {
SAFE_PRINT(Boot, L"Failed to create task (out of slots or memory).\n\r");
return;
}
SAFE_PRINT(Boot, L"Created task '%s' (PID %d)\n\r", t->name, t->pid);
}
/* ----------------------------------------------------------------
* memtest exercise the heap and PMM allocators
* ---------------------------------------------------------------- */
static void cmd_memtest(BootInfo *Boot, CHAR16 *Args)
{
void *ptrs[8];
UINTN sizes[] = { 16, 64, 128, 256, 512, 1024, 2048, 4096 };
UINTN i;
UINT64 page;
UINTN h_total, h_used, h_free, h_blocks;
Task *caller;
(void)Args;
/* Subsystem-level privilege enforcement: memtest requires KERNEL. */
caller = task_current();
if (caller != NULL && task_get_privilege(caller) < TASK_PRIV_KERNEL) {
SAFE_PRINT(Boot, L"Permission denied: memtest requires kernel privilege.\n\r");
return;
}
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Memory Test\n\r");
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L"\n\r");
/* --- Heap allocation test --- */
SAFE_PRINT(Boot, L"[1] Heap allocation test\n\r");
for (i = 0; i < 8; i++) {
ptrs[i] = kmalloc(sizes[i]);
if (ptrs[i] != NULL) {
SAFE_PRINT(Boot, L" kmalloc(%4d) = 0x%lx OK\n\r",
sizes[i], (UINT64)(UINTN)ptrs[i]);
} else {
SAFE_PRINT(Boot, L" kmalloc(%4d) = NULL FAIL\n\r", sizes[i]);
}
}
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
SAFE_PRINT(Boot, L" Heap after alloc: used=%d free=%d blocks=%d\n\r",
h_used, h_free, h_blocks);
/* --- Heap free test --- */
SAFE_PRINT(Boot, L"\n\r[2] Heap free test\n\r");
for (i = 0; i < 8; i++) {
if (ptrs[i] != NULL) {
kfree(ptrs[i]);
SAFE_PRINT(Boot, L" kfree(0x%lx) OK\n\r",
(UINT64)(UINTN)ptrs[i]);
}
}
heap_get_stats(&h_total, &h_used, &h_free, &h_blocks);
SAFE_PRINT(Boot, L" Heap after free: used=%d free=%d blocks=%d\n\r",
h_used, h_free, h_blocks);
/* --- PMM page allocation test --- */
SAFE_PRINT(Boot, L"\n\r[3] PMM page allocation test\n\r");
SAFE_PRINT(Boot, L" Free pages before: %d\n\r", pmm_get_free_pages());
page = pmm_alloc_page();
if (page != 0) {
SAFE_PRINT(Boot, L" pmm_alloc_page() = 0x%lx OK\n\r", page);
SAFE_PRINT(Boot, L" Free pages after alloc: %d\n\r", pmm_get_free_pages());
pmm_free_page(page);
SAFE_PRINT(Boot, L" pmm_free_page() OK\n\r");
SAFE_PRINT(Boot, L" Free pages after free: %d\n\r", pmm_get_free_pages());
} else {
SAFE_PRINT(Boot, L" pmm_alloc_page() = 0 FAIL (out of pages)\n\r");
}
/* --- Multi-page allocation test --- */
SAFE_PRINT(Boot, L"\n\r[4] PMM multi-page allocation test (4 pages)\n\r");
page = pmm_alloc_pages(4);
if (page != 0) {
SAFE_PRINT(Boot, L" pmm_alloc_pages(4) = 0x%lx OK\n\r", page);
SAFE_PRINT(Boot, L" Free pages after alloc: %d\n\r", pmm_get_free_pages());
pmm_free_pages(page, 4);
SAFE_PRINT(Boot, L" pmm_free_pages() OK\n\r");
SAFE_PRINT(Boot, L" Free pages after free: %d\n\r", pmm_get_free_pages());
} else {
SAFE_PRINT(Boot, L" pmm_alloc_pages(4) = 0 FAIL\n\r");
}
SAFE_PRINT(Boot, L"\n\rAll memory tests completed.\n\r\n\r");
}
/* ----------------------------------------------------------------
* tasktest spawn multiple concurrent tasks to exercise scheduler
* ---------------------------------------------------------------- */
static void worker_task_fn(void *arg)
{
BootInfo *Boot = (BootInfo *)arg;
Task *self = task_current();
UINTN i;
for (i = 0; i < 3; i++) {
SAFE_PRINT(Boot, L" [%s:%d] working... step %d/3\n\r",
self->name, self->pid, i + 1);
task_yield();
}
SAFE_PRINT(Boot, L" [%s:%d] done\n\r", self->name, self->pid);
}
static void cmd_tasktest(BootInfo *Boot, CHAR16 *Args)
{
Task *t1, *t2, *t3;
UINTN i;
(void)Args;
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Task Scheduler Test\n\r");
SAFE_PRINT(Boot, L"================================================\n\r");
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Spawning 3 worker tasks...\n\r\n\r");
t1 = task_create(L"worker-A", worker_task_fn, Boot);
t2 = task_create(L"worker-B", worker_task_fn, Boot);
t3 = task_create(L"worker-C", worker_task_fn, Boot);
if (t1 == NULL || t2 == NULL || t3 == NULL) {
SAFE_PRINT(Boot, L"Failed to create one or more tasks.\n\r");
return;
}
SAFE_PRINT(Boot, L"Created: %s (PID %d), %s (PID %d), %s (PID %d)\n\r",
t1->name, t1->pid, t2->name, t2->pid, t3->name, t3->pid);
SAFE_PRINT(Boot, L"\n\rYielding to let workers run:\n\r\n\r");
/* Yield enough times for all workers to complete (3 tasks x 3 steps) */
for (i = 0; i < 12; i++) {
task_yield();
}
SAFE_PRINT(Boot, L"\n\rTask list after test:\n\r");
task_print_list(Boot);
SAFE_PRINT(Boot, L"Task scheduler test completed.\n\r\n\r");
}
/* ----------------------------------------------------------------
* kusr run a command with escalated privilege
* ---------------------------------------------------------------- */
static void cmd_kusr(BootInfo *Boot, CHAR16 *Args)
{
Task *self;
TaskPrivilege saved_priv;
if (Args == NULL || Args[0] == L'\0') {
SAFE_PRINT(Boot, L"Usage: kusr <command> [args...]\n\r");
return;
}
self = task_current();
if (self == NULL) {
SAFE_PRINT(Boot, L"kusr: no task context available.\n\r");
return;
}
/* Elevate, dispatch, restore. */
saved_priv = task_get_privilege(self);
task_set_privilege(self, TASK_PRIV_KERNEL);
{
Task *cmd_task = execute_command(Boot, Args, TASK_PRIV_KERNEL);
if (cmd_task != NULL) {
task_wait(cmd_task);
}
}
task_set_privilege(self, saved_priv);
}
/* ================================================================
* Public API
* ================================================================ */
/* Print a formatted table of all registered commands. */
void show_help(BootInfo *Boot)
{
UINTN i = 0;
UINTN max_len = 0;
UINTN name_len = 0;
UINTN padding = 0;
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"Available commands:\n\r");
SAFE_PRINT(Boot, L"\n\r");
/* Find the longest command name for column alignment */
for (i = 0; commands[i].name != NULL; i++) {
name_len = 0;
while (commands[i].name[name_len] != L'\0') {
name_len++;
}
if (name_len > max_len) {
max_len = name_len;
}
}
for (i = 0; commands[i].name != NULL; i++) {
SAFE_PRINT(Boot, L" %s", commands[i].name);
/* Pad to align descriptions */
name_len = 0;
while (commands[i].name[name_len] != L'\0') {
name_len++;
}
for (padding = 0; padding < (max_len - name_len + 2); padding++) {
SAFE_PRINT(Boot, L" ");
}
SAFE_PRINT(Boot, L"- %s\n\r", commands[i].description);
}
SAFE_PRINT(Boot, L"\n\r");
SAFE_PRINT(Boot, L"For detailed help on a command, type: man <command>\n\r");
SAFE_PRINT(Boot, L"\n\r");
}
/*
* Parse a line of user input into command + arguments and dispatch
* by spawning a dedicated task for the matching handler. Unknown
* commands print an error.
*
* Returns:
* - Pointer to the spawned Task for the command, or NULL if the
* command was not found or had to run synchronously.
*/
Task *execute_command(BootInfo *Boot, CHAR16 *Input, TaskPrivilege caller_priv)
{
CHAR16 *cmd_start = NULL;
CHAR16 *args_start = NULL;
UINTN i = 0;
CommandTaskContext *ctx;
Task *t;
if (Boot == NULL || Input == NULL) {
return NULL;
}
trim_spaces_inplace(Input);
if (Input[0] == L'\0') {
return NULL;
}
/* Split input into command and argument strings */
cmd_start = Input;
args_start = Input;
/* Advance past the command keyword */
while (*args_start != L'\0' && !is_space16(*args_start)) {
args_start++;
}
/* NUL-terminate the command and skip leading whitespace in args */
if (*args_start != L'\0') {
*args_start = L'\0';
args_start++;
/* skip leading whitespace in args */
while (*args_start != L'\0' && is_space16(*args_start)) {
args_start++;
}
}
/* Look up and dispatch the command */
for (i = 0; commands[i].name != NULL; i++) {
if (ascii_streq_ci(cmd_start, commands[i].name)) {
/* Allocate a context block for the command task. */
ctx = (CommandTaskContext *)kmalloc(sizeof(CommandTaskContext));
if (ctx == NULL) {
SAFE_PRINT(Boot, L"Failed to allocate command context; running in core thread.\n\r");
commands[i].handler(Boot, args_start);
return NULL;
}
ctx->Boot = Boot;
ctx->handler = commands[i].handler;
wstrcpy16_local(ctx->args, args_start, sizeof(ctx->args) / sizeof(ctx->args[0]));
t = task_create_with_priv(commands[i].name,
command_task_entry,
ctx,
caller_priv);
if (t == NULL) {
SAFE_PRINT(Boot, L"Failed to create task for command '%s'; running in core thread.\n\r",
commands[i].name);
kfree(ctx);
commands[i].handler(Boot, args_start);
return NULL;
}
SAFE_PRINT(Boot, L"[starling] spawned '%s' as PID %d\n\r", t->name, t->pid);
return t;
}
}
/* Command not found */
SAFE_PRINT(Boot, L"Unknown command: %s\n\r", cmd_start);
SAFE_PRINT(Boot, L"Type 'help' for a list of available commands.\n\r");
return NULL;
}
/* ----------------------------------------------------------------
* Command task entry executes one command in its own task context
* ---------------------------------------------------------------- */
static void command_task_entry(void *arg)
{
CommandTaskContext *ctx = (CommandTaskContext *)arg;
BootInfo *Boot = NULL;
if (ctx == NULL) {
return;
}
Boot = ctx->Boot;
if (ctx->handler != NULL) {
ctx->handler(Boot, ctx->args);
}
/* Context was heap-allocated in execute_command. */
kfree(ctx);
}