Feat: Implementing time command inside bsh.c

This commit is contained in:
Lluciocc 2026-05-14 00:47:47 +02:00
parent 29e1b362ff
commit fdd25b31cd
2 changed files with 256 additions and 204 deletions

View file

@ -4,6 +4,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <syscall.h> #include <syscall.h>
#include <stdbool.h> #include <stdbool.h>
#include <unistd.h>
#include "utf-8.h" #include "utf-8.h"
#define MAX_LINE 512 #define MAX_LINE 512
@ -37,6 +38,8 @@ typedef struct {
static bsh_config_t g_cfg; static bsh_config_t g_cfg;
static char g_paths[MAX_PATHS][MAX_PATH_LEN]; static char g_paths[MAX_PATHS][MAX_PATH_LEN];
static int g_path_count = 0; 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 char g_history[MAX_HISTORY][MAX_LINE];
static int g_history_count = 0; 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'; 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) { static bool contains_slash(const char *cmd) {
if (!cmd || !cmd[0]) return -1; if (!cmd) return false;
bool has_slash = false;
for (int i = 0; cmd[i]; i++) { for (int i = 0; cmd[i]; i++) {
if (cmd[i] == '/') { has_slash = true; break; } if (cmd[i] == '/') return true;
}
return false;
} }
if (has_slash) { static const char *env_get_value(char *const envp[], const char *name) {
if (sys_exists(cmd)) { int name_len;
if (!is_file_path(cmd)) return -2;
if (!is_elf_file(cmd)) return -3; if (!envp || !name || !name[0]) return NULL;
str_copy(out, cmd, out_len);
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;
}
}
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; 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")) { if (!ends_with(cmd, ".elf")) {
char temp[256]; str_append(candidate, ".elf", sizeof(candidate));
str_copy(temp, cmd, sizeof(temp)); res = accept_command_candidate(candidate);
str_append(temp, ".elf", sizeof(temp)); if (res == 0) {
if (sys_exists(temp)) { g_resolve_status = 0;
if (!is_file_path(temp)) return -2; return g_resolved_command_path;
if (!is_elf_file(temp)) return -3;
str_copy(out, temp, out_len);
return 0;
}
}
return -1;
}
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;
} }
if (res != -1 && first_error == -1) first_error = res;
} }
} }
return -1; start = i + 1;
}
if (path_str[i] == 0) break;
i++;
}
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;
}
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) { 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; int count = 0;
const char *builtins[] = { const char *builtins[] = {
"cd", "pwd", "ls", "cat", "echo", "clear", "mkdir", "rm", "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++) { 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]); 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); 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) { while (1) {
int rc = sys_waitpid(pid, NULL, 1); int child_status = 0;
if (rc == pid || rc < 0) break; 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) { if (g_tty_id >= 0) {
int fg = sys_tty_get_fg(g_tty_id); int fg = sys_tty_get_fg(g_tty_id);
if (fg != pid) break; if (fg != pid) return -1;
} }
sleep(10); sleep(10);
} }
} }
static void wait_for_pid(int pid) {
wait_for_pid_status(pid, NULL);
}
static void cmd_clear(void) { static void cmd_clear(void) {
sys_write(1, "\x1b[2J\x1b[H", 7); 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 <command> [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[]) { static int builtin_cd(int argc, char *argv[]) {
const char *path = (argc > 1) ? argv[1] : "/"; const char *path = (argc > 1) ? argv[1] : "/";
if (chdir(path) != 0) { 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], "man")) return builtin_man(argc, argv);
if (str_eq(argv[0], "alias")) return builtin_alias(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], "unalias")) return builtin_unalias(argc, argv);
if (str_eq(argv[0], "time")) return builtin_time(argc, argv);
if (str_eq(argv[0], ".")) { if (str_eq(argv[0], ".")) {
if (argc < 2) { if (argc < 2) {
set_color(g_color_error); set_color(g_color_error);
@ -1347,22 +1557,8 @@ static int execute_line_inner(const char *line, int depth) {
char full_path[256]; char full_path[256];
int cmd_res = resolve_command(argv[0], full_path, sizeof(full_path)); 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) { if (cmd_res != 0) {
set_color(g_color_error); print_command_resolution_error("bsh", argv[0], cmd_res);
printf("bsh: command not found: %s\n", argv[0]);
reset_color();
return 1; return 1;
} }

View file

@ -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 <command> [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;
}