diff --git a/src/userland/cli/bsh.c b/src/userland/cli/bsh.c index 35b10a1..5f329df 100644 --- a/src/userland/cli/bsh.c +++ b/src/userland/cli/bsh.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "utf-8.h" #define MAX_LINE 512 @@ -37,6 +38,8 @@ typedef struct { static bsh_config_t g_cfg; static char g_paths[MAX_PATHS][MAX_PATH_LEN]; static int g_path_count = 0; +static char g_resolved_command_path[256]; +static int g_resolve_status = -1; static char g_history[MAX_HISTORY][MAX_LINE]; static int g_history_count = 0; @@ -665,55 +668,138 @@ static bool is_elf_file(const char *path) { return hdr[0] == 0x7F && hdr[1] == 'E' && hdr[2] == 'L' && hdr[3] == 'F'; } -static int resolve_command(const char *cmd, char *out, int out_len) { - if (!cmd || !cmd[0]) return -1; - - bool has_slash = false; +static bool contains_slash(const char *cmd) { + if (!cmd) return false; for (int i = 0; cmd[i]; i++) { - if (cmd[i] == '/') { has_slash = true; break; } + if (cmd[i] == '/') return true; + } + return false; +} + +static const char *env_get_value(char *const envp[], const char *name) { + int name_len; + + if (!envp || !name || !name[0]) return NULL; + + name_len = (int)strlen(name); + + for (int i = 0; envp[i]; i++) { + if (starts_with(envp[i], name) && envp[i][name_len] == '=') { + return envp[i] + name_len + 1; + } } - if (has_slash) { - if (sys_exists(cmd)) { - if (!is_file_path(cmd)) return -2; - if (!is_elf_file(cmd)) return -3; - str_copy(out, cmd, out_len); - return 0; - } - if (!ends_with(cmd, ".elf")) { - char temp[256]; - str_copy(temp, cmd, sizeof(temp)); - str_append(temp, ".elf", sizeof(temp)); - if (sys_exists(temp)) { - if (!is_file_path(temp)) return -2; - if (!is_elf_file(temp)) return -3; - str_copy(out, temp, out_len); - return 0; + return NULL; +} + +static void build_path_candidate(char *out, int out_len, const char *dir, int dir_len, const char *cmd) { + int pos = 0; + + if (!out || out_len <= 0) return; + out[0] = 0; + if (!dir || !cmd) return; + + for (int i = 0; i < dir_len && dir[i] && pos < out_len - 1; i++) { + out[pos++] = dir[i]; + } + out[pos] = 0; + + if (pos > 0 && out[pos - 1] != '/' && pos < out_len - 1) { + out[pos++] = '/'; + out[pos] = 0; + } + + for (int i = 0; cmd[i] && pos < out_len - 1; i++) { + out[pos++] = cmd[i]; + } + out[pos] = 0; +} + +static int accept_command_candidate(const char *candidate) { + if (access(candidate, X_OK) != 0) return -1; + if (!is_file_path(candidate)) return -2; + if (!is_elf_file(candidate)) return -3; + + str_copy(g_resolved_command_path, candidate, sizeof(g_resolved_command_path)); + return 0; +} + +static char *resolve_in_path_string(const char *cmd, const char *path_str) { + int first_error = -1; + int start = 0; + int i = 0; + + if (!path_str || !path_str[0]) { + g_resolve_status = -1; + return NULL; + } + + while (1) { + if (path_str[i] == ':' || path_str[i] == 0) { + int len = i - start; + + if (len > 0) { + char candidate[256]; + int res; + + build_path_candidate(candidate, sizeof(candidate), path_str + start, len, cmd); + res = accept_command_candidate(candidate); + if (res == 0) { + g_resolve_status = 0; + return g_resolved_command_path; + } + if (res != -1 && first_error == -1) first_error = res; + + if (!ends_with(cmd, ".elf")) { + str_append(candidate, ".elf", sizeof(candidate)); + res = accept_command_candidate(candidate); + if (res == 0) { + g_resolve_status = 0; + return g_resolved_command_path; + } + if (res != -1 && first_error == -1) first_error = res; + } } + + start = i + 1; } - return -1; + + if (path_str[i] == 0) break; + i++; } - for (int i = 0; i < g_path_count; i++) { - char temp[256]; - temp[0] = 0; - str_copy(temp, g_paths[i], sizeof(temp)); - if (temp[0] && temp[strlen(temp) - 1] != '/') str_append(temp, "/", sizeof(temp)); - str_append(temp, cmd, sizeof(temp)); - if (sys_exists(temp) && is_file_path(temp) && is_elf_file(temp)) { - str_copy(out, temp, out_len); - return 0; - } - if (!ends_with(cmd, ".elf")) { - str_append(temp, ".elf", sizeof(temp)); - if (sys_exists(temp) && is_file_path(temp) && is_elf_file(temp)) { - str_copy(out, temp, out_len); - return 0; - } - } + g_resolve_status = first_error; + return NULL; +} + +static char *resolve_command_path(const char *cmd, char *const envp[]) { + const char *path; + int res; + + g_resolved_command_path[0] = 0; + g_resolve_status = -1; + + if (!cmd || !cmd[0]) return NULL; + + if (contains_slash(cmd)) { + res = accept_command_candidate(cmd); + g_resolve_status = res; + return (res == 0) ? g_resolved_command_path : NULL; } - return -1; + path = env_get_value(envp, "PATH"); + if (!path) path = g_cfg.path; + + return resolve_in_path_string(cmd, path); +} + +static int resolve_command(const char *cmd, char *out, int out_len) { + char *resolved = resolve_command_path(cmd, NULL); + + if (!resolved) return g_resolve_status; + + str_copy(out, resolved, out_len); + return 0; } static void build_args_string(int argc, char *argv[], int start, char *out, int out_len) { @@ -783,7 +869,7 @@ static int collect_command_matches(const char *prefix, char matches[][MAX_MATCH_ int count = 0; const char *builtins[] = { "cd", "pwd", "ls", "cat", "echo", "clear", "mkdir", "rm", - "touch", "cp", "mv", "man", "alias", "unalias", ".", "exit" + "touch", "cp", "mv", "man", "alias", "unalias", "time", ".", "exit" }; for (int i = 0; i < (int)(sizeof(builtins) / sizeof(builtins[0])); i++) { if (starts_with(builtins[i], prefix)) count = add_match_unique(matches, count, builtins[i]); @@ -897,22 +983,145 @@ static void show_matches(const char *prompt_tmpl, const char *line, int len, cha redraw_input(prompt_tmpl, line, len, len); } -static void wait_for_pid(int pid) { +static int wait_for_pid_status(int pid, int *status) { while (1) { - int rc = sys_waitpid(pid, NULL, 1); - if (rc == pid || rc < 0) break; + int child_status = 0; + int rc = sys_waitpid(pid, &child_status, 1); + if (rc == pid) { + if (status) *status = child_status; + return 0; + } + if (rc < 0) return -1; if (g_tty_id >= 0) { int fg = sys_tty_get_fg(g_tty_id); - if (fg != pid) break; + if (fg != pid) return -1; } sleep(10); } } +static void wait_for_pid(int pid) { + wait_for_pid_status(pid, NULL); +} + static void cmd_clear(void) { sys_write(1, "\x1b[2J\x1b[H", 7); } +static void print_command_resolution_error(const char *who, const char *cmd, int res) { + set_color(g_color_error); + if (res == -2) { + printf("%s: is a directory: %s\n", who, cmd); + } else if (res == -3) { + printf("%s: not executable: %s\n", who, cmd); + } else { + printf("%s: command not found: %s\n", who, cmd); + } + reset_color(); +} + +static void builtin_time_usage(void) { + printf("Usage: time [args...]\n"); + printf("\n"); + printf("Examples:\n"); + printf(" time ls\n"); + printf(" time hexdump file.txt\n"); + printf(" time /bin/hexdump.elf file.txt\n"); +} + +// Reads the system uptime in milliseconds by parsing /proc/uptime +// before and after running the command, then calculating the difference. +static unsigned long long read_uptime_ms(void) { + char buf[64]; + int fd; + int bytes; + int seconds; + + fd = sys_open("/proc/uptime", "r"); + if (fd < 0) return 0; + + bytes = sys_read(fd, buf, sizeof(buf) - 1); + sys_close(fd); + + if (bytes <= 0) return 0; + + buf[bytes] = 0; + seconds = atoi(buf); + + return (unsigned long long)seconds * 1000ULL; +} + +static int builtin_time(int argc, char *argv[]) { + char *resolved; + char full_path[256]; + char args_buf[256]; + char cmdline[MAX_LINE]; + unsigned long long start; + unsigned long long end; + unsigned long long elapsed; + int pid = -1; + int ret = -1; + + if (argc < 2) { + builtin_time_usage(); + return 1; + } + + if (str_eq(argv[1], "-h") || str_eq(argv[1], "--help")) { + builtin_time_usage(); + return 0; + } + + resolved = resolve_command_path(argv[1], NULL); + if (!resolved) { + print_command_resolution_error("time", argv[1], g_resolve_status); + return 1; + } + str_copy(full_path, resolved, sizeof(full_path)); + + build_args_string(argc, argv, 2, args_buf, sizeof(args_buf)); + + str_copy(cmdline, full_path, sizeof(cmdline)); + if (args_buf[0]) { + str_append(cmdline, " ", sizeof(cmdline)); + str_append(cmdline, args_buf, sizeof(cmdline)); + } + + start = read_uptime_ms(); + + for (int attempt = 0; attempt < 5; attempt++) { + pid = sys_spawn(full_path, args_buf[0] ? args_buf : NULL, SPAWN_FLAG_TERMINAL | SPAWN_FLAG_INHERIT_TTY, 0); + if (pid >= 0) break; + sleep(10); + } + + if (pid >= 0) { + if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, pid); + if (wait_for_pid_status(pid, &ret) != 0) ret = -1; + if (g_tty_id >= 0) sys_tty_set_fg(g_tty_id, 0); + } + + end = read_uptime_ms(); + + if (end >= start) elapsed = end - start; + else elapsed = 0; + + printf("\n"); + printf("Command: %s\n", cmdline); + printf("Exit code: %d\n", ret); + + if (ret == -1) { + printf("Command failed with non-zero exit code, not reporting time.\n"); + return ret; + } + + printf("Elapsed: %llu ms\n", elapsed); + + sys_system(SYSTEM_CMD_SLEEP, 1, 0, 0, 0); + + return ret; +} + static int builtin_cd(int argc, char *argv[]) { const char *path = (argc > 1) ? argv[1] : "/"; if (chdir(path) != 0) { @@ -1292,6 +1501,7 @@ static int execute_builtin(int argc, char *argv[]) { if (str_eq(argv[0], "man")) return builtin_man(argc, argv); if (str_eq(argv[0], "alias")) return builtin_alias(argc, argv); if (str_eq(argv[0], "unalias")) return builtin_unalias(argc, argv); + if (str_eq(argv[0], "time")) return builtin_time(argc, argv); if (str_eq(argv[0], ".")) { if (argc < 2) { set_color(g_color_error); @@ -1347,22 +1557,8 @@ static int execute_line_inner(const char *line, int depth) { char full_path[256]; int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path)); - if (cmd_res == -2) { - set_color(g_color_error); - printf("bsh: is a directory: %s\n", argv[0]); - reset_color(); - return 1; - } - if (cmd_res == -3) { - set_color(g_color_error); - printf("bsh: not executable: %s\n", argv[0]); - reset_color(); - return 1; - } if (cmd_res != 0) { - set_color(g_color_error); - printf("bsh: command not found: %s\n", argv[0]); - reset_color(); + print_command_resolution_error("bsh", argv[0], cmd_res); return 1; } diff --git a/src/userland/cli/time.c b/src/userland/cli/time.c deleted file mode 100644 index cfd13db..0000000 --- a/src/userland/cli/time.c +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) 2026 Lluciocc (https://github.com/lluciocc) -// This software is released under the GNU General Public License v3.0. See LICENSE file for details. -// This header needs to maintain in any file it is present in, as per the GPL license terms. - -#include "stdlib.h" -#include "syscall.h" - -// CMDLINE_MAX includes the trailing NUL, so at most 511 command-line bytes can -// be reconstructed here. Unchecked concatenation can overflow CMDLINE_MAX; all -// command-line construction in this file must go through the checked append -// helpers below. -#define CMDLINE_MAX 512 - -static int has_slash(const char *s) { - while (s && *s) { - if (*s == '/') - return 1; - s++; - } - - return 0; -} - -static int ends_with_elf(const char *s) { - int len; - - if (!s) - return 0; - - len = strlen(s); - - if (len < 4) - return 0; - - return strcmp(s + len - 4, ".elf") == 0; -} - -static void print_usage(void) { - printf("Usage: time [args...]\n"); - printf("\n"); - printf("Examples:\n"); - printf(" time ls\n"); - printf(" time hexdump file.txt\n"); - printf(" time /bin/hexdump.elf file.txt\n"); -} - -// Read the system uptime in milliseconds by reading /proc/uptime and parsing the first number (seconds). -static unsigned long long read_uptime_ms(void) { - char buf[64]; - int fd; - int bytes; - int seconds; - - fd = sys_open("/proc/uptime", "r"); - - if (fd < 0) - return 0; - - bytes = sys_read(fd, buf, sizeof(buf) - 1); - - sys_close(fd); - - if (bytes <= 0) - return 0; - - buf[bytes] = 0; - - seconds = atoi(buf); - - return (unsigned long long)seconds * 1000ULL; -} - -// Build the command line for execution -// If the first argument contains a slash, use it as is. Otherwise, prepend "/bin/" and append ".elf" if it doesn't already end with ".elf". -static void build_command_line(int argc, char **argv, char *out) { - int i; - - out[0] = 0; - - if (has_slash(argv[1])) { - strcat(out, argv[1]); - } else { - strcat(out, "/bin/"); - strcat(out, argv[1]); - - if (!ends_with_elf(argv[1])) { - strcat(out, ".elf"); - } - } - - for (i = 2; i < argc; i++) { - strcat(out, " "); - strcat(out, argv[i]); - } -} - -int main(int argc, char **argv) { - char cmdline[CMDLINE_MAX]; - - unsigned long long start; - unsigned long long end; - unsigned long long elapsed; - - int ret; - - if (argc < 2) { - print_usage(); - return 1; - } - - if (strcmp(argv[1], "-h") == 0 || - strcmp(argv[1], "--help") == 0) { - print_usage(); - return 0; - } - - build_command_line(argc, argv, cmdline); - - start = read_uptime_ms(); - - ret = system(cmdline); - - end = read_uptime_ms(); - - if (end >= start) - elapsed = end - start; - else - elapsed = 0; - - printf("\n"); - printf("Command: %s\n", cmdline); - printf("Exit code: %d\n", ret); - - if (ret == -1) { - printf("Command failed with non-zero exit code, not reporting time.\n"); - return ret; - } - - printf("Elapsed: %llu ms\n", elapsed); - - sys_system(SYSTEM_CMD_SLEEP, 1, 0, 0, 0); - - return ret; -}