diff --git a/CMakeLists.txt b/CMakeLists.txt index 686aee9..355513c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -364,12 +364,34 @@ foreach(SRC IN LISTS USER_APP_MAIN_SOURCES) list(APPEND USER_APP_NAMES "${_app_name}") add_user_c_object("${SRC}" _user_obj) + + set(_app_specific_objs) + file(GLOB _app_specific_abs CONFIGURE_DEPENDS + "${CMAKE_SOURCE_DIR}/cleonos/c/apps/${_app_name}_*.c" + "${CMAKE_SOURCE_DIR}/cleonos/c/apps/${_app_name}/*.c" + ) + foreach(_extra_abs IN LISTS _app_specific_abs) + file(RELATIVE_PATH _extra_rel "${CMAKE_SOURCE_DIR}" "${_extra_abs}") + + if(_extra_rel STREQUAL "${SRC}") + continue() + endif() + + if(_extra_rel MATCHES "_kmain\\.c$") + continue() + endif() + + add_user_c_object("${_extra_rel}" _extra_obj) + list(APPEND _app_specific_objs "${_extra_obj}") + endforeach() + list(REMOVE_DUPLICATES _app_specific_objs) + set(_app_out "${USER_APP_DIR}/${_app_name}.elf") add_custom_command( OUTPUT "${_app_out}" COMMAND ${CMAKE_COMMAND} -E make_directory "${USER_APP_DIR}" - COMMAND ${USER_LD} ${USER_LDFLAGS} -o "${_app_out}" ${USER_COMMON_OBJECTS} "${_user_obj}" "${USER_RUST_LIB}" - DEPENDS ${USER_COMMON_OBJECTS} "${_user_obj}" "${USER_RUST_LIB}" "${USER_LINKER_SCRIPT}" + COMMAND ${USER_LD} ${USER_LDFLAGS} -o "${_app_out}" ${USER_COMMON_OBJECTS} "${_user_obj}" ${_app_specific_objs} "${USER_RUST_LIB}" + DEPENDS ${USER_COMMON_OBJECTS} "${_user_obj}" ${_app_specific_objs} "${USER_RUST_LIB}" "${USER_LINKER_SCRIPT}" VERBATIM ) diff --git a/cleonos/c/apps/shell/shell_cmd.c b/cleonos/c/apps/shell/shell_cmd.c new file mode 100644 index 0000000..e4ebb2a --- /dev/null +++ b/cleonos/c/apps/shell/shell_cmd.c @@ -0,0 +1,800 @@ +#include "shell_internal.h" + +#define USH_DMESG_DEFAULT 64ULL +#define USH_DMESG_LINE_MAX 256ULL +#define USH_COPY_MAX 65536U + +static int ush_path_is_under_temp(const char *path) { + if (path == (const char *)0) { + return 0; + } + + if (path[0] != '/' || path[1] != 't' || path[2] != 'e' || path[3] != 'm' || path[4] != 'p') { + return 0; + } + + return (path[5] == '\0' || path[5] == '/') ? 1 : 0; +} + +static int ush_split_first_and_rest(const char *arg, char *out_first, u64 out_first_size, const char **out_rest) { + u64 i = 0ULL; + u64 p = 0ULL; + + if (arg == (const char *)0 || out_first == (char *)0 || out_first_size == 0ULL || out_rest == (const char **)0) { + return 0; + } + + out_first[0] = '\0'; + *out_rest = ""; + + while (arg[i] != '\0' && ush_is_space(arg[i]) != 0) { + i++; + } + + if (arg[i] == '\0') { + return 0; + } + + while (arg[i] != '\0' && ush_is_space(arg[i]) == 0) { + if (p + 1ULL < out_first_size) { + out_first[p++] = arg[i]; + } + i++; + } + + out_first[p] = '\0'; + + while (arg[i] != '\0' && ush_is_space(arg[i]) != 0) { + i++; + } + + *out_rest = &arg[i]; + return 1; +} + +static int ush_split_two_args(const char *arg, + char *out_first, + u64 out_first_size, + char *out_second, + u64 out_second_size) { + u64 i = 0ULL; + u64 p = 0ULL; + + if (arg == (const char *)0 || + out_first == (char *)0 || out_first_size == 0ULL || + out_second == (char *)0 || out_second_size == 0ULL) { + return 0; + } + + out_first[0] = '\0'; + out_second[0] = '\0'; + + while (arg[i] != '\0' && ush_is_space(arg[i]) != 0) { + i++; + } + + if (arg[i] == '\0') { + return 0; + } + + while (arg[i] != '\0' && ush_is_space(arg[i]) == 0) { + if (p + 1ULL < out_first_size) { + out_first[p++] = arg[i]; + } + i++; + } + + out_first[p] = '\0'; + + while (arg[i] != '\0' && ush_is_space(arg[i]) != 0) { + i++; + } + + if (arg[i] == '\0') { + return 0; + } + + p = 0ULL; + while (arg[i] != '\0' && ush_is_space(arg[i]) == 0) { + if (p + 1ULL < out_second_size) { + out_second[p++] = arg[i]; + } + i++; + } + + out_second[p] = '\0'; + + return (out_first[0] != '\0' && out_second[0] != '\0') ? 1 : 0; +} + +static int ush_cmd_help(void) { + ush_writeln("commands:"); + ush_writeln(" help"); + ush_writeln(" ls [dir]"); + ush_writeln(" cat "); + ush_writeln(" pwd"); + ush_writeln(" cd [dir]"); + ush_writeln(" exec|run "); + ush_writeln(" clear"); + ush_writeln(" memstat / fsstat / taskstat / userstat / shstat / stats"); + ush_writeln(" tty [index]"); + ush_writeln(" dmesg [n]"); + ush_writeln(" kbdstat"); + ush_writeln(" mkdir (/temp only)"); + ush_writeln(" touch (/temp only)"); + ush_writeln(" write (/temp only)"); + ush_writeln(" append (/temp only)"); + ush_writeln(" cp (dst /temp only)"); + ush_writeln(" mv (/temp only)"); + ush_writeln(" rm (/temp only)"); + ush_writeln(" rusttest / panic / elfloader (kernel shell only)"); + ush_writeln("edit keys: Left/Right, Home/End, Up/Down history"); + return 1; +} + +static int ush_cmd_ls(const ush_state *sh, const char *arg) { + const char *target = arg; + char path[USH_PATH_MAX]; + u64 count; + u64 i; + + if (target == (const char *)0 || target[0] == '\0') { + target = "."; + } + + if (ush_resolve_path(sh, target, path, (u64)sizeof(path)) == 0) { + ush_writeln("ls: invalid path"); + return 0; + } + + if (cleonos_sys_fs_stat_type(path) != 2ULL) { + ush_writeln("ls: directory not found"); + return 0; + } + + count = cleonos_sys_fs_child_count(path); + + if (count == 0ULL) { + ush_writeln("(empty)"); + return 1; + } + + for (i = 0ULL; i < count; i++) { + char name[CLEONOS_FS_NAME_MAX]; + name[0] = '\0'; + + if (cleonos_sys_fs_get_child_name(path, i, name) != 0ULL) { + ush_writeln(name); + } + } + + return 1; +} + +static int ush_cmd_cat(const ush_state *sh, const char *arg) { + char path[USH_PATH_MAX]; + char buf[USH_CAT_MAX + 1ULL]; + u64 size; + u64 req; + u64 got; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("cat: file path required"); + return 0; + } + + if (ush_resolve_path(sh, arg, path, (u64)sizeof(path)) == 0) { + ush_writeln("cat: invalid path"); + return 0; + } + + if (cleonos_sys_fs_stat_type(path) != 1ULL) { + ush_writeln("cat: file not found"); + return 0; + } + + size = cleonos_sys_fs_stat_size(path); + + if (size == (u64)-1) { + ush_writeln("cat: failed to stat file"); + return 0; + } + + if (size == 0ULL) { + return 1; + } + + req = (size < USH_CAT_MAX) ? size : USH_CAT_MAX; + got = cleonos_sys_fs_read(path, buf, req); + + if (got == 0ULL) { + ush_writeln("cat: read failed"); + return 0; + } + + if (got > USH_CAT_MAX) { + got = USH_CAT_MAX; + } + + buf[got] = '\0'; + ush_writeln(buf); + + if (size > got) { + ush_writeln("[cat] output truncated"); + } + + return 1; +} + +static int ush_cmd_pwd(const ush_state *sh) { + ush_writeln(sh->cwd); + return 1; +} + +static int ush_cmd_cd(ush_state *sh, const char *arg) { + const char *target = arg; + char path[USH_PATH_MAX]; + + if (target == (const char *)0 || target[0] == '\0') { + target = "/"; + } + + if (ush_resolve_path(sh, target, path, (u64)sizeof(path)) == 0) { + ush_writeln("cd: invalid path"); + return 0; + } + + if (cleonos_sys_fs_stat_type(path) != 2ULL) { + ush_writeln("cd: directory not found"); + return 0; + } + + ush_copy(sh->cwd, (u64)sizeof(sh->cwd), path); + return 1; +} + +static int ush_cmd_exec(const ush_state *sh, const char *arg) { + char path[USH_PATH_MAX]; + u64 status; + + if (ush_resolve_exec_path(sh, arg, path, (u64)sizeof(path)) == 0) { + ush_writeln("exec: invalid target"); + return 0; + } + + if (ush_path_is_under_system(path) != 0) { + ush_writeln("exec: /system/*.elf is kernel-mode (KELF), not user-exec"); + return 0; + } + + status = cleonos_sys_exec_path(path); + + if (status == (u64)-1) { + ush_writeln("exec: request failed"); + return 0; + } + + if (status == 0ULL) { + ush_writeln("exec: request accepted"); + return 1; + } + + ush_writeln("exec: returned non-zero status"); + return 0; +} + +static int ush_cmd_clear(void) { + u64 i; + + for (i = 0ULL; i < USH_CLEAR_LINES; i++) { + ush_write_char('\n'); + } + + return 1; +} + +static int ush_cmd_kbdstat(void) { + ush_writeln("kbdstat:"); + ush_print_kv_hex(" BUFFERED", cleonos_sys_kbd_buffered()); + ush_print_kv_hex(" PUSHED", cleonos_sys_kbd_pushed()); + ush_print_kv_hex(" POPPED", cleonos_sys_kbd_popped()); + ush_print_kv_hex(" DROPPED", cleonos_sys_kbd_dropped()); + ush_print_kv_hex(" HOTKEY_SWITCHES", cleonos_sys_kbd_hotkey_switches()); + return 1; +} + +static int ush_cmd_memstat(void) { + ush_writeln("memstat (user ABI limited):"); + ush_print_kv_hex(" SERVICE_COUNT", cleonos_sys_service_count()); + ush_print_kv_hex(" SERVICE_READY_COUNT", cleonos_sys_service_ready_count()); + ush_print_kv_hex(" KELF_COUNT", cleonos_sys_kelf_count()); + ush_print_kv_hex(" KELF_RUNS", cleonos_sys_kelf_runs()); + return 1; +} + +static int ush_cmd_fsstat(void) { + ush_writeln("fsstat:"); + ush_print_kv_hex(" NODE_COUNT", cleonos_sys_fs_node_count()); + ush_print_kv_hex(" ROOT_CHILDREN", cleonos_sys_fs_child_count("/")); + ush_print_kv_hex(" SYSTEM_CHILDREN", cleonos_sys_fs_child_count("/system")); + ush_print_kv_hex(" SHELL_CHILDREN", cleonos_sys_fs_child_count("/shell")); + ush_print_kv_hex(" TEMP_CHILDREN", cleonos_sys_fs_child_count("/temp")); + ush_print_kv_hex(" DRIVER_CHILDREN", cleonos_sys_fs_child_count("/driver")); + return 1; +} + +static int ush_cmd_taskstat(void) { + ush_writeln("taskstat:"); + ush_print_kv_hex(" TASK_COUNT", cleonos_sys_task_count()); + ush_print_kv_hex(" CURRENT_TASK", cleonos_syscall(CLEONOS_SYSCALL_CUR_TASK, 0ULL, 0ULL, 0ULL)); + ush_print_kv_hex(" TIMER_TICKS", cleonos_sys_timer_ticks()); + ush_print_kv_hex(" CONTEXT_SWITCHES", cleonos_sys_context_switches()); + return 1; +} + +static int ush_cmd_userstat(void) { + ush_writeln("userstat:"); + ush_print_kv_hex(" USER_SHELL_READY", cleonos_sys_user_shell_ready()); + ush_print_kv_hex(" USER_EXEC_REQUESTED", cleonos_sys_user_exec_requested()); + ush_print_kv_hex(" USER_LAUNCH_TRIES", cleonos_sys_user_launch_tries()); + ush_print_kv_hex(" USER_LAUNCH_OK", cleonos_sys_user_launch_ok()); + ush_print_kv_hex(" USER_LAUNCH_FAIL", cleonos_sys_user_launch_fail()); + ush_print_kv_hex(" EXEC_REQUESTS", cleonos_sys_exec_request_count()); + ush_print_kv_hex(" EXEC_SUCCESS", cleonos_sys_exec_success_count()); + ush_print_kv_hex(" TTY_COUNT", cleonos_sys_tty_count()); + ush_print_kv_hex(" TTY_ACTIVE", cleonos_sys_tty_active()); + return 1; +} + +static int ush_cmd_shstat(const ush_state *sh) { + ush_writeln("shstat:"); + ush_print_kv_hex(" CMD_TOTAL", sh->cmd_total); + ush_print_kv_hex(" CMD_OK", sh->cmd_ok); + ush_print_kv_hex(" CMD_FAIL", sh->cmd_fail); + ush_print_kv_hex(" CMD_UNKNOWN", sh->cmd_unknown); + return 1; +} + +static int ush_cmd_tty(const char *arg) { + u64 tty_count = cleonos_sys_tty_count(); + u64 active = cleonos_sys_tty_active(); + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_print_kv_hex("TTY_COUNT", tty_count); + ush_print_kv_hex("TTY_ACTIVE", active); + return 1; + } + + { + u64 idx; + + if (ush_parse_u64_dec(arg, &idx) == 0) { + ush_writeln("tty: usage tty [index]"); + return 0; + } + + if (idx >= tty_count) { + ush_writeln("tty: index out of range"); + return 0; + } + + if (cleonos_sys_tty_switch(idx) == (u64)-1) { + ush_writeln("tty: switch failed"); + return 0; + } + + ush_writeln("tty: switched"); + ush_print_kv_hex("TTY_ACTIVE", cleonos_sys_tty_active()); + return 1; + } +} + +static int ush_cmd_dmesg(const char *arg) { + u64 total = cleonos_sys_log_journal_count(); + u64 limit = USH_DMESG_DEFAULT; + u64 start; + u64 i; + + if (arg != (const char *)0 && arg[0] != '\0') { + if (ush_parse_u64_dec(arg, &limit) == 0 || limit == 0ULL) { + ush_writeln("dmesg: usage dmesg [positive_count]"); + return 0; + } + } + + if (total == 0ULL) { + ush_writeln("(journal empty)"); + return 1; + } + + if (limit > total) { + limit = total; + } + + start = total - limit; + + for (i = start; i < total; i++) { + char line[USH_DMESG_LINE_MAX]; + + if (cleonos_sys_log_journal_read(i, line, (u64)sizeof(line)) != 0ULL) { + ush_writeln(line); + } + } + + return 1; +} + +static int ush_cmd_mkdir(const ush_state *sh, const char *arg) { + char path[USH_PATH_MAX]; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("mkdir: directory path required"); + return 0; + } + + if (ush_resolve_path(sh, arg, path, (u64)sizeof(path)) == 0) { + ush_writeln("mkdir: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(path) == 0) { + ush_writeln("mkdir: target must be under /temp"); + return 0; + } + + if (cleonos_sys_fs_mkdir(path) == 0ULL) { + ush_writeln("mkdir: failed"); + return 0; + } + + return 1; +} + +static int ush_cmd_touch(const ush_state *sh, const char *arg) { + static const char empty_data[1] = {'\0'}; + char path[USH_PATH_MAX]; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("touch: file path required"); + return 0; + } + + if (ush_resolve_path(sh, arg, path, (u64)sizeof(path)) == 0) { + ush_writeln("touch: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(path) == 0) { + ush_writeln("touch: target must be under /temp"); + return 0; + } + + if (cleonos_sys_fs_write(path, empty_data, 0ULL) == 0ULL) { + ush_writeln("touch: failed"); + return 0; + } + + return 1; +} + +static int ush_cmd_write(const ush_state *sh, const char *arg) { + char path_arg[USH_PATH_MAX]; + char abs_path[USH_PATH_MAX]; + const char *payload; + u64 payload_len; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("write: usage write "); + return 0; + } + + if (ush_split_first_and_rest(arg, path_arg, (u64)sizeof(path_arg), &payload) == 0) { + ush_writeln("write: usage write "); + return 0; + } + + if (ush_resolve_path(sh, path_arg, abs_path, (u64)sizeof(abs_path)) == 0) { + ush_writeln("write: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(abs_path) == 0) { + ush_writeln("write: target must be under /temp"); + return 0; + } + + payload_len = ush_strlen(payload); + + if (cleonos_sys_fs_write(abs_path, payload, payload_len) == 0ULL) { + ush_writeln("write: failed"); + return 0; + } + + return 1; +} + +static int ush_cmd_append(const ush_state *sh, const char *arg) { + char path_arg[USH_PATH_MAX]; + char abs_path[USH_PATH_MAX]; + const char *payload; + u64 payload_len; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("append: usage append "); + return 0; + } + + if (ush_split_first_and_rest(arg, path_arg, (u64)sizeof(path_arg), &payload) == 0) { + ush_writeln("append: usage append "); + return 0; + } + + if (ush_resolve_path(sh, path_arg, abs_path, (u64)sizeof(abs_path)) == 0) { + ush_writeln("append: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(abs_path) == 0) { + ush_writeln("append: target must be under /temp"); + return 0; + } + + payload_len = ush_strlen(payload); + + if (cleonos_sys_fs_append(abs_path, payload, payload_len) == 0ULL) { + ush_writeln("append: failed"); + return 0; + } + + return 1; +} + +static int ush_copy_file(const char *src_path, const char *dst_path) { + static char copy_buf[USH_COPY_MAX]; + u64 src_type; + u64 src_size; + u64 got; + + src_type = cleonos_sys_fs_stat_type(src_path); + + if (src_type != 1ULL) { + ush_writeln("cp: source file not found"); + return 0; + } + + src_size = cleonos_sys_fs_stat_size(src_path); + + if (src_size == (u64)-1) { + ush_writeln("cp: failed to stat source"); + return 0; + } + + if (src_size > (u64)USH_COPY_MAX) { + ush_writeln("cp: source too large for user shell buffer"); + return 0; + } + + if (src_size == 0ULL) { + got = 0ULL; + } else { + got = cleonos_sys_fs_read(src_path, copy_buf, src_size); + + if (got == 0ULL || got != src_size) { + ush_writeln("cp: failed to read source"); + return 0; + } + } + + if (cleonos_sys_fs_write(dst_path, copy_buf, got) == 0ULL) { + ush_writeln("cp: failed to write destination"); + return 0; + } + + return 1; +} + +static int ush_cmd_cp(const ush_state *sh, const char *arg) { + char src_arg[USH_PATH_MAX]; + char dst_arg[USH_PATH_MAX]; + char src_path[USH_PATH_MAX]; + char dst_path[USH_PATH_MAX]; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("cp: usage cp "); + return 0; + } + + if (ush_split_two_args(arg, src_arg, (u64)sizeof(src_arg), dst_arg, (u64)sizeof(dst_arg)) == 0) { + ush_writeln("cp: usage cp "); + return 0; + } + + if (ush_resolve_path(sh, src_arg, src_path, (u64)sizeof(src_path)) == 0 || + ush_resolve_path(sh, dst_arg, dst_path, (u64)sizeof(dst_path)) == 0) { + ush_writeln("cp: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(dst_path) == 0) { + ush_writeln("cp: destination must be under /temp"); + return 0; + } + + return ush_copy_file(src_path, dst_path); +} + +static int ush_cmd_mv(const ush_state *sh, const char *arg) { + char src_arg[USH_PATH_MAX]; + char dst_arg[USH_PATH_MAX]; + char src_path[USH_PATH_MAX]; + char dst_path[USH_PATH_MAX]; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("mv: usage mv "); + return 0; + } + + if (ush_split_two_args(arg, src_arg, (u64)sizeof(src_arg), dst_arg, (u64)sizeof(dst_arg)) == 0) { + ush_writeln("mv: usage mv "); + return 0; + } + + if (ush_resolve_path(sh, src_arg, src_path, (u64)sizeof(src_path)) == 0 || + ush_resolve_path(sh, dst_arg, dst_path, (u64)sizeof(dst_path)) == 0) { + ush_writeln("mv: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(src_path) == 0 || ush_path_is_under_temp(dst_path) == 0) { + ush_writeln("mv: source and destination must be under /temp"); + return 0; + } + + if (ush_copy_file(src_path, dst_path) == 0) { + return 0; + } + + if (cleonos_sys_fs_remove(src_path) == 0ULL) { + ush_writeln("mv: source remove failed"); + return 0; + } + + return 1; +} + +static int ush_cmd_rm(const ush_state *sh, const char *arg) { + char path[USH_PATH_MAX]; + + if (arg == (const char *)0 || arg[0] == '\0') { + ush_writeln("rm: path required"); + return 0; + } + + if (ush_resolve_path(sh, arg, path, (u64)sizeof(path)) == 0) { + ush_writeln("rm: invalid path"); + return 0; + } + + if (ush_path_is_under_temp(path) == 0) { + ush_writeln("rm: target must be under /temp"); + return 0; + } + + if (cleonos_sys_fs_remove(path) == 0ULL) { + ush_writeln("rm: failed (directory must be empty)"); + return 0; + } + + return 1; +} + +static int ush_cmd_stats(const ush_state *sh) { + (void)ush_cmd_memstat(); + (void)ush_cmd_fsstat(); + (void)ush_cmd_taskstat(); + (void)ush_cmd_userstat(); + (void)ush_cmd_kbdstat(); + (void)ush_cmd_shstat(sh); + return 1; +} + +static int ush_cmd_not_supported(const char *name, const char *why) { + ush_write(name); + ush_write(": "); + ush_writeln(why); + return 0; +} + +void ush_execute_line(ush_state *sh, const char *line) { + char line_buf[USH_LINE_MAX]; + char cmd[USH_CMD_MAX]; + char arg[USH_ARG_MAX]; + u64 i = 0ULL; + int known = 1; + int success = 0; + + if (sh == (ush_state *)0 || 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'; + ush_trim_line(line_buf); + + if (line_buf[0] == '\0' || line_buf[0] == '#') { + return; + } + + ush_parse_line(line_buf, cmd, (u64)sizeof(cmd), arg, (u64)sizeof(arg)); + ush_trim_line(arg); + + if (ush_streq(cmd, "help") != 0) { + success = ush_cmd_help(); + } else if (ush_streq(cmd, "ls") != 0 || ush_streq(cmd, "dir") != 0) { + success = ush_cmd_ls(sh, arg); + } else if (ush_streq(cmd, "cat") != 0) { + success = ush_cmd_cat(sh, arg); + } else if (ush_streq(cmd, "pwd") != 0) { + success = ush_cmd_pwd(sh); + } else if (ush_streq(cmd, "cd") != 0) { + success = ush_cmd_cd(sh, arg); + } else if (ush_streq(cmd, "exec") != 0 || ush_streq(cmd, "run") != 0) { + success = ush_cmd_exec(sh, arg); + } else if (ush_streq(cmd, "clear") != 0 || ush_streq(cmd, "cls") != 0) { + success = ush_cmd_clear(); + } else if (ush_streq(cmd, "memstat") != 0) { + success = ush_cmd_memstat(); + } else if (ush_streq(cmd, "fsstat") != 0) { + success = ush_cmd_fsstat(); + } else if (ush_streq(cmd, "taskstat") != 0) { + success = ush_cmd_taskstat(); + } else if (ush_streq(cmd, "userstat") != 0) { + success = ush_cmd_userstat(); + } else if (ush_streq(cmd, "shstat") != 0) { + success = ush_cmd_shstat(sh); + } else if (ush_streq(cmd, "stats") != 0) { + success = ush_cmd_stats(sh); + } else if (ush_streq(cmd, "tty") != 0) { + success = ush_cmd_tty(arg); + } else if (ush_streq(cmd, "dmesg") != 0) { + success = ush_cmd_dmesg(arg); + } else if (ush_streq(cmd, "kbdstat") != 0) { + success = ush_cmd_kbdstat(); + } else if (ush_streq(cmd, "mkdir") != 0) { + success = ush_cmd_mkdir(sh, arg); + } else if (ush_streq(cmd, "touch") != 0) { + success = ush_cmd_touch(sh, arg); + } else if (ush_streq(cmd, "write") != 0) { + success = ush_cmd_write(sh, arg); + } else if (ush_streq(cmd, "append") != 0) { + success = ush_cmd_append(sh, arg); + } else if (ush_streq(cmd, "cp") != 0) { + success = ush_cmd_cp(sh, arg); + } else if (ush_streq(cmd, "mv") != 0) { + success = ush_cmd_mv(sh, arg); + } else if (ush_streq(cmd, "rm") != 0) { + success = ush_cmd_rm(sh, arg); + } else if (ush_streq(cmd, "rusttest") != 0 || ush_streq(cmd, "panic") != 0 || ush_streq(cmd, "elfloader") != 0) { + success = ush_cmd_not_supported(cmd, "this command is kernel-shell only"); + } else { + known = 0; + success = 0; + ush_writeln("unknown command; type 'help'"); + } + + sh->cmd_total++; + + if (success != 0) { + sh->cmd_ok++; + } else { + sh->cmd_fail++; + } + + if (known == 0) { + sh->cmd_unknown++; + } +} \ No newline at end of file diff --git a/cleonos/c/apps/shell/shell_input.c b/cleonos/c/apps/shell/shell_input.c new file mode 100644 index 0000000..2034a43 --- /dev/null +++ b/cleonos/c/apps/shell/shell_input.c @@ -0,0 +1,322 @@ +#include "shell_internal.h" + +static void ush_history_cancel_nav(ush_state *sh) { + if (sh == (ush_state *)0) { + return; + } + + sh->history_nav = -1; + sh->nav_saved_len = 0ULL; + sh->nav_saved_cursor = 0ULL; + sh->nav_saved_line[0] = '\0'; +} + +static void ush_reset_line(ush_state *sh) { + if (sh == (ush_state *)0) { + return; + } + + sh->line_len = 0ULL; + sh->cursor = 0ULL; + sh->rendered_len = 0ULL; + sh->line[0] = '\0'; +} + +static void ush_load_line(ush_state *sh, const char *line) { + if (sh == (ush_state *)0) { + return; + } + + if (line == (const char *)0) { + ush_reset_line(sh); + return; + } + + ush_copy(sh->line, (u64)sizeof(sh->line), line); + sh->line_len = ush_strlen(sh->line); + sh->cursor = sh->line_len; +} + +static void ush_render_line(ush_state *sh) { + u64 i; + + if (sh == (ush_state *)0) { + return; + } + + ush_write_char('\r'); + ush_prompt(sh); + + for (i = 0ULL; i < sh->line_len; i++) { + ush_write_char(sh->line[i]); + } + + for (i = sh->line_len; i < sh->rendered_len; i++) { + ush_write_char(' '); + } + + ush_write_char('\r'); + ush_prompt(sh); + + for (i = 0ULL; i < sh->cursor; i++) { + ush_write_char(sh->line[i]); + } + + sh->rendered_len = sh->line_len; +} + +static int ush_line_has_non_space(const char *line) { + u64 i = 0ULL; + + if (line == (const char *)0) { + return 0; + } + + while (line[i] != '\0') { + if (ush_is_space(line[i]) == 0) { + return 1; + } + i++; + } + + return 0; +} + +static void ush_history_push(ush_state *sh, const char *line) { + if (sh == (ush_state *)0) { + return; + } + + if (ush_line_has_non_space(line) == 0) { + ush_history_cancel_nav(sh); + return; + } + + if (sh->history_count > 0ULL && ush_streq(sh->history[sh->history_count - 1ULL], line) != 0) { + ush_history_cancel_nav(sh); + return; + } + + if (sh->history_count < USH_HISTORY_MAX) { + ush_copy(sh->history[sh->history_count], (u64)sizeof(sh->history[sh->history_count]), line); + sh->history_count++; + } else { + u64 i; + + for (i = 1ULL; i < USH_HISTORY_MAX; i++) { + ush_copy(sh->history[i - 1ULL], (u64)sizeof(sh->history[i - 1ULL]), sh->history[i]); + } + + ush_copy(sh->history[USH_HISTORY_MAX - 1ULL], (u64)sizeof(sh->history[USH_HISTORY_MAX - 1ULL]), line); + } + + ush_history_cancel_nav(sh); +} + +static void ush_history_apply_current(ush_state *sh) { + if (sh == (ush_state *)0) { + return; + } + + if (sh->history_nav >= 0) { + ush_load_line(sh, sh->history[(u64)sh->history_nav]); + } else { + ush_copy(sh->line, (u64)sizeof(sh->line), sh->nav_saved_line); + sh->line_len = sh->nav_saved_len; + if (sh->line_len > USH_LINE_MAX - 1ULL) { + sh->line_len = USH_LINE_MAX - 1ULL; + sh->line[sh->line_len] = '\0'; + } + sh->cursor = sh->nav_saved_cursor; + if (sh->cursor > sh->line_len) { + sh->cursor = sh->line_len; + } + } + + ush_render_line(sh); +} + +static void ush_history_up(ush_state *sh) { + if (sh == (ush_state *)0 || sh->history_count == 0ULL) { + return; + } + + if (sh->history_nav < 0) { + ush_copy(sh->nav_saved_line, (u64)sizeof(sh->nav_saved_line), sh->line); + sh->nav_saved_len = sh->line_len; + sh->nav_saved_cursor = sh->cursor; + sh->history_nav = (i64)sh->history_count - 1; + } else if (sh->history_nav > 0) { + sh->history_nav--; + } + + ush_history_apply_current(sh); +} + +static void ush_history_down(ush_state *sh) { + if (sh == (ush_state *)0 || sh->history_nav < 0) { + return; + } + + if ((u64)sh->history_nav + 1ULL < sh->history_count) { + sh->history_nav++; + } else { + sh->history_nav = -1; + } + + ush_history_apply_current(sh); +} + +static char ush_read_char_blocking(void) { + for (;;) { + u64 ch = cleonos_sys_kbd_get_char(); + + if (ch != (u64)-1) { + return (char)(ch & 0xFFULL); + } + + __asm__ volatile("pause"); + } +} + +void ush_read_line(ush_state *sh, char *out_line, u64 out_size) { + if (sh == (ush_state *)0 || out_line == (char *)0 || out_size == 0ULL) { + return; + } + + ush_reset_line(sh); + ush_history_cancel_nav(sh); + + out_line[0] = '\0'; + + ush_prompt(sh); + + for (;;) { + char ch = ush_read_char_blocking(); + + if (ch == '\r') { + continue; + } + + if (ch == '\n') { + ush_write_char('\n'); + sh->line[sh->line_len] = '\0'; + ush_history_push(sh, sh->line); + ush_copy(out_line, out_size, sh->line); + ush_reset_line(sh); + return; + } + + if (ch == USH_KEY_UP) { + ush_history_up(sh); + continue; + } + + if (ch == USH_KEY_DOWN) { + ush_history_down(sh); + continue; + } + + if (ch == USH_KEY_LEFT) { + if (sh->cursor > 0ULL) { + sh->cursor--; + ush_render_line(sh); + } + continue; + } + + if (ch == USH_KEY_RIGHT) { + if (sh->cursor < sh->line_len) { + sh->cursor++; + ush_render_line(sh); + } + continue; + } + + if (ch == USH_KEY_HOME) { + if (sh->cursor != 0ULL) { + sh->cursor = 0ULL; + ush_render_line(sh); + } + continue; + } + + if (ch == USH_KEY_END) { + if (sh->cursor != sh->line_len) { + sh->cursor = sh->line_len; + ush_render_line(sh); + } + continue; + } + + if (ch == '\b' || ch == 127) { + if (sh->cursor > 0ULL && sh->line_len > 0ULL) { + u64 i; + + ush_history_cancel_nav(sh); + + for (i = sh->cursor - 1ULL; i < sh->line_len; i++) { + sh->line[i] = sh->line[i + 1ULL]; + } + + sh->line_len--; + sh->cursor--; + ush_render_line(sh); + } + continue; + } + + if (ch == USH_KEY_DELETE) { + if (sh->cursor < sh->line_len) { + u64 i; + + ush_history_cancel_nav(sh); + + for (i = sh->cursor; i < sh->line_len; i++) { + sh->line[i] = sh->line[i + 1ULL]; + } + + sh->line_len--; + ush_render_line(sh); + } + continue; + } + + if (ch == '\t') { + ch = ' '; + } + + if (ush_is_printable(ch) == 0) { + continue; + } + + if (sh->line_len + 1ULL >= USH_LINE_MAX) { + continue; + } + + ush_history_cancel_nav(sh); + + if (sh->cursor == sh->line_len) { + sh->line[sh->line_len++] = ch; + sh->line[sh->line_len] = '\0'; + sh->cursor = sh->line_len; + ush_write_char(ch); + sh->rendered_len = sh->line_len; + continue; + } + + { + u64 i; + + for (i = sh->line_len; i > sh->cursor; i--) { + sh->line[i] = sh->line[i - 1ULL]; + } + + sh->line[sh->cursor] = ch; + sh->line_len++; + sh->cursor++; + sh->line[sh->line_len] = '\0'; + ush_render_line(sh); + } + } +} diff --git a/cleonos/c/apps/shell/shell_internal.h b/cleonos/c/apps/shell/shell_internal.h new file mode 100644 index 0000000..9d28132 --- /dev/null +++ b/cleonos/c/apps/shell/shell_internal.h @@ -0,0 +1,74 @@ +#ifndef CLEONOS_USER_SHELL_INTERNAL_H +#define CLEONOS_USER_SHELL_INTERNAL_H + +#include + +typedef long long i64; + +#define USH_CMD_MAX 32ULL +#define USH_ARG_MAX 160ULL +#define USH_LINE_MAX 192ULL +#define USH_PATH_MAX 192ULL +#define USH_CAT_MAX 512ULL +#define USH_SCRIPT_MAX 1024ULL +#define USH_CLEAR_LINES 56ULL +#define USH_HISTORY_MAX 16ULL + +#define USH_KEY_LEFT ((char)0x01) +#define USH_KEY_RIGHT ((char)0x02) +#define USH_KEY_UP ((char)0x03) +#define USH_KEY_DOWN ((char)0x04) +#define USH_KEY_HOME ((char)0x05) +#define USH_KEY_END ((char)0x06) +#define USH_KEY_DELETE ((char)0x07) + +typedef struct ush_state { + char line[USH_LINE_MAX]; + u64 line_len; + u64 cursor; + u64 rendered_len; + + char cwd[USH_PATH_MAX]; + + char history[USH_HISTORY_MAX][USH_LINE_MAX]; + u64 history_count; + i64 history_nav; + char nav_saved_line[USH_LINE_MAX]; + u64 nav_saved_len; + u64 nav_saved_cursor; + + u64 cmd_total; + u64 cmd_ok; + u64 cmd_fail; + u64 cmd_unknown; +} ush_state; + +void ush_init_state(ush_state *sh); + +u64 ush_strlen(const char *str); +int ush_streq(const char *left, const char *right); +int ush_is_space(char ch); +int ush_is_printable(char ch); +int ush_has_suffix(const char *name, const char *suffix); +int ush_contains_char(const char *text, char needle); +int ush_parse_u64_dec(const char *text, u64 *out_value); +void ush_copy(char *dst, u64 dst_size, const char *src); +void ush_trim_line(char *line); +void ush_parse_line(const char *line, char *out_cmd, u64 cmd_size, char *out_arg, u64 arg_size); + +void ush_write(const char *text); +void ush_write_char(char ch); +void ush_writeln(const char *text); +void ush_prompt(const ush_state *sh); +void ush_write_hex_u64(u64 value); +void ush_print_kv_hex(const char *label, u64 value); + +int ush_resolve_path(const ush_state *sh, const char *arg, char *out_path, u64 out_size); +int ush_resolve_exec_path(const ush_state *sh, const char *arg, char *out_path, u64 out_size); +int ush_path_is_under_system(const char *path); + +void ush_read_line(ush_state *sh, char *out_line, u64 out_size); +int ush_run_script_file(ush_state *sh, const char *path); +void ush_execute_line(ush_state *sh, const char *line); + +#endif diff --git a/cleonos/c/apps/shell/shell_path.c b/cleonos/c/apps/shell/shell_path.c new file mode 100644 index 0000000..20dfbda --- /dev/null +++ b/cleonos/c/apps/shell/shell_path.c @@ -0,0 +1,202 @@ +#include "shell_internal.h" + +static int ush_path_push_component(char *path, u64 path_size, u64 *io_len, const char *component, u64 comp_len) { + u64 i; + + if (path == (char *)0 || io_len == (u64 *)0 || component == (const char *)0 || comp_len == 0ULL) { + return 0; + } + + if (*io_len == 1ULL) { + if (*io_len + comp_len >= path_size) { + return 0; + } + + for (i = 0ULL; i < comp_len; i++) { + path[1ULL + i] = component[i]; + } + + *io_len = 1ULL + comp_len; + path[*io_len] = '\0'; + return 1; + } + + if (*io_len + 1ULL + comp_len >= path_size) { + return 0; + } + + path[*io_len] = '/'; + for (i = 0ULL; i < comp_len; i++) { + path[*io_len + 1ULL + i] = component[i]; + } + + *io_len += (1ULL + comp_len); + path[*io_len] = '\0'; + return 1; +} + +static void ush_path_pop_component(char *path, u64 *io_len) { + if (path == (char *)0 || io_len == (u64 *)0) { + return; + } + + if (*io_len <= 1ULL) { + path[0] = '/'; + path[1] = '\0'; + *io_len = 1ULL; + return; + } + + while (*io_len > 1ULL && path[*io_len - 1ULL] != '/') { + (*io_len)--; + } + + if (*io_len > 1ULL) { + (*io_len)--; + } + + path[*io_len] = '\0'; +} + +static int ush_path_parse_into(const char *src, char *out_path, u64 out_size, u64 *io_len) { + u64 i = 0ULL; + + if (src == (const char *)0 || out_path == (char *)0 || io_len == (u64 *)0) { + return 0; + } + + if (src[0] == '/') { + i = 1ULL; + } + + while (src[i] != '\0') { + u64 start; + u64 len; + + while (src[i] == '/') { + i++; + } + + if (src[i] == '\0') { + break; + } + + start = i; + + while (src[i] != '\0' && src[i] != '/') { + i++; + } + + len = i - start; + + if (len == 1ULL && src[start] == '.') { + continue; + } + + if (len == 2ULL && src[start] == '.' && src[start + 1ULL] == '.') { + ush_path_pop_component(out_path, io_len); + continue; + } + + if (ush_path_push_component(out_path, out_size, io_len, src + start, len) == 0) { + return 0; + } + } + + return 1; +} + +int ush_resolve_path(const ush_state *sh, const char *arg, char *out_path, u64 out_size) { + u64 len = 1ULL; + + if (sh == (const ush_state *)0 || out_path == (char *)0 || out_size < 2ULL) { + return 0; + } + + out_path[0] = '/'; + out_path[1] = '\0'; + + if (arg == (const char *)0 || arg[0] == '\0') { + return ush_path_parse_into(sh->cwd, out_path, out_size, &len); + } + + if (arg[0] != '/') { + if (ush_path_parse_into(sh->cwd, out_path, out_size, &len) == 0) { + return 0; + } + } + + return ush_path_parse_into(arg, out_path, out_size, &len); +} + +int ush_resolve_exec_path(const ush_state *sh, const char *arg, char *out_path, u64 out_size) { + u64 i; + u64 cursor = 0ULL; + + if (sh == (const ush_state *)0 || 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] == '/') { + ush_copy(out_path, out_size, arg); + } else if (ush_contains_char(arg, '/') != 0) { + if (ush_resolve_path(sh, arg, out_path, out_size) == 0) { + return 0; + } + } else { + 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]; + } + + for (i = 0ULL; arg[i] != '\0'; i++) { + if (cursor + 1ULL >= out_size) { + return 0; + } + out_path[cursor++] = arg[i]; + } + + out_path[cursor] = '\0'; + } + + if (ush_has_suffix(out_path, ".elf") == 0) { + static const char suffix[] = ".elf"; + + cursor = ush_strlen(out_path); + + 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; +} + +int ush_path_is_under_system(const char *path) { + if (path == (const char *)0) { + return 0; + } + + if (path[0] != '/' || path[1] != 's' || path[2] != 'y' || path[3] != 's' || path[4] != 't' || path[5] != 'e' || path[6] != 'm') { + return 0; + } + + return (path[7] == '\0' || path[7] == '/') ? 1 : 0; +} diff --git a/cleonos/c/apps/shell/shell_script.c b/cleonos/c/apps/shell/shell_script.c new file mode 100644 index 0000000..8e73079 --- /dev/null +++ b/cleonos/c/apps/shell/shell_script.c @@ -0,0 +1,46 @@ +#include "shell_internal.h" + +int ush_run_script_file(ush_state *sh, const char *path) { + char script[USH_SCRIPT_MAX + 1ULL]; + char line[USH_LINE_MAX]; + u64 got; + u64 i; + u64 line_pos = 0ULL; + + if (sh == (ush_state *)0 || path == (const char *)0 || path[0] == '\0') { + return 0; + } + + got = cleonos_sys_fs_read(path, script, USH_SCRIPT_MAX); + + if (got == 0ULL) { + return 0; + } + + if (got > USH_SCRIPT_MAX) { + got = USH_SCRIPT_MAX; + } + + script[got] = '\0'; + + for (i = 0ULL; i <= got; i++) { + char ch = script[i]; + + if (ch == '\r') { + continue; + } + + if (ch == '\n' || ch == '\0') { + line[line_pos] = '\0'; + ush_execute_line(sh, line); + line_pos = 0ULL; + continue; + } + + if (line_pos + 1ULL < (u64)sizeof(line)) { + line[line_pos++] = ch; + } + } + + return 1; +} diff --git a/cleonos/c/apps/shell/shell_util.c b/cleonos/c/apps/shell/shell_util.c new file mode 100644 index 0000000..9f19397 --- /dev/null +++ b/cleonos/c/apps/shell/shell_util.c @@ -0,0 +1,273 @@ +#include "shell_internal.h" + +void ush_init_state(ush_state *sh) { + if (sh == (ush_state *)0) { + return; + } + + sh->line[0] = '\0'; + sh->line_len = 0ULL; + sh->cursor = 0ULL; + sh->rendered_len = 0ULL; + + ush_copy(sh->cwd, (u64)sizeof(sh->cwd), "/"); + + sh->history_count = 0ULL; + sh->history_nav = -1; + sh->nav_saved_line[0] = '\0'; + sh->nav_saved_len = 0ULL; + sh->nav_saved_cursor = 0ULL; + + sh->cmd_total = 0ULL; + sh->cmd_ok = 0ULL; + sh->cmd_fail = 0ULL; + sh->cmd_unknown = 0ULL; +} + +u64 ush_strlen(const char *str) { + u64 len = 0ULL; + + if (str == (const char *)0) { + return 0ULL; + } + + while (str[len] != '\0') { + len++; + } + + return len; +} + +int ush_streq(const char *left, const char *right) { + u64 i = 0ULL; + + if (left == (const char *)0 || right == (const char *)0) { + return 0; + } + + while (left[i] != '\0' && right[i] != '\0') { + if (left[i] != right[i]) { + return 0; + } + i++; + } + + return (left[i] == right[i]) ? 1 : 0; +} + +int ush_is_space(char ch) { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') ? 1 : 0; +} + +int ush_is_printable(char ch) { + return (ch >= 32 && ch <= 126) ? 1 : 0; +} + +int ush_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 = ush_strlen(name); + suffix_len = ush_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; +} + +int ush_contains_char(const char *text, char needle) { + u64 i = 0ULL; + + if (text == (const char *)0) { + return 0; + } + + while (text[i] != '\0') { + if (text[i] == needle) { + return 1; + } + i++; + } + + return 0; +} + +int ush_parse_u64_dec(const char *text, u64 *out_value) { + u64 value = 0ULL; + u64 i = 0ULL; + + if (text == (const char *)0 || out_value == (u64 *)0 || text[0] == '\0') { + return 0; + } + + while (text[i] != '\0') { + u64 digit; + + if (text[i] < '0' || text[i] > '9') { + return 0; + } + + digit = (u64)(text[i] - '0'); + + if (value > ((0xFFFFFFFFFFFFFFFFULL - digit) / 10ULL)) { + return 0; + } + + value = (value * 10ULL) + digit; + i++; + } + + *out_value = value; + return 1; +} + +void ush_copy(char *dst, u64 dst_size, const char *src) { + u64 i = 0ULL; + + if (dst == (char *)0 || src == (const char *)0 || dst_size == 0ULL) { + return; + } + + while (src[i] != '\0' && i + 1ULL < dst_size) { + dst[i] = src[i]; + i++; + } + + dst[i] = '\0'; +} + +void ush_trim_line(char *line) { + u64 start = 0ULL; + u64 i = 0ULL; + u64 len; + + if (line == (char *)0) { + return; + } + + while (line[start] != '\0' && ush_is_space(line[start]) != 0) { + start++; + } + + if (start > 0ULL) { + while (line[start + i] != '\0') { + line[i] = line[start + i]; + i++; + } + line[i] = '\0'; + } + + len = ush_strlen(line); + + while (len > 0ULL && ush_is_space(line[len - 1ULL]) != 0) { + line[len - 1ULL] = '\0'; + len--; + } +} + +void ush_parse_line(const char *line, char *out_cmd, u64 cmd_size, char *out_arg, u64 arg_size) { + u64 i = 0ULL; + u64 cmd_pos = 0ULL; + u64 arg_pos = 0ULL; + + if (line == (const char *)0 || out_cmd == (char *)0 || out_arg == (char *)0) { + return; + } + + out_cmd[0] = '\0'; + out_arg[0] = '\0'; + + while (line[i] != '\0' && ush_is_space(line[i]) != 0) { + i++; + } + + while (line[i] != '\0' && ush_is_space(line[i]) == 0) { + if (cmd_pos + 1ULL < cmd_size) { + out_cmd[cmd_pos++] = line[i]; + } + i++; + } + + out_cmd[cmd_pos] = '\0'; + + while (line[i] != '\0' && ush_is_space(line[i]) != 0) { + i++; + } + + while (line[i] != '\0') { + if (arg_pos + 1ULL < arg_size) { + out_arg[arg_pos++] = line[i]; + } + i++; + } + + out_arg[arg_pos] = '\0'; +} + +void ush_write(const char *text) { + u64 len; + + if (text == (const char *)0) { + return; + } + + len = ush_strlen(text); + + if (len == 0ULL) { + return; + } + + (void)cleonos_sys_tty_write(text, len); +} + +void ush_write_char(char ch) { + (void)cleonos_sys_tty_write_char(ch); +} + +void ush_writeln(const char *text) { + ush_write(text); + ush_write_char('\n'); +} + +void ush_prompt(const ush_state *sh) { + if (sh == (const ush_state *)0) { + ush_write("cleonos(user)> "); + return; + } + + ush_write("cleonos(user:"); + ush_write(sh->cwd); + ush_write(")> "); +} + +void ush_write_hex_u64(u64 value) { + i64 nibble; + + ush_write("0X"); + + for (nibble = 15; nibble >= 0; nibble--) { + u64 current = (value >> (u64)(nibble * 4)) & 0x0FULL; + char out = (current < 10ULL) ? (char)('0' + current) : (char)('A' + (current - 10ULL)); + ush_write_char(out); + } +} + +void ush_print_kv_hex(const char *label, u64 value) { + ush_write(label); + ush_write(": "); + ush_write_hex_u64(value); + ush_write_char('\n'); +} diff --git a/cleonos/c/apps/shell_main.c b/cleonos/c/apps/shell_main.c index b33f2e1..73ad284 100644 --- a/cleonos/c/apps/shell_main.c +++ b/cleonos/c/apps/shell_main.c @@ -1,539 +1,18 @@ -#include - -#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; - - 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; - - if (left == (const char *)0 || right == (const char *)0) { - return 0; - } - - while (left[i] != '\0' && right[i] != '\0') { - if (left[i] != right[i]) { - return 0; - } - i++; - } - - return (left[i] == right[i]) ? 1 : 0; -} - -static int shell_is_space(char ch) { - return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') ? 1 : 0; -} - -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; - } - - while (src[i] != '\0' && i + 1ULL < dst_size) { - dst[i] = src[i]; - i++; - } - - dst[i] = '\0'; -} - -static void shell_write(const char *text) { - u64 len; - - if (text == (const char *)0) { - return; - } - - len = shell_strlen(text); - - if (len == 0ULL) { - return; - } - - (void)cleonos_sys_tty_write(text, len); -} - -static void shell_write_char(char ch) { - (void)cleonos_sys_tty_write_char(ch); -} - -static void shell_writeln(const char *text) { - shell_write(text); - shell_write_char('\n'); -} - -static void shell_prompt(void) { - shell_write("cleonos(user)> "); -} - -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; - u64 arg_pos = 0ULL; - - if (line == (const char *)0 || out_cmd == (char *)0 || out_arg == (char *)0) { - return; - } - - out_cmd[0] = '\0'; - out_arg[0] = '\0'; - - while (line[i] != '\0' && shell_is_space(line[i]) != 0) { - i++; - } - - while (line[i] != '\0' && shell_is_space(line[i]) == 0) { - if (cmd_pos + 1ULL < cmd_size) { - out_cmd[cmd_pos++] = line[i]; - } - i++; - } - - out_cmd[cmd_pos] = '\0'; - - while (line[i] != '\0' && shell_is_space(line[i]) != 0) { - i++; - } - - while (line[i] != '\0') { - if (arg_pos + 1ULL < arg_size) { - out_arg[arg_pos++] = line[i]; - } - i++; - } - - 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 int shell_path_is_under_system(const char *path) { - if (path == (const char *)0) { - return 0; - } - - if (path[0] != '/' || path[1] != 's' || path[2] != 'y' || path[3] != 's' || path[4] != 't' || path[5] != 'e' || path[6] != 'm') { - return 0; - } - - return (path[7] == '\0' || path[7] == '/') ? 1 : 0; -} - -static void shell_cmd_help(void) { - 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) { - const char *path = arg; - u64 count; - u64 i; - - if (path == (const char *)0 || path[0] == '\0') { - path = "/"; - } - - count = cleonos_sys_fs_child_count(path); - - if (count == (u64)-1) { - shell_writeln("ls: directory not found"); - return; - } - - if (count == 0ULL) { - shell_writeln("(empty)"); - return; - } - - for (i = 0ULL; i < count; i++) { - char name[CLEONOS_FS_NAME_MAX]; - - name[0] = '\0'; - - if (cleonos_sys_fs_get_child_name(path, i, name) == 0ULL) { - continue; - } - - shell_writeln(name); - } -} - -static void shell_cmd_cat(const char *arg) { - char cat_buf[SHELL_CAT_MAX + 1ULL]; - u64 got; - - if (arg == (const char *)0 || arg[0] == '\0') { - shell_writeln("cat: file path required"); - return; - } - - got = cleonos_sys_fs_read(arg, cat_buf, SHELL_CAT_MAX); - - if (got == 0ULL) { - shell_writeln("cat: file not found"); - return; - } - - if (got > SHELL_CAT_MAX) { - got = SHELL_CAT_MAX; - } - - cat_buf[got] = '\0'; - shell_writeln(cat_buf); -} - -static void shell_cmd_exec(const char *arg) { - char path[SHELL_LINE_MAX]; - u64 status; - - if (shell_resolve_exec_path(arg, path, (u64)sizeof(path)) == 0) { - shell_writeln("exec: invalid target"); - return; - } - - if (shell_path_is_under_system(path) != 0) { - shell_writeln("exec: /system/*.elf is kernel-mode (KELF), not user-exec"); - return; - } - - status = cleonos_sys_exec_path(path); - - if (status == (u64)-1) { - shell_writeln("exec: request failed"); - } else if (status == 0ULL) { - shell_writeln("exec: request accepted"); - } else { - shell_writeln("exec: returned non-zero status"); - } -} - -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) { - (void)cleonos_sys_task_count(); - shell_writeln("stats: use kernel log channel for full counters"); -} - -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; - - 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_parse_line(line_buf, cmd, (u64)sizeof(cmd), arg, (u64)sizeof(arg)); - - if (shell_streq(cmd, "help") != 0) { - shell_cmd_help(); - return; - } - - if (shell_streq(cmd, "ls") != 0) { - shell_cmd_ls(arg); - return; - } - - if (shell_streq(cmd, "cat") != 0) { - shell_cmd_cat(arg); - return; - } - - 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; - } - - if (shell_streq(cmd, "stats") != 0) { - shell_cmd_stats(); - return; - } - - shell_writeln("unknown command; type 'help'"); -} - -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'; - - 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; -} - -static char shell_read_char_blocking(void) { - for (;;) { - u64 ch = cleonos_sys_kbd_get_char(); - - 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; - } - - 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); - } -} +#include "shell/shell_internal.h" int cleonos_app_main(void) { - char line[SHELL_LINE_MAX]; + ush_state sh; + char line[USH_LINE_MAX]; - shell_writeln("[USER][SHELL] interactive framework online"); + ush_init_state(&sh); + ush_writeln("[USER][SHELL] interactive framework online"); - if (shell_run_script_file("/shell/init.cmd") == 0) { - shell_writeln("[USER][SHELL] /shell/init.cmd missing"); + if (ush_run_script_file(&sh, "/shell/init.cmd") == 0) { + ush_writeln("[USER][SHELL] /shell/init.cmd missing"); } for (;;) { - shell_prompt(); - shell_read_line(line, (u64)sizeof(line)); - shell_execute_line(line); + ush_read_line(&sh, line, (u64)sizeof(line)); + ush_execute_line(&sh, line); } } diff --git a/cleonos/c/include/cleonos_syscall.h b/cleonos/c/include/cleonos_syscall.h index 469450f..d8fdc46 100644 --- a/cleonos/c/include/cleonos_syscall.h +++ b/cleonos/c/include/cleonos_syscall.h @@ -33,6 +33,19 @@ typedef unsigned long long usize; #define CLEONOS_SYSCALL_TTY_WRITE 24ULL #define CLEONOS_SYSCALL_TTY_WRITE_CHAR 25ULL #define CLEONOS_SYSCALL_KBD_GET_CHAR 26ULL +#define CLEONOS_SYSCALL_FS_STAT_TYPE 27ULL +#define CLEONOS_SYSCALL_FS_STAT_SIZE 28ULL +#define CLEONOS_SYSCALL_FS_MKDIR 29ULL +#define CLEONOS_SYSCALL_FS_WRITE 30ULL +#define CLEONOS_SYSCALL_FS_APPEND 31ULL +#define CLEONOS_SYSCALL_FS_REMOVE 32ULL +#define CLEONOS_SYSCALL_LOG_JOURNAL_COUNT 33ULL +#define CLEONOS_SYSCALL_LOG_JOURNAL_READ 34ULL +#define CLEONOS_SYSCALL_KBD_BUFFERED 35ULL +#define CLEONOS_SYSCALL_KBD_PUSHED 36ULL +#define CLEONOS_SYSCALL_KBD_POPPED 37ULL +#define CLEONOS_SYSCALL_KBD_DROPPED 38ULL +#define CLEONOS_SYSCALL_KBD_HOTKEY_SWITCHES 39ULL u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); u64 cleonos_sys_log_write(const char *message, u64 length); @@ -61,5 +74,18 @@ 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); +u64 cleonos_sys_fs_stat_type(const char *path); +u64 cleonos_sys_fs_stat_size(const char *path); +u64 cleonos_sys_fs_mkdir(const char *path); +u64 cleonos_sys_fs_write(const char *path, const char *data, u64 size); +u64 cleonos_sys_fs_append(const char *path, const char *data, u64 size); +u64 cleonos_sys_fs_remove(const char *path); +u64 cleonos_sys_log_journal_count(void); +u64 cleonos_sys_log_journal_read(u64 index_from_oldest, char *out_line, u64 out_size); +u64 cleonos_sys_kbd_buffered(void); +u64 cleonos_sys_kbd_pushed(void); +u64 cleonos_sys_kbd_popped(void); +u64 cleonos_sys_kbd_dropped(void); +u64 cleonos_sys_kbd_hotkey_switches(void); #endif \ No newline at end of file diff --git a/cleonos/c/src/syscall.c b/cleonos/c/src/syscall.c index a0762d9..35aaace 100644 --- a/cleonos/c/src/syscall.c +++ b/cleonos/c/src/syscall.c @@ -115,4 +115,56 @@ u64 cleonos_sys_tty_write_char(char ch) { u64 cleonos_sys_kbd_get_char(void) { return cleonos_syscall(CLEONOS_SYSCALL_KBD_GET_CHAR, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_fs_stat_type(const char *path) { + return cleonos_syscall(CLEONOS_SYSCALL_FS_STAT_TYPE, (u64)path, 0ULL, 0ULL); +} + +u64 cleonos_sys_fs_stat_size(const char *path) { + return cleonos_syscall(CLEONOS_SYSCALL_FS_STAT_SIZE, (u64)path, 0ULL, 0ULL); +} + +u64 cleonos_sys_fs_mkdir(const char *path) { + return cleonos_syscall(CLEONOS_SYSCALL_FS_MKDIR, (u64)path, 0ULL, 0ULL); +} + +u64 cleonos_sys_fs_write(const char *path, const char *data, u64 size) { + return cleonos_syscall(CLEONOS_SYSCALL_FS_WRITE, (u64)path, (u64)data, size); +} + +u64 cleonos_sys_fs_append(const char *path, const char *data, u64 size) { + return cleonos_syscall(CLEONOS_SYSCALL_FS_APPEND, (u64)path, (u64)data, size); +} + +u64 cleonos_sys_fs_remove(const char *path) { + return cleonos_syscall(CLEONOS_SYSCALL_FS_REMOVE, (u64)path, 0ULL, 0ULL); +} + +u64 cleonos_sys_log_journal_count(void) { + return cleonos_syscall(CLEONOS_SYSCALL_LOG_JOURNAL_COUNT, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_log_journal_read(u64 index_from_oldest, char *out_line, u64 out_size) { + return cleonos_syscall(CLEONOS_SYSCALL_LOG_JOURNAL_READ, index_from_oldest, (u64)out_line, out_size); +} + +u64 cleonos_sys_kbd_buffered(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KBD_BUFFERED, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_kbd_pushed(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KBD_PUSHED, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_kbd_popped(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KBD_POPPED, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_kbd_dropped(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KBD_DROPPED, 0ULL, 0ULL, 0ULL); +} + +u64 cleonos_sys_kbd_hotkey_switches(void) { + return cleonos_syscall(CLEONOS_SYSCALL_KBD_HOTKEY_SWITCHES, 0ULL, 0ULL, 0ULL); } \ No newline at end of file diff --git a/clks/include/clks/syscall.h b/clks/include/clks/syscall.h index 8f91fca..0c2dfb2 100644 --- a/clks/include/clks/syscall.h +++ b/clks/include/clks/syscall.h @@ -30,6 +30,19 @@ #define CLKS_SYSCALL_TTY_WRITE 24ULL #define CLKS_SYSCALL_TTY_WRITE_CHAR 25ULL #define CLKS_SYSCALL_KBD_GET_CHAR 26ULL +#define CLKS_SYSCALL_FS_STAT_TYPE 27ULL +#define CLKS_SYSCALL_FS_STAT_SIZE 28ULL +#define CLKS_SYSCALL_FS_MKDIR 29ULL +#define CLKS_SYSCALL_FS_WRITE 30ULL +#define CLKS_SYSCALL_FS_APPEND 31ULL +#define CLKS_SYSCALL_FS_REMOVE 32ULL +#define CLKS_SYSCALL_LOG_JOURNAL_COUNT 33ULL +#define CLKS_SYSCALL_LOG_JOURNAL_READ 34ULL +#define CLKS_SYSCALL_KBD_BUFFERED 35ULL +#define CLKS_SYSCALL_KBD_PUSHED 36ULL +#define CLKS_SYSCALL_KBD_POPPED 37ULL +#define CLKS_SYSCALL_KBD_DROPPED 38ULL +#define CLKS_SYSCALL_KBD_HOTKEY_SWITCHES 39ULL void clks_syscall_init(void); u64 clks_syscall_dispatch(void *frame_ptr); diff --git a/clks/kernel/syscall.c b/clks/kernel/syscall.c index 87ece54..ea5d4b2 100644 --- a/clks/kernel/syscall.c +++ b/clks/kernel/syscall.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -12,10 +13,12 @@ #include #include -#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 +#define CLKS_SYSCALL_LOG_MAX_LEN 191U +#define CLKS_SYSCALL_PATH_MAX 192U +#define CLKS_SYSCALL_NAME_MAX 96U +#define CLKS_SYSCALL_TTY_MAX_LEN 512U +#define CLKS_SYSCALL_FS_IO_MAX_LEN 65536U +#define CLKS_SYSCALL_JOURNAL_MAX_LEN 256U struct clks_syscall_frame { u64 rax; @@ -94,15 +97,15 @@ static u64 clks_syscall_log_write(u64 arg0, u64 arg1) { 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]; + char buf[CLKS_SYSCALL_TTY_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; + if (len > CLKS_SYSCALL_TTY_MAX_LEN) { + len = CLKS_SYSCALL_TTY_MAX_LEN; } for (i = 0ULL; i < len; i++) { @@ -197,6 +200,139 @@ static u64 clks_syscall_exec_path(u64 arg0) { return status; } +static u64 clks_syscall_fs_stat_type(u64 arg0) { + char path[CLKS_SYSCALL_PATH_MAX]; + struct clks_fs_node_info info; + + if (clks_syscall_copy_user_string(arg0, path, sizeof(path)) == CLKS_FALSE) { + return (u64)-1; + } + + if (clks_fs_stat(path, &info) == CLKS_FALSE) { + return (u64)-1; + } + + return (u64)info.type; +} + +static u64 clks_syscall_fs_stat_size(u64 arg0) { + char path[CLKS_SYSCALL_PATH_MAX]; + struct clks_fs_node_info info; + + if (clks_syscall_copy_user_string(arg0, path, sizeof(path)) == CLKS_FALSE) { + return (u64)-1; + } + + if (clks_fs_stat(path, &info) == CLKS_FALSE) { + return (u64)-1; + } + + return info.size; +} + +static u64 clks_syscall_fs_mkdir(u64 arg0) { + char path[CLKS_SYSCALL_PATH_MAX]; + + if (clks_syscall_copy_user_string(arg0, path, sizeof(path)) == CLKS_FALSE) { + return 0ULL; + } + + return (clks_fs_mkdir(path) == CLKS_TRUE) ? 1ULL : 0ULL; +} + +static u64 clks_syscall_fs_write_common(u64 arg0, u64 arg1, u64 arg2, clks_bool append_mode) { + char path[CLKS_SYSCALL_PATH_MAX]; + void *heap_copy = CLKS_NULL; + const void *payload = CLKS_NULL; + clks_bool ok; + + if (clks_syscall_copy_user_string(arg0, path, sizeof(path)) == CLKS_FALSE) { + return 0ULL; + } + + if (arg2 > CLKS_SYSCALL_FS_IO_MAX_LEN) { + return 0ULL; + } + + if (arg2 > 0ULL) { + if (arg1 == 0ULL) { + return 0ULL; + } + + heap_copy = clks_kmalloc((usize)arg2); + + if (heap_copy == CLKS_NULL) { + return 0ULL; + } + + clks_memcpy(heap_copy, (const void *)arg1, (usize)arg2); + payload = (const void *)heap_copy; + } + + if (append_mode == CLKS_TRUE) { + ok = clks_fs_append(path, payload, arg2); + } else { + ok = clks_fs_write_all(path, payload, arg2); + } + + if (heap_copy != CLKS_NULL) { + clks_kfree(heap_copy); + } + + return (ok == CLKS_TRUE) ? 1ULL : 0ULL; +} + +static u64 clks_syscall_fs_write(u64 arg0, u64 arg1, u64 arg2) { + return clks_syscall_fs_write_common(arg0, arg1, arg2, CLKS_FALSE); +} + +static u64 clks_syscall_fs_append(u64 arg0, u64 arg1, u64 arg2) { + return clks_syscall_fs_write_common(arg0, arg1, arg2, CLKS_TRUE); +} + +static u64 clks_syscall_fs_remove(u64 arg0) { + char path[CLKS_SYSCALL_PATH_MAX]; + + if (clks_syscall_copy_user_string(arg0, path, sizeof(path)) == CLKS_FALSE) { + return 0ULL; + } + + return (clks_fs_remove(path) == CLKS_TRUE) ? 1ULL : 0ULL; +} + +static u64 clks_syscall_log_journal_count(void) { + return clks_log_journal_count(); +} + +static u64 clks_syscall_log_journal_read(u64 arg0, u64 arg1, u64 arg2) { + char line[CLKS_SYSCALL_JOURNAL_MAX_LEN]; + usize line_len; + usize copy_len; + + if (arg1 == 0ULL || arg2 == 0ULL) { + return 0ULL; + } + + if (clks_log_journal_read(arg0, line, sizeof(line)) == CLKS_FALSE) { + return 0ULL; + } + + line_len = clks_strlen(line) + 1U; + copy_len = line_len; + + if (copy_len > (usize)arg2) { + copy_len = (usize)arg2; + } + + if (copy_len > sizeof(line)) { + copy_len = sizeof(line); + } + + clks_memcpy((void *)arg1, line, copy_len); + ((char *)arg1)[copy_len - 1U] = '\0'; + return 1ULL; +} + void clks_syscall_init(void) { clks_syscall_ready = CLKS_TRUE; clks_log(CLKS_LOG_INFO, "SYSCALL", "INT80 FRAMEWORK ONLINE"); @@ -274,6 +410,32 @@ u64 clks_syscall_dispatch(void *frame_ptr) { return clks_syscall_tty_write_char(frame->rbx); case CLKS_SYSCALL_KBD_GET_CHAR: return clks_syscall_kbd_get_char(); + case CLKS_SYSCALL_FS_STAT_TYPE: + return clks_syscall_fs_stat_type(frame->rbx); + case CLKS_SYSCALL_FS_STAT_SIZE: + return clks_syscall_fs_stat_size(frame->rbx); + case CLKS_SYSCALL_FS_MKDIR: + return clks_syscall_fs_mkdir(frame->rbx); + case CLKS_SYSCALL_FS_WRITE: + return clks_syscall_fs_write(frame->rbx, frame->rcx, frame->rdx); + case CLKS_SYSCALL_FS_APPEND: + return clks_syscall_fs_append(frame->rbx, frame->rcx, frame->rdx); + case CLKS_SYSCALL_FS_REMOVE: + return clks_syscall_fs_remove(frame->rbx); + case CLKS_SYSCALL_LOG_JOURNAL_COUNT: + return clks_syscall_log_journal_count(); + case CLKS_SYSCALL_LOG_JOURNAL_READ: + return clks_syscall_log_journal_read(frame->rbx, frame->rcx, frame->rdx); + case CLKS_SYSCALL_KBD_BUFFERED: + return clks_keyboard_buffered_count(); + case CLKS_SYSCALL_KBD_PUSHED: + return clks_keyboard_push_count(); + case CLKS_SYSCALL_KBD_POPPED: + return clks_keyboard_pop_count(); + case CLKS_SYSCALL_KBD_DROPPED: + return clks_keyboard_drop_count(); + case CLKS_SYSCALL_KBD_HOTKEY_SWITCHES: + return clks_keyboard_hotkey_switch_count(); default: return (u64)-1; } diff --git a/docs/README.md b/docs/README.md index 9c171e2..50de0d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,6 +24,10 @@ - `stage25.md` - `stage26.md` +- `syscall.md` (syscall ABI reference) + ## Notes - Stage docs use a fixed template: goal, implementation, acceptance criteria, build targets, QEMU command, and debugging notes. - Stages 16~19 are currently not documented in this folder; add them later using the same template to keep history continuous. + + diff --git a/docs/syscall.md b/docs/syscall.md new file mode 100644 index 0000000..0943009 --- /dev/null +++ b/docs/syscall.md @@ -0,0 +1,316 @@ +# CLeonOS Syscall 文档 + +本文档描述 CLeonOS 用户态通过 `int 0x80` 进入内核的 syscall ABI 与当前实现行为。 + +## 1. 调用约定(x86_64) + +用户态统一通过: + +```c +u64 cleonos_syscall(u64 id, u64 arg0, u64 arg1, u64 arg2); +``` + +寄存器约定: + +- `rax` = syscall id +- `rbx` = arg0 +- `rcx` = arg1 +- `rdx` = arg2 +- 返回值在 `rax` + +中断向量: + +- `int 0x80` + +头文件位置: + +- `cleonos/c/include/cleonos_syscall.h` + +内核分发位置: + +- `clks/kernel/syscall.c` + +## 2. 全局返回规则 + +- 成功时通常返回非负值(如长度、计数、状态)。 +- 失败时多数接口返回 `0xFFFFFFFFFFFFFFFF`(即 `u64` 的 `-1`)。 +- 部分接口失败返回 `0`(例如 `FS_READ` / `FS_WRITE` / `FS_APPEND` / `FS_REMOVE` / `LOG_JOURNAL_READ`)。 + +## 3. 当前实现中的长度/路径限制 + +以下限制由内核 `clks/kernel/syscall.c` 当前实现决定: + +- 日志写入 `LOG_WRITE`:最大拷贝 `191` 字节。 +- TTY 文本写入 `TTY_WRITE`:最大拷贝 `512` 字节。 +- 文件读取 `FS_READ`:最多读取 `min(file_size, buffer_size)` 字节。 +- 文件写入 `FS_WRITE` / `FS_APPEND`:单次最大 `65536` 字节。 +- log journal 行读取缓冲:`256` 字节。 +- 路径缓冲上限:`192` 字节(包含 `\0`)。 +- 文件名输出上限:`96` 字节(与 `CLEONOS_FS_NAME_MAX` 对齐)。 + +文件系统写入类 syscall 的权限限制: + +- `FS_MKDIR` / `FS_WRITE` / `FS_APPEND` / `FS_REMOVE` 仅允许 `/temp` 树下路径。 + +## 4. Syscall 列表(0~39) + +### 0 `CLEONOS_SYSCALL_LOG_WRITE` + +- 参数: +- `arg0`: `const char *message` +- `arg1`: `u64 length` +- 返回:实际写入长度 +- 说明:写入内核日志通道(tag 为 `SYSCALL`),长度会被截断到 191。 + +### 1 `CLEONOS_SYSCALL_TIMER_TICKS` + +- 参数:无 +- 返回:系统 timer tick 计数 + +### 2 `CLEONOS_SYSCALL_TASK_COUNT` + +- 参数:无 +- 返回:任务总数 + +### 3 `CLEONOS_SYSCALL_CUR_TASK` + +- 参数:无 +- 返回:当前任务 ID + +### 4 `CLEONOS_SYSCALL_SERVICE_COUNT` + +- 参数:无 +- 返回:服务总数 + +### 5 `CLEONOS_SYSCALL_SERVICE_READY_COUNT` + +- 参数:无 +- 返回:ready 服务数 + +### 6 `CLEONOS_SYSCALL_CONTEXT_SWITCHES` + +- 参数:无 +- 返回:上下文切换计数 + +### 7 `CLEONOS_SYSCALL_KELF_COUNT` + +- 参数:无 +- 返回:内核态 ELF 应用计数 + +### 8 `CLEONOS_SYSCALL_KELF_RUNS` + +- 参数:无 +- 返回:内核态 ELF 累计运行次数 + +### 9 `CLEONOS_SYSCALL_FS_NODE_COUNT` + +- 参数:无 +- 返回:VFS 节点总数 + +### 10 `CLEONOS_SYSCALL_FS_CHILD_COUNT` + +- 参数: +- `arg0`: `const char *dir_path` +- 返回:子节点数量 + +### 11 `CLEONOS_SYSCALL_FS_GET_CHILD_NAME` + +- 参数: +- `arg0`: `const char *dir_path` +- `arg1`: `u64 index` +- `arg2`: `char *out_name` +- 返回:成功 `1`,失败 `0` +- 说明:最多写入 96 字节(含终止符)。 + +### 12 `CLEONOS_SYSCALL_FS_READ` + +- 参数: +- `arg0`: `const char *path` +- `arg1`: `char *out_buffer` +- `arg2`: `u64 buffer_size` +- 返回:实际读取字节数,失败/文件空返回 `0` +- 说明:不会自动追加 `\0`,调用方应自行处理文本终止。 + +### 13 `CLEONOS_SYSCALL_EXEC_PATH` + +- 参数: +- `arg0`: `const char *path` +- 返回: +- `-1`:请求失败 +- 其他:由内核执行器返回状态(通常 `0` 表示 accepted) + +### 14 `CLEONOS_SYSCALL_EXEC_REQUESTS` + +- 参数:无 +- 返回:执行请求累计数 + +### 15 `CLEONOS_SYSCALL_EXEC_SUCCESS` + +- 参数:无 +- 返回:执行成功累计数 + +### 16 `CLEONOS_SYSCALL_USER_SHELL_READY` + +- 参数:无 +- 返回:用户 shell ready(1/0) + +### 17 `CLEONOS_SYSCALL_USER_EXEC_REQUESTED` + +- 参数:无 +- 返回:是否发起过用户侧 exec 请求(1/0) + +### 18 `CLEONOS_SYSCALL_USER_LAUNCH_TRIES` + +- 参数:无 +- 返回:用户态启动尝试次数 + +### 19 `CLEONOS_SYSCALL_USER_LAUNCH_OK` + +- 参数:无 +- 返回:用户态启动成功次数 + +### 20 `CLEONOS_SYSCALL_USER_LAUNCH_FAIL` + +- 参数:无 +- 返回:用户态启动失败次数 + +### 21 `CLEONOS_SYSCALL_TTY_COUNT` + +- 参数:无 +- 返回:TTY 总数 + +### 22 `CLEONOS_SYSCALL_TTY_ACTIVE` + +- 参数:无 +- 返回:当前 active TTY 索引 + +### 23 `CLEONOS_SYSCALL_TTY_SWITCH` + +- 参数: +- `arg0`: `u64 tty_index` +- 返回:切换后的 active TTY 索引 + +### 24 `CLEONOS_SYSCALL_TTY_WRITE` + +- 参数: +- `arg0`: `const char *text` +- `arg1`: `u64 length` +- 返回:实际写入长度 +- 说明:长度会被截断到 512。 + +### 25 `CLEONOS_SYSCALL_TTY_WRITE_CHAR` + +- 参数: +- `arg0`: `u64 ch`(低 8 位有效) +- 返回:当前实现固定返回 `1` + +### 26 `CLEONOS_SYSCALL_KBD_GET_CHAR` + +- 参数:无 +- 返回: +- 无输入时 `-1` +- 有输入时返回字符值(低 8 位) + +### 27 `CLEONOS_SYSCALL_FS_STAT_TYPE` + +- 参数: +- `arg0`: `const char *path` +- 返回:`1=FILE`,`2=DIR`,失败 `-1` + +### 28 `CLEONOS_SYSCALL_FS_STAT_SIZE` + +- 参数: +- `arg0`: `const char *path` +- 返回:文件大小;目录通常为 `0`;失败 `-1` + +### 29 `CLEONOS_SYSCALL_FS_MKDIR` + +- 参数: +- `arg0`: `const char *path` +- 返回:成功 `1`,失败 `0` +- 说明:仅允许 `/temp` 下创建目录。 + +### 30 `CLEONOS_SYSCALL_FS_WRITE` + +- 参数: +- `arg0`: `const char *path` +- `arg1`: `const char *data` +- `arg2`: `u64 size` +- 返回:成功 `1`,失败 `0` +- 说明:覆盖写;仅允许 `/temp` 下文件。 + +### 31 `CLEONOS_SYSCALL_FS_APPEND` + +- 参数: +- `arg0`: `const char *path` +- `arg1`: `const char *data` +- `arg2`: `u64 size` +- 返回:成功 `1`,失败 `0` +- 说明:追加写;仅允许 `/temp` 下文件。 + +### 32 `CLEONOS_SYSCALL_FS_REMOVE` + +- 参数: +- `arg0`: `const char *path` +- 返回:成功 `1`,失败 `0` +- 说明:仅允许 `/temp` 下删除;目录需为空。 + +### 33 `CLEONOS_SYSCALL_LOG_JOURNAL_COUNT` + +- 参数:无 +- 返回:日志 journal 条目数量 + +### 34 `CLEONOS_SYSCALL_LOG_JOURNAL_READ` + +- 参数: +- `arg0`: `u64 index_from_oldest` +- `arg1`: `char *out_line` +- `arg2`: `u64 out_size` +- 返回:成功 `1`,失败 `0` +- 说明:内核会保证输出字符串有 `\0` 终止。 + +### 35 `CLEONOS_SYSCALL_KBD_BUFFERED` + +- 参数:无 +- 返回:当前键盘队列中的字符数量 + +### 36 `CLEONOS_SYSCALL_KBD_PUSHED` + +- 参数:无 +- 返回:键盘累计入队计数 + +### 37 `CLEONOS_SYSCALL_KBD_POPPED` + +- 参数:无 +- 返回:键盘累计出队计数 + +### 38 `CLEONOS_SYSCALL_KBD_DROPPED` + +- 参数:无 +- 返回:键盘队列溢出丢弃计数 + +### 39 `CLEONOS_SYSCALL_KBD_HOTKEY_SWITCHES` + +- 参数:无 +- 返回:ALT+F1..F4 热键切换计数 + +## 5. 用户态封装函数 + +用户态封装位于: + +- `cleonos/c/src/syscall.c` + +常用封装示例: + +- `cleonos_sys_fs_read()` +- `cleonos_sys_fs_write()` / `cleonos_sys_fs_append()` / `cleonos_sys_fs_remove()` +- `cleonos_sys_log_journal_count()` / `cleonos_sys_log_journal_read()` +- `cleonos_sys_exec_path()` +- `cleonos_sys_tty_write()` +- `cleonos_sys_kbd_get_char()` / `cleonos_sys_kbd_buffered()` + +## 6. 开发注意事项 + +- 传入的字符串/缓冲指针目前按“同地址空间可直接访问”模型处理,后续若引入严格用户态地址隔离,需要补充用户内存校验。 +- `FS_READ` 不保证文本终止符;读取文本请预留 1 字节并手动 `buf[n] = '\0'`。 +- `FS_WRITE`/`FS_APPEND` 仅允许 `/temp`,并有单次长度上限。 \ No newline at end of file diff --git a/ramdisk/shell/init.cmd b/ramdisk/shell/init.cmd index 2c62e92..20f505f 100644 --- a/ramdisk/shell/init.cmd +++ b/ramdisk/shell/init.cmd @@ -2,10 +2,4 @@ # Lines starting with # are comments. help -stats -ls / -ls /shell -ls /system -cat /README.txt -run /hello.elf -stats +stats \ No newline at end of file diff --git a/wine/cleonos_wine.py b/wine/cleonos_wine.py index f8e23d6..4a0e3f0 100644 --- a/wine/cleonos_wine.py +++ b/wine/cleonos_wine.py @@ -73,6 +73,19 @@ SYS_TTY_SWITCH = 23 SYS_TTY_WRITE = 24 SYS_TTY_WRITE_CHAR = 25 SYS_KBD_GET_CHAR = 26 +SYS_FS_STAT_TYPE = 27 +SYS_FS_STAT_SIZE = 28 +SYS_FS_MKDIR = 29 +SYS_FS_WRITE = 30 +SYS_FS_APPEND = 31 +SYS_FS_REMOVE = 32 +SYS_LOG_JOURNAL_COUNT = 33 +SYS_LOG_JOURNAL_READ = 34 +SYS_KBD_BUFFERED = 35 +SYS_KBD_PUSHED = 36 +SYS_KBD_POPPED = 37 +SYS_KBD_DROPPED = 38 +SYS_KBD_HOTKEY_SWITCHES = 39 def u64(value: int) -> int: @@ -126,22 +139,57 @@ class SharedKernelState: tty_active: int = 0 kbd_queue: Deque[int] = field(default_factory=collections.deque) kbd_lock: threading.Lock = field(default_factory=threading.Lock) + kbd_queue_cap: int = 256 + kbd_drop_count: int = 0 + kbd_push_count: int = 0 + kbd_pop_count: int = 0 + kbd_hotkey_switches: int = 0 + log_journal_cap: int = 256 + log_journal: Deque[str] = field(default_factory=lambda: collections.deque(maxlen=256)) + fs_write_max: int = 65536 def timer_ticks(self) -> int: return (time.monotonic_ns() - self.start_ns) // 1_000_000 def push_key(self, key: int) -> None: with self.kbd_lock: - if len(self.kbd_queue) >= 1024: + if len(self.kbd_queue) >= self.kbd_queue_cap: self.kbd_queue.popleft() + self.kbd_drop_count = u64(self.kbd_drop_count + 1) self.kbd_queue.append(key & 0xFF) + self.kbd_push_count = u64(self.kbd_push_count + 1) def pop_key(self) -> Optional[int]: with self.kbd_lock: if not self.kbd_queue: return None + self.kbd_pop_count = u64(self.kbd_pop_count + 1) return self.kbd_queue.popleft() + def buffered_count(self) -> int: + with self.kbd_lock: + return len(self.kbd_queue) + + def log_journal_push(self, text: str) -> None: + if text is None: + return + + normalized = text.replace("\r", "") + lines = normalized.split("\n") + + for line in lines: + if len(line) > 255: + line = line[:255] + self.log_journal.append(line) + + def log_journal_count(self) -> int: + return len(self.log_journal) + + def log_journal_read(self, index_from_oldest: int) -> Optional[str]: + if index_from_oldest < 0 or index_from_oldest >= len(self.log_journal): + return None + return list(self.log_journal)[index_from_oldest] + class InputPump: def __init__(self, state: SharedKernelState) -> None: @@ -321,7 +369,9 @@ class CLeonOSWineNative: def _dispatch_syscall(self, uc: Uc, sid: int, arg0: int, arg1: int, arg2: int) -> int: if sid == SYS_LOG_WRITE: data = self._read_guest_bytes(uc, arg0, arg1) - self._host_write(data.decode("utf-8", errors="replace")) + text = data.decode("utf-8", errors="replace") + self._host_write(text) + self.state.log_journal_push(text) return len(data) if sid == SYS_TIMER_TICKS: return self.state.timer_ticks() @@ -371,7 +421,7 @@ class CLeonOSWineNative: if arg0 >= self.state.tty_count: return u64_neg1() self.state.tty_active = int(arg0) - return 0 + return self.state.tty_active if sid == SYS_TTY_WRITE: data = self._read_guest_bytes(uc, arg0, arg1) self._host_write(data.decode("utf-8", errors="replace")) @@ -382,10 +432,36 @@ class CLeonOSWineNative: self._host_write("\b \b") else: self._host_write(ch) - return 0 + return 1 if sid == SYS_KBD_GET_CHAR: key = self.state.pop_key() return u64_neg1() if key is None else key + if sid == SYS_FS_STAT_TYPE: + return self._fs_stat_type(uc, arg0) + if sid == SYS_FS_STAT_SIZE: + return self._fs_stat_size(uc, arg0) + if sid == SYS_FS_MKDIR: + return self._fs_mkdir(uc, arg0) + if sid == SYS_FS_WRITE: + return self._fs_write(uc, arg0, arg1, arg2) + if sid == SYS_FS_APPEND: + return self._fs_append(uc, arg0, arg1, arg2) + if sid == SYS_FS_REMOVE: + return self._fs_remove(uc, arg0) + if sid == SYS_LOG_JOURNAL_COUNT: + return self.state.log_journal_count() + if sid == SYS_LOG_JOURNAL_READ: + return self._log_journal_read(uc, arg0, arg1, arg2) + if sid == SYS_KBD_BUFFERED: + return self.state.buffered_count() + if sid == SYS_KBD_PUSHED: + return self.state.kbd_push_count + if sid == SYS_KBD_POPPED: + return self.state.kbd_pop_count + if sid == SYS_KBD_DROPPED: + return self.state.kbd_drop_count + if sid == SYS_KBD_HOTKEY_SWITCHES: + return self.state.kbd_hotkey_switches return u64_neg1() @@ -603,6 +679,132 @@ class CLeonOSWineNative: return 0 return len(data) if self._write_guest_bytes(uc, out_ptr, data) else 0 + def _fs_stat_type(self, uc: Uc, path_ptr: int) -> int: + path = self._read_guest_cstring(uc, path_ptr) + host_path = self._guest_to_host(path, must_exist=True) + if host_path is None: + return u64_neg1() + if host_path.is_dir(): + return 2 + if host_path.is_file(): + return 1 + return u64_neg1() + + def _fs_stat_size(self, uc: Uc, path_ptr: int) -> int: + path = self._read_guest_cstring(uc, path_ptr) + host_path = self._guest_to_host(path, must_exist=True) + if host_path is None: + return u64_neg1() + if host_path.is_dir(): + return 0 + if host_path.is_file(): + try: + return host_path.stat().st_size + except Exception: + return u64_neg1() + return u64_neg1() + + @staticmethod + def _guest_path_is_under_temp(path: str) -> bool: + return path == "/temp" or path.startswith("/temp/") + + def _fs_mkdir(self, uc: Uc, path_ptr: int) -> int: + path = self._normalize_guest_path(self._read_guest_cstring(uc, path_ptr)) + if not self._guest_path_is_under_temp(path): + return 0 + + host_path = self._guest_to_host(path, must_exist=False) + if host_path is None: + return 0 + + if host_path.exists() and host_path.is_file(): + return 0 + + try: + host_path.mkdir(parents=True, exist_ok=True) + return 1 + except Exception: + return 0 + + def _fs_write_common(self, uc: Uc, path_ptr: int, data_ptr: int, size: int, append_mode: bool) -> int: + path = self._normalize_guest_path(self._read_guest_cstring(uc, path_ptr)) + + if not self._guest_path_is_under_temp(path) or path == "/temp": + return 0 + + if size < 0 or size > self.state.fs_write_max: + return 0 + + host_path = self._guest_to_host(path, must_exist=False) + if host_path is None: + return 0 + + if host_path.exists() and host_path.is_dir(): + return 0 + + data = b"" + if size > 0: + if data_ptr == 0: + return 0 + data = self._read_guest_bytes(uc, data_ptr, size) + if len(data) != int(size): + return 0 + + try: + host_path.parent.mkdir(parents=True, exist_ok=True) + mode = "ab" if append_mode else "wb" + with host_path.open(mode) as fh: + if data: + fh.write(data) + return 1 + except Exception: + return 0 + + def _fs_write(self, uc: Uc, path_ptr: int, data_ptr: int, size: int) -> int: + return self._fs_write_common(uc, path_ptr, data_ptr, size, append_mode=False) + + def _fs_append(self, uc: Uc, path_ptr: int, data_ptr: int, size: int) -> int: + return self._fs_write_common(uc, path_ptr, data_ptr, size, append_mode=True) + + def _fs_remove(self, uc: Uc, path_ptr: int) -> int: + path = self._normalize_guest_path(self._read_guest_cstring(uc, path_ptr)) + + if not self._guest_path_is_under_temp(path) or path == "/temp": + return 0 + + host_path = self._guest_to_host(path, must_exist=True) + if host_path is None: + return 0 + + try: + if host_path.is_dir(): + if any(host_path.iterdir()): + return 0 + host_path.rmdir() + else: + host_path.unlink() + return 1 + except Exception: + return 0 + + def _log_journal_read(self, uc: Uc, index_from_oldest: int, out_ptr: int, out_size: int) -> int: + if out_ptr == 0 or out_size == 0: + return 0 + + line = self.state.log_journal_read(int(index_from_oldest)) + if line is None: + return 0 + + encoded = line.encode("utf-8", errors="replace") + max_payload = int(out_size) - 1 + if max_payload < 0: + return 0 + + if len(encoded) > max_payload: + encoded = encoded[:max_payload] + + return 1 if self._write_guest_bytes(uc, out_ptr, encoded + b"\x00") else 0 + def _exec_path(self, uc: Uc, path_ptr: int) -> int: path = self._read_guest_cstring(uc, path_ptr) guest_path = self._normalize_guest_path(path)