diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b7012f..3e7eb16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,12 +5,9 @@ project(CLeonOS NONE) set(NO_COLOR 0 CACHE STRING "Disable colored log output when set to 1") include("${CMAKE_SOURCE_DIR}/cmake/log.cmake") if(NO_COLOR) - set(BUILD_LOG_STEP_PREFIX "[STEP]") - set(BUILD_LOG_INFO_PREFIX "[INFO]") + set(BUILD_LOG_COLOR_OPT --no-color) else() - string(ASCII 27 BUILD_LOG_ESC) - set(BUILD_LOG_STEP_PREFIX "${BUILD_LOG_ESC}[1;35m[STEP]${BUILD_LOG_ESC}[0m") - set(BUILD_LOG_INFO_PREFIX "${BUILD_LOG_ESC}[1;36m[INFO]${BUILD_LOG_ESC}[0m") + set(BUILD_LOG_COLOR_OPT) endif() set(CC "x86_64-elf-gcc" CACHE STRING "Kernel C compiler") @@ -274,7 +271,6 @@ function(add_kernel_asm_object SRC OUTPUT_LIST_VAR) add_custom_command( OUTPUT "${OBJ_PATH}" COMMAND ${CMAKE_COMMAND} -E make_directory "${OBJ_DIR}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} assembling ${SRC}" COMMAND ${CC} ${ASFLAGS_COMMON} ${ARCH_CFLAGS} -c "${CMAKE_SOURCE_DIR}/${SRC}" -o "${OBJ_PATH}" DEPENDS "${CMAKE_SOURCE_DIR}/${SRC}" VERBATIM @@ -326,7 +322,6 @@ endforeach() add_custom_command( OUTPUT "${USER_RUST_LIB}" COMMAND ${CMAKE_COMMAND} -E make_directory "${USER_LIB_DIR}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} building rust user lib" COMMAND ${RUSTC} --crate-type staticlib -C panic=abort -O "${CMAKE_SOURCE_DIR}/cleonos/rust/src/lib.rs" -o "${USER_RUST_LIB}" DEPENDS "${CMAKE_SOURCE_DIR}/cleonos/rust/src/lib.rs" VERBATIM @@ -413,7 +408,7 @@ add_custom_target(userapps DEPENDS ${USER_APP_OUTPUTS}) add_custom_command( TARGET userapps POST_BUILD - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_INFO_PREFIX} user elf apps ready" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --cyan "[INFO] user elf apps ready" ) file(GLOB_RECURSE RAMDISK_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/ramdisk/*") @@ -434,7 +429,6 @@ endforeach() add_custom_command( OUTPUT "${RAMDISK_ROOT_STAMP}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} staging ramdisk root -> ${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E rm -rf "${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E make_directory "${RAMDISK_ROOT}" COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/ramdisk" "${RAMDISK_ROOT}" @@ -451,7 +445,6 @@ add_dependencies(ramdisk-root userapps) add_custom_command( OUTPUT "${RAMDISK_IMAGE}" COMMAND ${CMAKE_COMMAND} -E make_directory "${BUILD_ROOT}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} packing ramdisk -> ${RAMDISK_IMAGE}" COMMAND ${TAR} -C "${RAMDISK_ROOT}" -cf "${RAMDISK_IMAGE}" . DEPENDS "${RAMDISK_ROOT_STAMP}" VERBATIM @@ -500,11 +493,10 @@ add_custom_target(setup-limine add_dependencies(setup-limine setup-tools) add_custom_target(setup DEPENDS setup-tools setup-limine) -add_custom_command(TARGET setup POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_INFO_PREFIX} environment ready") +add_custom_command(TARGET setup POST_BUILD COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --cyan "[INFO] environment ready") add_custom_command( OUTPUT "${ISO_IMAGE}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} assembling iso root" COMMAND ${CMAKE_COMMAND} -E rm -rf "${ISO_ROOT}" COMMAND ${CMAKE_COMMAND} -E make_directory "${ISO_ROOT}/boot/limine" COMMAND ${CMAKE_COMMAND} -E copy "${KERNEL_ELF}" "${ISO_ROOT}/boot/clks_kernel.elf" @@ -514,7 +506,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy "${LIMINE_BIN_DIR_ABS}/limine-bios-cd.bin" "${ISO_ROOT}/boot/limine/limine-bios-cd.bin" COMMAND ${CMAKE_COMMAND} -E copy "${LIMINE_BIN_DIR_ABS}/limine-uefi-cd.bin" "${ISO_ROOT}/boot/limine/limine-uefi-cd.bin" COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_SOURCE_DIR}/build" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} building iso -> ${ISO_IMAGE}" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --magenta "[STEP] building iso -> ${ISO_IMAGE}" COMMAND ${XORRISO} -as mkisofs -b boot/limine/limine-bios-cd.bin -no-emul-boot @@ -526,9 +518,9 @@ add_custom_command( --protective-msdos-label "${ISO_ROOT}" -o "${ISO_IMAGE}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} installing limine boot sectors" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --magenta "[STEP] installing limine boot sectors" COMMAND "${LIMINE_BIN_DIR_ABS}/limine" bios-install "${ISO_IMAGE}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_INFO_PREFIX} iso ready: ${ISO_IMAGE}" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --cyan "[INFO] iso ready: ${ISO_IMAGE}" DEPENDS "${KERNEL_ELF}" "${RAMDISK_IMAGE}" "${CMAKE_SOURCE_DIR}/configs/limine.conf" VERBATIM ) @@ -537,29 +529,29 @@ add_custom_target(iso ALL DEPENDS "${ISO_IMAGE}") add_dependencies(iso setup-tools setup-limine kernel ramdisk) add_custom_target(run - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} launching qemu run" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --magenta "[STEP] launching qemu run" COMMAND ${QEMU_X86_64} -M q35 -m 1024M -cdrom "${ISO_IMAGE}" -serial stdio DEPENDS iso USES_TERMINAL ) add_custom_target(debug - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} launching qemu debug (-s -S)" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --magenta "[STEP] launching qemu debug (-s -S)" COMMAND ${QEMU_X86_64} -M q35 -m 1024M -cdrom "${ISO_IMAGE}" -serial stdio -s -S DEPENDS iso USES_TERMINAL ) add_custom_target(clean-x86 - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} cleaning ${BUILD_ROOT}" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --magenta "[STEP] cleaning ${BUILD_ROOT}" COMMAND ${CMAKE_COMMAND} -E rm -rf "${BUILD_ROOT}" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_INFO_PREFIX} clean done" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --cyan "[INFO] clean done" ) add_custom_target(clean-all - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_STEP_PREFIX} cleaning build" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --magenta "[STEP] cleaning build" COMMAND ${CMAKE_COMMAND} -E rm -rf "${CMAKE_SOURCE_DIR}/build" - COMMAND ${CMAKE_COMMAND} -E echo "${BUILD_LOG_INFO_PREFIX} clean-all done" + COMMAND ${CMAKE_COMMAND} -E cmake_echo_color ${BUILD_LOG_COLOR_OPT} --cyan "[INFO] clean-all done" ) add_custom_target(cleonos-help diff --git a/cleonos/c/apps/shell_main.c b/cleonos/c/apps/shell_main.c index f1afa0b..dc03a7d 100644 --- a/cleonos/c/apps/shell_main.c +++ b/cleonos/c/apps/shell_main.c @@ -1,11 +1,11 @@ -#include #include -#define SHELL_CMD_MAX 24ULL -#define SHELL_ARG_MAX 160ULL -#define SHELL_LINE_MAX 320ULL -#define SHELL_CAT_MAX 224ULL -#define SHELL_SCRIPT_MAX 1024ULL +#define SHELL_CMD_MAX 32ULL +#define SHELL_ARG_MAX 160ULL +#define SHELL_LINE_MAX 192ULL +#define SHELL_CAT_MAX 512ULL +#define SHELL_SCRIPT_MAX 1024ULL +#define SHELL_CLEAR_LINES 56ULL static u64 shell_strlen(const char *str) { u64 len = 0ULL; @@ -42,66 +42,52 @@ static int shell_is_space(char ch) { return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') ? 1 : 0; } -static void shell_append_char(char *dst, u64 *cursor, u64 dst_size, char ch) { - if (*cursor + 1ULL >= dst_size) { +static int shell_is_printable(char ch) { + return (ch >= 32 && ch <= 126) ? 1 : 0; +} + +static void shell_copy(char *dst, u64 dst_size, const char *src) { + u64 i = 0ULL; + + if (dst == (char *)0 || src == (const char *)0 || dst_size == 0ULL) { return; } - dst[*cursor] = ch; - (*cursor)++; + while (src[i] != '\0' && i + 1ULL < dst_size) { + dst[i] = src[i]; + i++; + } + + dst[i] = '\0'; } -static void shell_append_text(char *dst, u64 *cursor, u64 dst_size, const char *text) { - u64 i = 0ULL; +static void shell_write(const char *text) { + u64 len; if (text == (const char *)0) { return; } - while (text[i] != '\0') { - shell_append_char(dst, cursor, dst_size, text[i]); - i++; + len = shell_strlen(text); + + if (len == 0ULL) { + return; } + + (void)cleonos_sys_tty_write(text, len); } -static void shell_append_hex_u64(char *dst, u64 *cursor, u64 dst_size, u64 value) { - int nibble; - - shell_append_text(dst, cursor, dst_size, "0X"); - - for (nibble = 15; nibble >= 0; nibble--) { - u64 shift = (u64)nibble * 4ULL; - u64 current = (value >> shift) & 0x0FULL; - char out = (current < 10ULL) ? (char)('0' + (char)current) : (char)('A' + (char)(current - 10ULL)); - shell_append_char(dst, cursor, dst_size, out); - } +static void shell_write_char(char ch) { + (void)cleonos_sys_tty_write_char(ch); } -static void shell_log_text(const char *text) { - u64 len = cleonos_rust_guarded_len((const unsigned char *)text, (usize)511U); - cleonos_sys_log_write(text, len); +static void shell_writeln(const char *text) { + shell_write(text); + shell_write_char('\n'); } -static void shell_log_prefixed(const char *prefix, const char *value) { - char line[SHELL_LINE_MAX]; - u64 cursor = 0ULL; - - shell_append_text(line, &cursor, (u64)sizeof(line), prefix); - shell_append_text(line, &cursor, (u64)sizeof(line), value); - line[cursor] = '\0'; - - shell_log_text(line); -} - -static void shell_log_hex_prefixed(const char *prefix, u64 value) { - char line[SHELL_LINE_MAX]; - u64 cursor = 0ULL; - - shell_append_text(line, &cursor, (u64)sizeof(line), prefix); - shell_append_hex_u64(line, &cursor, (u64)sizeof(line), value); - line[cursor] = '\0'; - - shell_log_text(line); +static void shell_prompt(void) { + shell_write("cleonos(user)> "); } static void shell_trim_line(char *line) { @@ -142,6 +128,9 @@ static void shell_parse_line(const char *line, char *out_cmd, u64 cmd_size, char return; } + out_cmd[0] = '\0'; + out_arg[0] = '\0'; + while (line[i] != '\0' && shell_is_space(line[i]) != 0) { i++; } @@ -169,8 +158,98 @@ static void shell_parse_line(const char *line, char *out_cmd, u64 cmd_size, char out_arg[arg_pos] = '\0'; } +static int shell_has_suffix(const char *name, const char *suffix) { + u64 name_len; + u64 suffix_len; + u64 i; + + if (name == (const char *)0 || suffix == (const char *)0) { + return 0; + } + + name_len = shell_strlen(name); + suffix_len = shell_strlen(suffix); + + if (suffix_len > name_len) { + return 0; + } + + for (i = 0ULL; i < suffix_len; i++) { + if (name[name_len - suffix_len + i] != suffix[i]) { + return 0; + } + } + + return 1; +} + +static int shell_resolve_exec_path(const char *arg, char *out_path, u64 out_size) { + u64 cursor = 0ULL; + u64 i; + + if (arg == (const char *)0 || out_path == (char *)0 || out_size == 0ULL) { + return 0; + } + + if (arg[0] == '\0') { + return 0; + } + + out_path[0] = '\0'; + + if (arg[0] == '/') { + shell_copy(out_path, out_size, arg); + return 1; + } + + { + static const char prefix[] = "/shell/"; + u64 prefix_len = (u64)(sizeof(prefix) - 1U); + + if (prefix_len + 1ULL >= out_size) { + return 0; + } + + for (i = 0ULL; i < prefix_len; i++) { + out_path[cursor++] = prefix[i]; + } + } + + i = 0ULL; + while (arg[i] != '\0' && cursor + 1ULL < out_size) { + out_path[cursor++] = arg[i++]; + } + + out_path[cursor] = '\0'; + + if (arg[i] != '\0') { + return 0; + } + + if (shell_has_suffix(out_path, ".elf") == 0) { + static const char suffix[] = ".elf"; + + for (i = 0ULL; suffix[i] != '\0'; i++) { + if (cursor + 1ULL >= out_size) { + return 0; + } + out_path[cursor++] = suffix[i]; + } + + out_path[cursor] = '\0'; + } + + return 1; +} + static void shell_cmd_help(void) { - shell_log_text("[USER][SHELL] commands: help ls cat run stats"); + shell_writeln("commands:"); + shell_writeln(" help"); + shell_writeln(" ls [dir]"); + shell_writeln(" cat "); + shell_writeln(" exec "); + shell_writeln(" clear"); + shell_writeln(" stats"); } static void shell_cmd_ls(const char *arg) { @@ -185,12 +264,14 @@ static void shell_cmd_ls(const char *arg) { count = cleonos_sys_fs_child_count(path); if (count == (u64)-1) { - shell_log_text("[USER][SHELL] ls failed"); + shell_writeln("ls: directory not found"); return; } - shell_log_prefixed("[USER][SHELL] ls ", path); - shell_log_hex_prefixed("[USER][SHELL] ls count: ", count); + if (count == 0ULL) { + shell_writeln("(empty)"); + return; + } for (i = 0ULL; i < count; i++) { char name[CLEONOS_FS_NAME_MAX]; @@ -201,7 +282,7 @@ static void shell_cmd_ls(const char *arg) { continue; } - shell_log_prefixed("[USER][SHELL] - ", name); + shell_writeln(name); } } @@ -210,14 +291,14 @@ static void shell_cmd_cat(const char *arg) { u64 got; if (arg == (const char *)0 || arg[0] == '\0') { - shell_log_text("[USER][SHELL] cat requires path"); + shell_writeln("cat: file path required"); return; } got = cleonos_sys_fs_read(arg, cat_buf, SHELL_CAT_MAX); if (got == 0ULL) { - shell_log_text("[USER][SHELL] cat failed"); + shell_writeln("cat: file not found"); return; } @@ -226,43 +307,38 @@ static void shell_cmd_cat(const char *arg) { } cat_buf[got] = '\0'; - - shell_log_prefixed("[USER][SHELL] cat ", arg); - shell_log_text(cat_buf); + shell_writeln(cat_buf); } -static void shell_cmd_run(const char *arg) { +static void shell_cmd_exec(const char *arg) { + char path[SHELL_LINE_MAX]; u64 status; - if (arg == (const char *)0 || arg[0] == '\0') { - shell_log_text("[USER][SHELL] run requires path"); + if (shell_resolve_exec_path(arg, path, (u64)sizeof(path)) == 0) { + shell_writeln("exec: invalid target"); return; } - status = cleonos_sys_exec_path(arg); + status = cleonos_sys_exec_path(path); if (status == 0ULL) { - shell_log_prefixed("[USER][SHELL] run ok ", arg); + shell_writeln("exec: request accepted"); } else { - shell_log_prefixed("[USER][SHELL] run failed ", arg); + shell_writeln("exec: request failed"); + } +} + +static void shell_cmd_clear(void) { + u64 i; + + for (i = 0ULL; i < SHELL_CLEAR_LINES; i++) { + shell_write_char('\n'); } } 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()); + (void)cleonos_sys_task_count(); + shell_writeln("stats: use kernel log channel for full counters"); } static void shell_execute_line(const char *line) { @@ -271,9 +347,6 @@ static void shell_execute_line(const char *line) { char line_buf[SHELL_LINE_MAX]; u64 i = 0ULL; - cmd[0] = '\0'; - arg[0] = '\0'; - if (line == (const char *)0) { return; } @@ -290,7 +363,6 @@ static void shell_execute_line(const char *line) { 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) { @@ -308,8 +380,13 @@ static void shell_execute_line(const char *line) { return; } - if (shell_streq(cmd, "run") != 0) { - shell_cmd_run(arg); + if (shell_streq(cmd, "exec") != 0 || shell_streq(cmd, "run") != 0) { + shell_cmd_exec(arg); + return; + } + + if (shell_streq(cmd, "clear") != 0) { + shell_cmd_clear(); return; } @@ -318,24 +395,7 @@ static void shell_execute_line(const char *line) { return; } - shell_log_prefixed("[USER][SHELL] unknown command: ", cmd); -} - -static void shell_run_default_script(void) { - static const char *script[] = { - "help", - "stats", - "ls /", - "ls /system", - "cat /README.txt", - "run /system/elfrunner.elf", - "run /system/memc.elf" - }; - u64 i; - - for (i = 0ULL; i < (u64)(sizeof(script) / sizeof(script[0])); i++) { - shell_execute_line(script[i]); - } + shell_writeln("unknown command; type 'help'"); } static int shell_run_script_file(const char *path) { @@ -360,7 +420,6 @@ static int shell_run_script_file(const char *path) { } script[got] = '\0'; - shell_log_prefixed("[USER][SHELL] script ", path); for (i = 0ULL; i <= got; i++) { char ch = script[i]; @@ -384,14 +443,78 @@ static int shell_run_script_file(const char *path) { return 1; } -int cleonos_app_main(void) { - shell_log_text("[USER][SHELL] shell.elf command framework online"); +static char shell_read_char_blocking(void) { + for (;;) { + u64 ch = cleonos_sys_kbd_get_char(); - 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(); + if (ch != (u64)-1) { + return (char)(ch & 0xFFULL); + } + + __asm__ volatile("pause"); + } +} + +static void shell_read_line(char *out_line, u64 out_size) { + u64 cursor = 0ULL; + + if (out_line == (char *)0 || out_size == 0ULL) { + return; } - shell_log_text("[USER][SHELL] script done"); - return 0; + out_line[0] = '\0'; + + for (;;) { + char ch = shell_read_char_blocking(); + + if (ch == '\r') { + continue; + } + + if (ch == '\n') { + shell_write_char('\n'); + break; + } + + if (ch == '\b' || ch == 127) { + if (cursor > 0ULL) { + cursor--; + out_line[cursor] = '\0'; + shell_write_char('\b'); + } + continue; + } + + if (ch == '\t') { + ch = ' '; + } + + if (shell_is_printable(ch) == 0) { + continue; + } + + if (cursor + 1ULL >= out_size) { + continue; + } + + out_line[cursor++] = ch; + out_line[cursor] = '\0'; + shell_write_char(ch); + } } + +int cleonos_app_main(void) { + char line[SHELL_LINE_MAX]; + + shell_writeln("[USER][SHELL] interactive framework online"); + + if (shell_run_script_file("/shell/init.cmd") == 0) { + shell_writeln("[USER][SHELL] /shell/init.cmd missing"); + } + + for (;;) { + shell_prompt(); + shell_read_line(line, (u64)sizeof(line)); + shell_execute_line(line); + } +} \ No newline at end of file diff --git a/cleonos/c/include/cleonos_syscall.h b/cleonos/c/include/cleonos_syscall.h index 92c21af..469450f 100644 --- a/cleonos/c/include/cleonos_syscall.h +++ b/cleonos/c/include/cleonos_syscall.h @@ -30,6 +30,9 @@ typedef unsigned long long usize; #define CLEONOS_SYSCALL_TTY_COUNT 21ULL #define CLEONOS_SYSCALL_TTY_ACTIVE 22ULL #define CLEONOS_SYSCALL_TTY_SWITCH 23ULL +#define CLEONOS_SYSCALL_TTY_WRITE 24ULL +#define CLEONOS_SYSCALL_TTY_WRITE_CHAR 25ULL +#define CLEONOS_SYSCALL_KBD_GET_CHAR 26ULL u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); u64 cleonos_sys_log_write(const char *message, u64 length); @@ -55,6 +58,8 @@ u64 cleonos_sys_user_launch_fail(void); u64 cleonos_sys_tty_count(void); u64 cleonos_sys_tty_active(void); u64 cleonos_sys_tty_switch(u64 tty_index); +u64 cleonos_sys_tty_write(const char *text, u64 length); +u64 cleonos_sys_tty_write_char(char ch); +u64 cleonos_sys_kbd_get_char(void); -#endif - +#endif \ No newline at end of file diff --git a/cleonos/c/src/syscall.c b/cleonos/c/src/syscall.c index 791cc80..a0762d9 100644 --- a/cleonos/c/src/syscall.c +++ b/cleonos/c/src/syscall.c @@ -105,3 +105,14 @@ u64 cleonos_sys_tty_switch(u64 tty_index) { return cleonos_syscall(CLEONOS_SYSCALL_TTY_SWITCH, tty_index, 0ULL, 0ULL); } +u64 cleonos_sys_tty_write(const char *text, u64 length) { + return cleonos_syscall(CLEONOS_SYSCALL_TTY_WRITE, (u64)text, length, 0ULL); +} + +u64 cleonos_sys_tty_write_char(char ch) { + return cleonos_syscall(CLEONOS_SYSCALL_TTY_WRITE_CHAR, (u64)(unsigned char)ch, 0ULL, 0ULL); +} + +u64 cleonos_sys_kbd_get_char(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KBD_GET_CHAR, 0ULL, 0ULL, 0ULL); +} \ No newline at end of file diff --git a/clks/include/clks/keyboard.h b/clks/include/clks/keyboard.h index 8bba403..9a9e19b 100644 --- a/clks/include/clks/keyboard.h +++ b/clks/include/clks/keyboard.h @@ -6,5 +6,7 @@ void clks_keyboard_init(void); void clks_keyboard_handle_scancode(u8 scancode); u64 clks_keyboard_hotkey_switch_count(void); +clks_bool clks_keyboard_pop_char(char *out_ch); +u64 clks_keyboard_buffered_count(void); -#endif +#endif \ No newline at end of file diff --git a/clks/include/clks/shell.h b/clks/include/clks/shell.h new file mode 100644 index 0000000..cd445cb --- /dev/null +++ b/clks/include/clks/shell.h @@ -0,0 +1,9 @@ +#ifndef CLKS_SHELL_H +#define CLKS_SHELL_H + +#include + +void clks_shell_init(void); +void clks_shell_tick(u64 tick); + +#endif \ No newline at end of file diff --git a/clks/include/clks/syscall.h b/clks/include/clks/syscall.h index 478a042..8f91fca 100644 --- a/clks/include/clks/syscall.h +++ b/clks/include/clks/syscall.h @@ -27,10 +27,12 @@ #define CLKS_SYSCALL_TTY_COUNT 21ULL #define CLKS_SYSCALL_TTY_ACTIVE 22ULL #define CLKS_SYSCALL_TTY_SWITCH 23ULL +#define CLKS_SYSCALL_TTY_WRITE 24ULL +#define CLKS_SYSCALL_TTY_WRITE_CHAR 25ULL +#define CLKS_SYSCALL_KBD_GET_CHAR 26ULL void clks_syscall_init(void); u64 clks_syscall_dispatch(void *frame_ptr); u64 clks_syscall_invoke_kernel(u64 id, u64 arg0, u64 arg1, u64 arg2); -#endif - +#endif \ No newline at end of file diff --git a/clks/kernel/keyboard.c b/clks/kernel/keyboard.c index 0f2ca09..04f532c 100644 --- a/clks/kernel/keyboard.c +++ b/clks/kernel/keyboard.c @@ -3,35 +3,127 @@ #include #include -#define CLKS_SC_ALT 0x38U -#define CLKS_SC_F1 0x3BU -#define CLKS_SC_F2 0x3CU -#define CLKS_SC_F3 0x3DU -#define CLKS_SC_F4 0x3EU +#define CLKS_SC_ALT 0x38U +#define CLKS_SC_LSHIFT 0x2AU +#define CLKS_SC_RSHIFT 0x36U +#define CLKS_SC_F1 0x3BU +#define CLKS_SC_F2 0x3CU +#define CLKS_SC_F3 0x3DU +#define CLKS_SC_F4 0x3EU +#define CLKS_SC_EXT_PREFIX 0xE0U + +#define CLKS_KBD_INPUT_CAP 256U + +static const char clks_kbd_map[128] = { + [2] = '1', [3] = '2', [4] = '3', [5] = '4', [6] = '5', [7] = '6', [8] = '7', [9] = '8', + [10] = '9', [11] = '0', [12] = '-', [13] = '=', [14] = '\b', [15] = '\t', + [16] = 'q', [17] = 'w', [18] = 'e', [19] = 'r', [20] = 't', [21] = 'y', [22] = 'u', [23] = 'i', + [24] = 'o', [25] = 'p', [26] = '[', [27] = ']', [28] = '\n', + [30] = 'a', [31] = 's', [32] = 'd', [33] = 'f', [34] = 'g', [35] = 'h', [36] = 'j', [37] = 'k', + [38] = 'l', [39] = ';', [40] = '\'', [41] = '`', [43] = '\\', + [44] = 'z', [45] = 'x', [46] = 'c', [47] = 'v', [48] = 'b', [49] = 'n', [50] = 'm', + [51] = ',', [52] = '.', [53] = '/', [57] = ' ' +}; + +static const char clks_kbd_shift_map[128] = { + [2] = '!', [3] = '@', [4] = '#', [5] = '$', [6] = '%', [7] = '^', [8] = '&', [9] = '*', + [10] = '(', [11] = ')', [12] = '_', [13] = '+', [14] = '\b', [15] = '\t', + [16] = 'Q', [17] = 'W', [18] = 'E', [19] = 'R', [20] = 'T', [21] = 'Y', [22] = 'U', [23] = 'I', + [24] = 'O', [25] = 'P', [26] = '{', [27] = '}', [28] = '\n', + [30] = 'A', [31] = 'S', [32] = 'D', [33] = 'F', [34] = 'G', [35] = 'H', [36] = 'J', [37] = 'K', + [38] = 'L', [39] = ':', [40] = '"', [41] = '~', [43] = '|', + [44] = 'Z', [45] = 'X', [46] = 'C', [47] = 'V', [48] = 'B', [49] = 'N', [50] = 'M', + [51] = '<', [52] = '>', [53] = '?', [57] = ' ' +}; + +static char clks_kbd_input_queue[CLKS_KBD_INPUT_CAP]; +static u16 clks_kbd_input_head = 0U; +static u16 clks_kbd_input_tail = 0U; +static u16 clks_kbd_input_count = 0U; static clks_bool clks_kbd_alt_down = CLKS_FALSE; +static clks_bool clks_kbd_lshift_down = CLKS_FALSE; +static clks_bool clks_kbd_rshift_down = CLKS_FALSE; +static clks_bool clks_kbd_e0_prefix = CLKS_FALSE; static u64 clks_kbd_hotkey_switches = 0ULL; +static clks_bool clks_keyboard_queue_push(char ch) { + if (clks_kbd_input_count >= CLKS_KBD_INPUT_CAP) { + return CLKS_FALSE; + } + + clks_kbd_input_queue[clks_kbd_input_head] = ch; + clks_kbd_input_head = (u16)((clks_kbd_input_head + 1U) % CLKS_KBD_INPUT_CAP); + clks_kbd_input_count++; + return CLKS_TRUE; +} + +static char clks_keyboard_translate_scancode(u8 code) { + clks_bool shift_active = (clks_kbd_lshift_down == CLKS_TRUE || clks_kbd_rshift_down == CLKS_TRUE) + ? CLKS_TRUE + : CLKS_FALSE; + + if (shift_active == CLKS_TRUE) { + return clks_kbd_shift_map[code]; + } + + return clks_kbd_map[code]; +} + void clks_keyboard_init(void) { + clks_kbd_input_head = 0U; + clks_kbd_input_tail = 0U; + clks_kbd_input_count = 0U; clks_kbd_alt_down = CLKS_FALSE; + clks_kbd_lshift_down = CLKS_FALSE; + clks_kbd_rshift_down = CLKS_FALSE; + clks_kbd_e0_prefix = CLKS_FALSE; clks_kbd_hotkey_switches = 0ULL; + clks_log(CLKS_LOG_INFO, "KBD", "ALT+F1..F4 TTY HOTKEY ONLINE"); + clks_log(CLKS_LOG_INFO, "KBD", "PS2 INPUT QUEUE ONLINE"); } void clks_keyboard_handle_scancode(u8 scancode) { - clks_bool released = ((scancode & 0x80U) != 0U) ? CLKS_TRUE : CLKS_FALSE; - u8 code = (u8)(scancode & 0x7FU); + clks_bool released; + u8 code; + + if (scancode == CLKS_SC_EXT_PREFIX) { + clks_kbd_e0_prefix = CLKS_TRUE; + return; + } + + released = ((scancode & 0x80U) != 0U) ? CLKS_TRUE : CLKS_FALSE; + code = (u8)(scancode & 0x7FU); if (code == CLKS_SC_ALT) { clks_kbd_alt_down = (released == CLKS_FALSE) ? CLKS_TRUE : CLKS_FALSE; return; } - if (released == CLKS_TRUE || clks_kbd_alt_down == CLKS_FALSE) { + if (code == CLKS_SC_LSHIFT) { + clks_kbd_lshift_down = (released == CLKS_FALSE) ? CLKS_TRUE : CLKS_FALSE; return; } - if (code >= CLKS_SC_F1 && code <= CLKS_SC_F4) { + if (code == CLKS_SC_RSHIFT) { + clks_kbd_rshift_down = (released == CLKS_FALSE) ? CLKS_TRUE : CLKS_FALSE; + return; + } + + if (released == CLKS_TRUE) { + if (clks_kbd_e0_prefix == CLKS_TRUE) { + clks_kbd_e0_prefix = CLKS_FALSE; + } + return; + } + + if (clks_kbd_e0_prefix == CLKS_TRUE) { + clks_kbd_e0_prefix = CLKS_FALSE; + return; + } + + if (clks_kbd_alt_down == CLKS_TRUE && code >= CLKS_SC_F1 && code <= CLKS_SC_F4) { u32 target = (u32)(code - CLKS_SC_F1); u32 before = clks_tty_active(); u32 after; @@ -45,9 +137,34 @@ void clks_keyboard_handle_scancode(u8 scancode) { clks_log_hex(CLKS_LOG_INFO, "TTY", "ACTIVE", (u64)after); clks_log_hex(CLKS_LOG_INFO, "TTY", "HOTKEY_SWITCHES", clks_kbd_hotkey_switches); } + + return; + } + + { + char translated = clks_keyboard_translate_scancode(code); + + if (translated != '\0') { + (void)clks_keyboard_queue_push(translated); + } } } u64 clks_keyboard_hotkey_switch_count(void) { return clks_kbd_hotkey_switches; } + +clks_bool clks_keyboard_pop_char(char *out_ch) { + if (out_ch == CLKS_NULL || clks_kbd_input_count == 0U) { + return CLKS_FALSE; + } + + *out_ch = clks_kbd_input_queue[clks_kbd_input_tail]; + clks_kbd_input_tail = (u16)((clks_kbd_input_tail + 1U) % CLKS_KBD_INPUT_CAP); + clks_kbd_input_count--; + return CLKS_TRUE; +} + +u64 clks_keyboard_buffered_count(void) { + return (u64)clks_kbd_input_count; +} \ No newline at end of file diff --git a/clks/kernel/kmain.c b/clks/kernel/kmain.c index 20879da..4f6b565 100644 --- a/clks/kernel/kmain.c +++ b/clks/kernel/kmain.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -62,90 +63,9 @@ static void clks_task_kelfd(u64 tick) { static void clks_task_usrd(u64 tick) { clks_service_heartbeat(CLKS_SERVICE_USER, tick); clks_userland_tick(tick); + clks_shell_tick(tick); } -static void clks_stage15_syscall_probe(void) { - char child_name[96]; - char read_buf[160]; - u64 root_children; - u64 got_name; - u64 read_len; - u64 run_status; - - root_children = clks_syscall_invoke_kernel(CLKS_SYSCALL_FS_CHILD_COUNT, (u64)"/", 0ULL, 0ULL); - clks_log_hex(CLKS_LOG_INFO, "SHELL", "ROOT_CHILDREN", root_children); - - child_name[0] = '\0'; - got_name = clks_syscall_invoke_kernel(CLKS_SYSCALL_FS_GET_CHILD_NAME, (u64)"/", 0ULL, (u64)child_name); - - if (got_name == 1ULL) { - child_name[sizeof(child_name) - 1U] = '\0'; - clks_log(CLKS_LOG_INFO, "SHELL", "ROOT_ENTRY0"); - clks_log(CLKS_LOG_INFO, "SHELL", child_name); - } - - read_len = clks_syscall_invoke_kernel(CLKS_SYSCALL_FS_READ, - (u64)"/README.txt", - (u64)read_buf, - (u64)(sizeof(read_buf) - 1U)); - - if (read_len > 0ULL && read_len != (u64)-1) { - if (read_len >= (u64)sizeof(read_buf)) { - read_len = (u64)sizeof(read_buf) - 1ULL; - } - - read_buf[read_len] = '\0'; - clks_log(CLKS_LOG_INFO, "SHELL", "README PREVIEW"); - clks_log(CLKS_LOG_INFO, "SHELL", read_buf); - } - - run_status = clks_syscall_invoke_kernel(CLKS_SYSCALL_EXEC_PATH, (u64)"/system/elfrunner.elf", 0ULL, 0ULL); - - if (run_status == 0ULL) { - clks_log(CLKS_LOG_INFO, "EXEC", "RUN /SYSTEM/ELFRUNNER.ELF OK"); - } else { - clks_log(CLKS_LOG_WARN, "EXEC", "RUN /SYSTEM/ELFRUNNER.ELF FAILED"); - } - - clks_log_hex(CLKS_LOG_INFO, - "EXEC", - "REQUESTS", - clks_syscall_invoke_kernel(CLKS_SYSCALL_EXEC_REQUESTS, 0ULL, 0ULL, 0ULL)); - clks_log_hex(CLKS_LOG_INFO, - "EXEC", - "SUCCESS", - clks_syscall_invoke_kernel(CLKS_SYSCALL_EXEC_SUCCESS, 0ULL, 0ULL, 0ULL)); - - clks_log_hex(CLKS_LOG_INFO, - "USER", - "SHELL_READY", - clks_syscall_invoke_kernel(CLKS_SYSCALL_USER_SHELL_READY, 0ULL, 0ULL, 0ULL)); - clks_log_hex(CLKS_LOG_INFO, - "USER", - "EXEC_REQUESTED", - clks_syscall_invoke_kernel(CLKS_SYSCALL_USER_EXEC_REQUESTED, 0ULL, 0ULL, 0ULL)); - clks_log_hex(CLKS_LOG_INFO, - "USER", - "LAUNCH_TRIES", - clks_syscall_invoke_kernel(CLKS_SYSCALL_USER_LAUNCH_TRIES, 0ULL, 0ULL, 0ULL)); - clks_log_hex(CLKS_LOG_INFO, - "USER", - "LAUNCH_OK", - clks_syscall_invoke_kernel(CLKS_SYSCALL_USER_LAUNCH_OK, 0ULL, 0ULL, 0ULL)); - clks_log_hex(CLKS_LOG_INFO, - "USER", - "LAUNCH_FAIL", - clks_syscall_invoke_kernel(CLKS_SYSCALL_USER_LAUNCH_FAIL, 0ULL, 0ULL, 0ULL)); - - clks_log_hex(CLKS_LOG_INFO, - "TTY", - "COUNT", - clks_syscall_invoke_kernel(CLKS_SYSCALL_TTY_COUNT, 0ULL, 0ULL, 0ULL)); - clks_log_hex(CLKS_LOG_INFO, - "TTY", - "ACTIVE", - clks_syscall_invoke_kernel(CLKS_SYSCALL_TTY_ACTIVE, 0ULL, 0ULL, 0ULL)); -} void clks_kernel_main(void) { const struct limine_framebuffer *boot_fb; const struct limine_memmap_response *boot_memmap; @@ -171,7 +91,7 @@ void clks_kernel_main(void) { clks_tty_init(); } - clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS STAGE15 START"); + clks_log(CLKS_LOG_INFO, "BOOT", "CLEONOS STAGE16 START"); if (boot_fb == CLKS_NULL) { clks_log(CLKS_LOG_WARN, "VIDEO", "NO FRAMEBUFFER FROM LIMINE"); @@ -281,10 +201,12 @@ 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_stage15_syscall_probe(); + clks_shell_init(); + clks_log_hex(CLKS_LOG_INFO, "TTY", "COUNT", (u64)clks_tty_count()); + clks_log_hex(CLKS_LOG_INFO, "TTY", "ACTIVE", (u64)clks_tty_active()); clks_log(CLKS_LOG_INFO, "TTY", "VIRTUAL TTY0 READY"); clks_log(CLKS_LOG_DEBUG, "KERNEL", "IDLE LOOP ENTER"); clks_cpu_halt_forever(); -} +} \ No newline at end of file diff --git a/clks/kernel/shell.c b/clks/kernel/shell.c new file mode 100644 index 0000000..ce9febd --- /dev/null +++ b/clks/kernel/shell.c @@ -0,0 +1,441 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLKS_SHELL_LINE_MAX 192U +#define CLKS_SHELL_CMD_MAX 32U +#define CLKS_SHELL_ARG_MAX 160U +#define CLKS_SHELL_NAME_MAX 96U +#define CLKS_SHELL_PATH_MAX 192U +#define CLKS_SHELL_CAT_LIMIT 512U +#define CLKS_SHELL_INPUT_BUDGET 32U +#define CLKS_SHELL_CLEAR_LINES 56U + +static clks_bool clks_shell_ready = CLKS_FALSE; +static char clks_shell_line[CLKS_SHELL_LINE_MAX]; +static usize clks_shell_line_len = 0U; + +static clks_bool clks_shell_is_space(char ch) { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') ? CLKS_TRUE : CLKS_FALSE; +} + +static clks_bool clks_shell_is_printable(char ch) { + return (ch >= 32 && ch <= 126) ? CLKS_TRUE : CLKS_FALSE; +} + +static clks_bool clks_shell_streq(const char *left, const char *right) { + return (clks_strcmp(left, right) == 0) ? CLKS_TRUE : CLKS_FALSE; +} + +static void clks_shell_write(const char *text) { + if (text == CLKS_NULL) { + return; + } + + clks_tty_write(text); +} + +static void clks_shell_write_char(char ch) { + clks_tty_write_char(ch); +} + +static void clks_shell_writeln(const char *text) { + clks_shell_write(text); + clks_shell_write_char('\n'); +} + +static void clks_shell_prompt(void) { + clks_shell_write("cleonos> "); +} + +static void clks_shell_reset_line(void) { + clks_shell_line_len = 0U; + clks_shell_line[0] = '\0'; +} + +static void clks_shell_trim(char *line) { + usize start = 0U; + usize i = 0U; + usize len; + + if (line == CLKS_NULL) { + return; + } + + while (line[start] != '\0' && clks_shell_is_space(line[start]) == CLKS_TRUE) { + start++; + } + + if (start > 0U) { + while (line[start + i] != '\0') { + line[i] = line[start + i]; + i++; + } + + line[i] = '\0'; + } + + len = clks_strlen(line); + + while (len > 0U && clks_shell_is_space(line[len - 1U]) == CLKS_TRUE) { + line[len - 1U] = '\0'; + len--; + } +} + +static void clks_shell_split_line(const char *line, + char *out_cmd, + usize out_cmd_size, + char *out_arg, + usize out_arg_size) { + usize i = 0U; + usize cmd_pos = 0U; + usize arg_pos = 0U; + + if (line == CLKS_NULL || out_cmd == CLKS_NULL || out_arg == CLKS_NULL) { + return; + } + + out_cmd[0] = '\0'; + out_arg[0] = '\0'; + + while (line[i] != '\0' && clks_shell_is_space(line[i]) == CLKS_TRUE) { + i++; + } + + while (line[i] != '\0' && clks_shell_is_space(line[i]) == CLKS_FALSE) { + if (cmd_pos + 1U < out_cmd_size) { + out_cmd[cmd_pos++] = line[i]; + } + i++; + } + + out_cmd[cmd_pos] = '\0'; + + while (line[i] != '\0' && clks_shell_is_space(line[i]) == CLKS_TRUE) { + i++; + } + + while (line[i] != '\0') { + if (arg_pos + 1U < out_arg_size) { + out_arg[arg_pos++] = line[i]; + } + i++; + } + + out_arg[arg_pos] = '\0'; +} + +static clks_bool clks_shell_has_suffix(const char *name, const char *suffix) { + usize name_len; + usize suffix_len; + + if (name == CLKS_NULL || suffix == CLKS_NULL) { + return CLKS_FALSE; + } + + name_len = clks_strlen(name); + suffix_len = clks_strlen(suffix); + + if (suffix_len > name_len) { + return CLKS_FALSE; + } + + return (clks_strcmp(name + (name_len - suffix_len), suffix) == 0) ? CLKS_TRUE : CLKS_FALSE; +} + +static clks_bool clks_shell_resolve_exec_path(const char *arg, char *out_path, usize out_path_size) { + usize cursor = 0U; + usize i; + + if (arg == CLKS_NULL || out_path == CLKS_NULL || out_path_size == 0U) { + return CLKS_FALSE; + } + + if (arg[0] == '\0') { + return CLKS_FALSE; + } + + out_path[0] = '\0'; + + if (arg[0] == '/') { + usize len = clks_strlen(arg); + if (len + 1U > out_path_size) { + return CLKS_FALSE; + } + clks_memcpy(out_path, arg, len + 1U); + return CLKS_TRUE; + } + + { + static const char shell_prefix[] = "/shell/"; + usize prefix_len = (sizeof(shell_prefix) - 1U); + + if (prefix_len + 1U >= out_path_size) { + return CLKS_FALSE; + } + + clks_memcpy(out_path, shell_prefix, prefix_len); + cursor = prefix_len; + } + + for (i = 0U; arg[i] != '\0'; i++) { + if (cursor + 1U >= out_path_size) { + return CLKS_FALSE; + } + + out_path[cursor++] = arg[i]; + } + + out_path[cursor] = '\0'; + + if (clks_shell_has_suffix(out_path, ".elf") == CLKS_FALSE) { + static const char elf_suffix[] = ".elf"; + usize suffix_len = (sizeof(elf_suffix) - 1U); + + if (cursor + suffix_len + 1U > out_path_size) { + return CLKS_FALSE; + } + + clks_memcpy(out_path + cursor, elf_suffix, suffix_len + 1U); + } + + return CLKS_TRUE; +} + +static void clks_shell_cmd_help(void) { + clks_shell_writeln("commands:"); + clks_shell_writeln(" help"); + clks_shell_writeln(" ls [dir]"); + clks_shell_writeln(" cat "); + clks_shell_writeln(" exec "); + clks_shell_writeln(" clear"); +} + +static void clks_shell_cmd_ls(const char *arg) { + struct clks_fs_node_info info; + const char *path = arg; + u64 count; + u64 i; + + if (path == CLKS_NULL || path[0] == '\0') { + path = "/"; + } + + if (clks_fs_stat(path, &info) == CLKS_FALSE || info.type != CLKS_FS_NODE_DIR) { + clks_shell_writeln("ls: directory not found"); + return; + } + + count = clks_fs_count_children(path); + + if (count == 0ULL) { + clks_shell_writeln("(empty)"); + return; + } + + for (i = 0ULL; i < count; i++) { + char name[CLKS_SHELL_NAME_MAX]; + + name[0] = '\0'; + if (clks_fs_get_child_name(path, i, name, sizeof(name)) == CLKS_FALSE) { + continue; + } + + clks_shell_writeln(name); + } +} + +static void clks_shell_cmd_cat(const char *arg) { + const void *data; + u64 size = 0ULL; + u64 copy_len; + char out[CLKS_SHELL_CAT_LIMIT + 1U]; + + if (arg == CLKS_NULL || arg[0] == '\0') { + clks_shell_writeln("cat: file path required"); + return; + } + + data = clks_fs_read_all(arg, &size); + + if (data == CLKS_NULL) { + clks_shell_writeln("cat: file not found"); + return; + } + + if (size == 0ULL) { + clks_shell_writeln("(empty file)"); + return; + } + + copy_len = (size < CLKS_SHELL_CAT_LIMIT) ? size : CLKS_SHELL_CAT_LIMIT; + clks_memcpy(out, data, (usize)copy_len); + out[copy_len] = '\0'; + + clks_shell_writeln(out); + + if (size > copy_len) { + clks_shell_writeln("[cat] output truncated"); + } +} + +static void clks_shell_cmd_exec(const char *arg) { + char path[CLKS_SHELL_PATH_MAX]; + u64 status = (u64)-1; + + if (clks_shell_resolve_exec_path(arg, path, sizeof(path)) == CLKS_FALSE) { + clks_shell_writeln("exec: invalid target"); + return; + } + + if (clks_exec_run_path(path, &status) == CLKS_TRUE && status == 0ULL) { + clks_shell_writeln("exec: request accepted"); + } else { + clks_shell_writeln("exec: request failed"); + } +} + +static void clks_shell_cmd_clear(void) { + u32 i; + + for (i = 0U; i < CLKS_SHELL_CLEAR_LINES; i++) { + clks_shell_write_char('\n'); + } +} + +static void clks_shell_execute_line(const char *line) { + char line_buf[CLKS_SHELL_LINE_MAX]; + char cmd[CLKS_SHELL_CMD_MAX]; + char arg[CLKS_SHELL_ARG_MAX]; + usize i = 0U; + + if (line == CLKS_NULL) { + return; + } + + while (line[i] != '\0' && i + 1U < sizeof(line_buf)) { + line_buf[i] = line[i]; + i++; + } + + line_buf[i] = '\0'; + clks_shell_trim(line_buf); + + if (line_buf[0] == '\0') { + return; + } + + clks_shell_split_line(line_buf, cmd, sizeof(cmd), arg, sizeof(arg)); + + if (clks_shell_streq(cmd, "help") == CLKS_TRUE) { + clks_shell_cmd_help(); + return; + } + + if (clks_shell_streq(cmd, "ls") == CLKS_TRUE) { + clks_shell_cmd_ls(arg); + return; + } + + if (clks_shell_streq(cmd, "cat") == CLKS_TRUE) { + clks_shell_cmd_cat(arg); + return; + } + + if (clks_shell_streq(cmd, "exec") == CLKS_TRUE || clks_shell_streq(cmd, "run") == CLKS_TRUE) { + clks_shell_cmd_exec(arg); + return; + } + + if (clks_shell_streq(cmd, "clear") == CLKS_TRUE) { + clks_shell_cmd_clear(); + return; + } + + clks_shell_writeln("unknown command; type 'help'"); +} + +static void clks_shell_handle_char(char ch) { + if (ch == '\r') { + return; + } + + if (ch == '\n') { + clks_shell_write_char('\n'); + clks_shell_line[clks_shell_line_len] = '\0'; + clks_shell_execute_line(clks_shell_line); + clks_shell_reset_line(); + clks_shell_prompt(); + return; + } + + if (ch == '\b' || ch == 127) { + if (clks_shell_line_len > 0U) { + clks_shell_line_len--; + clks_shell_line[clks_shell_line_len] = '\0'; + clks_shell_write_char('\b'); + } + return; + } + + if (ch == '\t') { + ch = ' '; + } + + if (clks_shell_is_printable(ch) == CLKS_FALSE) { + return; + } + + if (clks_shell_line_len + 1U >= CLKS_SHELL_LINE_MAX) { + return; + } + + clks_shell_line[clks_shell_line_len++] = ch; + clks_shell_line[clks_shell_line_len] = '\0'; + clks_shell_write_char(ch); +} + +void clks_shell_init(void) { + clks_shell_reset_line(); + + if (clks_tty_ready() == CLKS_FALSE) { + clks_shell_ready = CLKS_FALSE; + clks_log(CLKS_LOG_WARN, "SHELL", "TTY NOT READY; SHELL DISABLED"); + return; + } + + clks_shell_ready = CLKS_TRUE; + + clks_shell_writeln(""); + clks_shell_writeln("CLeonOS interactive shell ready"); + clks_shell_writeln("type 'help' for commands"); + clks_shell_prompt(); + + clks_log(CLKS_LOG_INFO, "SHELL", "INTERACTIVE LOOP ONLINE"); +} + +void clks_shell_tick(u64 tick) { + u32 budget = 0U; + char ch; + + (void)tick; + + if (clks_shell_ready == CLKS_FALSE) { + return; + } + + while (budget < CLKS_SHELL_INPUT_BUDGET) { + if (clks_keyboard_pop_char(&ch) == CLKS_FALSE) { + break; + } + + clks_shell_handle_char(ch); + budget++; + } +} \ No newline at end of file diff --git a/clks/kernel/syscall.c b/clks/kernel/syscall.c index 582299a..87ece54 100644 --- a/clks/kernel/syscall.c +++ b/clks/kernel/syscall.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,7 @@ #define CLKS_SYSCALL_LOG_MAX_LEN 191U #define CLKS_SYSCALL_PATH_MAX 192U #define CLKS_SYSCALL_NAME_MAX 96U +#define CLKS_SYSCALL_IO_MAX_LEN 512U struct clks_syscall_frame { u64 rax; @@ -89,6 +91,44 @@ static u64 clks_syscall_log_write(u64 arg0, u64 arg1) { return len; } +static u64 clks_syscall_tty_write(u64 arg0, u64 arg1) { + const char *src = (const char *)arg0; + u64 len = arg1; + char buf[CLKS_SYSCALL_IO_MAX_LEN + 1U]; + u64 i; + + if (src == CLKS_NULL || len == 0ULL) { + return 0ULL; + } + + if (len > CLKS_SYSCALL_IO_MAX_LEN) { + len = CLKS_SYSCALL_IO_MAX_LEN; + } + + for (i = 0ULL; i < len; i++) { + buf[i] = src[i]; + } + + buf[len] = '\0'; + clks_tty_write(buf); + return len; +} + +static u64 clks_syscall_tty_write_char(u64 arg0) { + clks_tty_write_char((char)(arg0 & 0xFFULL)); + return 1ULL; +} + +static u64 clks_syscall_kbd_get_char(void) { + char ch; + + if (clks_keyboard_pop_char(&ch) == CLKS_FALSE) { + return (u64)-1; + } + + return (u64)(u8)ch; +} + static u64 clks_syscall_fs_child_count(u64 arg0) { char path[CLKS_SYSCALL_PATH_MAX]; @@ -228,6 +268,12 @@ u64 clks_syscall_dispatch(void *frame_ptr) { case CLKS_SYSCALL_TTY_SWITCH: clks_tty_switch((u32)frame->rbx); return (u64)clks_tty_active(); + case CLKS_SYSCALL_TTY_WRITE: + return clks_syscall_tty_write(frame->rbx, frame->rcx); + case CLKS_SYSCALL_TTY_WRITE_CHAR: + return clks_syscall_tty_write_char(frame->rbx); + case CLKS_SYSCALL_KBD_GET_CHAR: + return clks_syscall_kbd_get_char(); default: return (u64)-1; } @@ -244,5 +290,4 @@ u64 clks_syscall_invoke_kernel(u64 id, u64 arg0, u64 arg1, u64 arg2) { ); return ret; -} - +} \ No newline at end of file diff --git a/clks/kernel/tty.c b/clks/kernel/tty.c index 47bf056..8044366 100644 --- a/clks/kernel/tty.c +++ b/clks/kernel/tty.c @@ -141,6 +141,24 @@ void clks_tty_write_char(char ch) { return; } + if (ch == '\b') { + if (col == 0U && row == 0U) { + return; + } + + if (col == 0U) { + row--; + col = clks_tty_cols - 1U; + } else { + col--; + } + + clks_tty_put_visible(tty_index, row, col, ' '); + clks_tty_cursor_row[tty_index] = row; + clks_tty_cursor_col[tty_index] = col; + return; + } + if (ch == '\t') { clks_tty_write_char(' '); clks_tty_write_char(' ');