diff --git a/cleonos/c/apps/shell_main.c b/cleonos/c/apps/shell_main.c index 877cb27..f1afa0b 100644 --- a/cleonos/c/apps/shell_main.c +++ b/cleonos/c/apps/shell_main.c @@ -2,9 +2,24 @@ #include #define SHELL_CMD_MAX 24ULL -#define SHELL_ARG_MAX 128ULL -#define SHELL_LINE_MAX 256ULL +#define SHELL_ARG_MAX 160ULL +#define SHELL_LINE_MAX 320ULL #define SHELL_CAT_MAX 224ULL +#define SHELL_SCRIPT_MAX 1024ULL + +static u64 shell_strlen(const char *str) { + u64 len = 0ULL; + + if (str == (const char *)0) { + return 0ULL; + } + + while (str[len] != '\0') { + len++; + } + + return len; +} static int shell_streq(const char *left, const char *right) { u64 i = 0ULL; @@ -89,6 +104,35 @@ static void shell_log_hex_prefixed(const char *prefix, u64 value) { shell_log_text(line); } +static void shell_trim_line(char *line) { + u64 start = 0ULL; + u64 i = 0ULL; + u64 len; + + if (line == (char *)0) { + return; + } + + while (line[start] != '\0' && shell_is_space(line[start]) != 0) { + start++; + } + + if (start > 0ULL) { + while (line[start + i] != '\0') { + line[i] = line[start + i]; + i++; + } + line[i] = '\0'; + } + + len = shell_strlen(line); + + while (len > 0ULL && shell_is_space(line[len - 1ULL]) != 0) { + line[len - 1ULL] = '\0'; + len--; + } +} + static void shell_parse_line(const char *line, char *out_cmd, u64 cmd_size, char *out_arg, u64 arg_size) { u64 i = 0ULL; u64 cmd_pos = 0ULL; @@ -126,7 +170,7 @@ static void shell_parse_line(const char *line, char *out_cmd, u64 cmd_size, char } static void shell_cmd_help(void) { - shell_log_text("[USER][SHELL] commands: help ls cat run "); + shell_log_text("[USER][SHELL] commands: help ls cat run stats"); } static void shell_cmd_ls(const char *arg) { @@ -149,7 +193,7 @@ static void shell_cmd_ls(const char *arg) { shell_log_hex_prefixed("[USER][SHELL] ls count: ", count); for (i = 0ULL; i < count; i++) { - char name[96]; + char name[CLEONOS_FS_NAME_MAX]; name[0] = '\0'; @@ -204,15 +248,50 @@ static void shell_cmd_run(const char *arg) { } } +static void shell_cmd_stats(void) { + shell_log_hex_prefixed("[USER][SHELL] fs nodes: ", cleonos_sys_fs_node_count()); + shell_log_hex_prefixed("[USER][SHELL] task count: ", cleonos_sys_task_count()); + shell_log_hex_prefixed("[USER][SHELL] service count: ", cleonos_sys_service_count()); + shell_log_hex_prefixed("[USER][SHELL] service ready: ", cleonos_sys_service_ready_count()); + shell_log_hex_prefixed("[USER][SHELL] context switches: ", cleonos_sys_context_switches()); + shell_log_hex_prefixed("[USER][SHELL] kelf count: ", cleonos_sys_kelf_count()); + shell_log_hex_prefixed("[USER][SHELL] kelf runs: ", cleonos_sys_kelf_runs()); + shell_log_hex_prefixed("[USER][SHELL] user shell ready: ", cleonos_sys_user_shell_ready()); + shell_log_hex_prefixed("[USER][SHELL] user exec requested: ", cleonos_sys_user_exec_requested()); + shell_log_hex_prefixed("[USER][SHELL] user launch tries: ", cleonos_sys_user_launch_tries()); + shell_log_hex_prefixed("[USER][SHELL] user launch ok: ", cleonos_sys_user_launch_ok()); + shell_log_hex_prefixed("[USER][SHELL] user launch fail: ", cleonos_sys_user_launch_fail()); + shell_log_hex_prefixed("[USER][SHELL] exec requests: ", cleonos_sys_exec_request_count()); + shell_log_hex_prefixed("[USER][SHELL] exec success: ", cleonos_sys_exec_success_count()); +} + static void shell_execute_line(const char *line) { char cmd[SHELL_CMD_MAX]; char arg[SHELL_ARG_MAX]; + char line_buf[SHELL_LINE_MAX]; + u64 i = 0ULL; cmd[0] = '\0'; arg[0] = '\0'; - shell_log_prefixed("[USER][SHELL]$ ", line); - shell_parse_line(line, cmd, (u64)sizeof(cmd), arg, (u64)sizeof(arg)); + if (line == (const char *)0) { + return; + } + + while (line[i] != '\0' && i + 1ULL < (u64)sizeof(line_buf)) { + line_buf[i] = line[i]; + i++; + } + + line_buf[i] = '\0'; + shell_trim_line(line_buf); + + if (line_buf[0] == '\0' || line_buf[0] == '#') { + return; + } + + shell_log_prefixed("[USER][SHELL]$ ", line_buf); + shell_parse_line(line_buf, cmd, (u64)sizeof(cmd), arg, (u64)sizeof(arg)); if (shell_streq(cmd, "help") != 0) { shell_cmd_help(); @@ -234,12 +313,18 @@ static void shell_execute_line(const char *line) { return; } + if (shell_streq(cmd, "stats") != 0) { + shell_cmd_stats(); + return; + } + shell_log_prefixed("[USER][SHELL] unknown command: ", cmd); } -int cleonos_app_main(void) { +static void shell_run_default_script(void) { static const char *script[] = { "help", + "stats", "ls /", "ls /system", "cat /README.txt", @@ -248,15 +333,65 @@ int cleonos_app_main(void) { }; u64 i; - shell_log_text("[USER][SHELL] shell.elf command framework online"); - shell_log_hex_prefixed("[USER][SHELL] fs nodes: ", cleonos_sys_fs_node_count()); - shell_log_hex_prefixed("[USER][SHELL] task count: ", cleonos_sys_task_count()); - for (i = 0ULL; i < (u64)(sizeof(script) / sizeof(script[0])); i++) { shell_execute_line(script[i]); } +} - shell_log_hex_prefixed("[USER][SHELL] exec requests: ", cleonos_sys_exec_request_count()); - shell_log_hex_prefixed("[USER][SHELL] exec success: ", cleonos_sys_exec_success_count()); +static int shell_run_script_file(const char *path) { + char script[SHELL_SCRIPT_MAX + 1ULL]; + char line[SHELL_LINE_MAX]; + u64 got; + u64 i; + u64 line_pos = 0ULL; + + if (path == (const char *)0 || path[0] == '\0') { + return 0; + } + + got = cleonos_sys_fs_read(path, script, SHELL_SCRIPT_MAX); + + if (got == 0ULL) { + return 0; + } + + if (got > SHELL_SCRIPT_MAX) { + got = SHELL_SCRIPT_MAX; + } + + script[got] = '\0'; + shell_log_prefixed("[USER][SHELL] script ", path); + + for (i = 0ULL; i <= got; i++) { + char ch = script[i]; + + if (ch == '\r') { + continue; + } + + if (ch == '\n' || ch == '\0') { + line[line_pos] = '\0'; + shell_execute_line(line); + line_pos = 0ULL; + continue; + } + + if (line_pos + 1ULL < (u64)sizeof(line)) { + line[line_pos++] = ch; + } + } + + return 1; +} + +int cleonos_app_main(void) { + shell_log_text("[USER][SHELL] shell.elf command framework online"); + + if (shell_run_script_file("/shell/init.cmd") == 0) { + shell_log_text("[USER][SHELL] /shell/init.cmd missing, using default script"); + shell_run_default_script(); + } + + shell_log_text("[USER][SHELL] script done"); return 0; } diff --git a/cleonos/c/include/cleonos_syscall.h b/cleonos/c/include/cleonos_syscall.h index 1e3cb01..07698bb 100644 --- a/cleonos/c/include/cleonos_syscall.h +++ b/cleonos/c/include/cleonos_syscall.h @@ -32,6 +32,11 @@ u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); u64 cleonos_sys_log_write(const char *message, u64 length); u64 cleonos_sys_timer_ticks(void); u64 cleonos_sys_task_count(void); +u64 cleonos_sys_service_count(void); +u64 cleonos_sys_service_ready_count(void); +u64 cleonos_sys_context_switches(void); +u64 cleonos_sys_kelf_count(void); +u64 cleonos_sys_kelf_runs(void); u64 cleonos_sys_fs_node_count(void); u64 cleonos_sys_fs_child_count(const char *dir_path); u64 cleonos_sys_fs_get_child_name(const char *dir_path, u64 index, char *out_name); diff --git a/cleonos/c/src/syscall.c b/cleonos/c/src/syscall.c index e2ed35d..2e06950 100644 --- a/cleonos/c/src/syscall.c +++ b/cleonos/c/src/syscall.c @@ -25,6 +25,26 @@ u64 cleonos_sys_task_count(void) { return cleonos_syscall(CLEONOS_SYSCALL_TASK_COUNT, 0ULL, 0ULL, 0ULL); } +u64 cleonos_sys_service_count(void) { + return cleonos_syscall(CLEONOS_SYSCALL_SERVICE_COUNT, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_service_ready_count(void) { + return cleonos_syscall(CLEONOS_SYSCALL_SERVICE_READY_COUNT, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_context_switches(void) { + return cleonos_syscall(CLEONOS_SYSCALL_CONTEXT_SWITCHES, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_kelf_count(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KELF_COUNT, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_kelf_runs(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KELF_RUNS, 0ULL, 0ULL, 0ULL); +} + u64 cleonos_sys_fs_node_count(void) { return cleonos_syscall(CLEONOS_SYSCALL_FS_NODE_COUNT, 0ULL, 0ULL, 0ULL); } diff --git a/clks/kernel/kmain.c b/clks/kernel/kmain.c index 4479ce2..94af593 100644 --- a/clks/kernel/kmain.c +++ b/clks/kernel/kmain.c @@ -63,7 +63,7 @@ static void clks_task_usrd(u64 tick) { clks_userland_tick(tick); } -static void clks_stage12_syscall_probe(void) { +static void clks_stage13_syscall_probe(void) { char child_name[96]; char read_buf[160]; u64 root_children; @@ -162,7 +162,7 @@ void clks_kernel_main(void) { clks_tty_init(); } - clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS STAGE12 START"); + clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS STAGE13 START"); if (boot_fb == CLKS_NULL) { clks_log(CLKS_LOG_WARN, "VIDEO", "NO FRAMEBUFFER FROM LIMINE"); @@ -271,7 +271,7 @@ void clks_kernel_main(void) { syscall_ticks = clks_syscall_invoke_kernel(CLKS_SYSCALL_TIMER_TICKS, 0ULL, 0ULL, 0ULL); clks_log_hex(CLKS_LOG_INFO, "SYSCALL", "TICKS", syscall_ticks); - clks_stage12_syscall_probe(); + clks_stage13_syscall_probe(); clks_log(CLKS_LOG_INFO, "TTY", "VIRTUAL TTY0 READY"); clks_log(CLKS_LOG_DEBUG, "KERNEL", "IDLE LOOP ENTER"); @@ -279,3 +279,4 @@ void clks_kernel_main(void) { clks_cpu_halt_forever(); } + diff --git a/clks/kernel/userland.c b/clks/kernel/userland.c index d5df6ca..6f2a9fb 100644 --- a/clks/kernel/userland.c +++ b/clks/kernel/userland.c @@ -39,6 +39,21 @@ static clks_bool clks_userland_probe_elf(const char *path, const char *tag) { return CLKS_TRUE; } +static void clks_userland_probe_init_script(void) { + const void *data; + u64 size = 0ULL; + + data = clks_fs_read_all("/shell/init.cmd", &size); + + if (data == CLKS_NULL || size == 0ULL) { + clks_log(CLKS_LOG_WARN, "USER", "INIT SCRIPT NOT FOUND /SHELL/INIT.CMD"); + return; + } + + clks_log(CLKS_LOG_INFO, "USER", "INIT SCRIPT READY /SHELL/INIT.CMD"); + clks_log_hex(CLKS_LOG_INFO, "USER", "INIT_SCRIPT_SIZE", size); +} + static clks_bool clks_userland_request_shell_exec(void) { u64 status = (u64)-1; @@ -78,6 +93,7 @@ clks_bool clks_userland_init(void) { clks_user_shell_ready = CLKS_TRUE; clks_log(CLKS_LOG_INFO, "USER", "SHELL COMMAND ABI READY"); + clks_userland_probe_init_script(); if (clks_userland_probe_elf("/system/elfrunner.elf", "ELFRUNNER ELF READY") == CLKS_FALSE) { return CLKS_FALSE; diff --git a/docs/stage13.md b/docs/stage13.md new file mode 100644 index 0000000..6e91639 --- /dev/null +++ b/docs/stage13.md @@ -0,0 +1,39 @@ +# CLeonOS Stage13 + +## Stage Goal +- Upgrade shell command framework from hardcoded commands to script-driven execution. +- Add `/shell/init.cmd` startup script support with comment/blank-line handling. +- Extend user C syscall wrappers for runtime observability commands (`stats`). +- Keep Stage12 user execution manager stable while improving shell orchestration. + +## Acceptance Criteria +- Kernel boots and prints `CLEONOS STAGE13 START`. +- Userland logs include init script detection: + - `INIT SCRIPT READY /SHELL/INIT.CMD` and script size. +- Shell logs show script mode: + - `[USER][SHELL] script /shell/init.cmd` + - command lines executed from script (`$ help`, `$ stats`, etc.). +- `stats` command prints runtime counters via syscall wrappers. +- System remains stable in idle loop. + +## Build Targets +- `make setup` +- `make userapps` +- `make iso` +- `make run` +- `make debug` + +## QEMU Command +- `qemu-system-x86_64 -M q35 -m 1024M -cdrom build/CLeonOS-x86_64.iso -serial stdio` + +## Common Bugs and Debugging +- Shell falls back to default script: + - Check `ramdisk/shell/init.cmd` exists and is packed into ramdisk. +- `stats` values not updated: + - Confirm syscall IDs and wrappers remain aligned between kernel/user headers. +- Script command ignored unexpectedly: + - Verify command is not prefixed with `#` and does not exceed line buffer. +- `cat` output truncated: + - Current stage intentionally limits `cat` output to `SHELL_CAT_MAX` bytes. +- Boot regression after Stage13 merge: + - Re-check sequence: exec init -> userland init -> scheduler/services -> interrupts. diff --git a/ramdisk/shell/init.cmd b/ramdisk/shell/init.cmd new file mode 100644 index 0000000..da99fdd --- /dev/null +++ b/ramdisk/shell/init.cmd @@ -0,0 +1,12 @@ +# Stage13 shell init script for CLeonOS +# Lines starting with # are comments. + +help +stats +ls / +ls /shell +ls /system +cat /README.txt +run /system/elfrunner.elf +run /system/memc.elf +stats