From bdd43f43cd5fb63f0fb032f8e198fc198706f628 Mon Sep 17 00:00:00 2001 From: boreddevnl Date: Wed, 15 Apr 2026 22:47:24 +0200 Subject: [PATCH] FEATURE: add Bsh + userspace terminal, remove legacy cmd/cli utils --- Makefile | 4 + docs/usage/terminal.md | 29 +- src/core/main.c | 13 +- src/dev/ps2.c | 12 +- src/fs/procfs.c | 4 + src/library/bsh/boot.bsh | 4 + src/library/bsh/bshrc | 35 + src/library/bsh/startup.bsh | 4 + src/{wm => sys}/cmd.h | 13 +- src/sys/cmd_stub.c | 76 ++ src/sys/process.c | 112 +- src/sys/process.h | 3 + src/sys/syscall.c | 135 +- src/sys/tty.c | 158 +++ src/sys/tty.h | 18 + src/userland/cli/bsh.c | 1449 ++++++++++++++++++++++ src/userland/cli/cat.c | 32 - src/userland/cli/cp.c | 166 --- src/userland/cli/ls.c | 66 - src/userland/cli/mkdir.c | 28 - src/userland/cli/mv.c | 128 -- src/userland/cli/rm.c | 29 - src/userland/cli/touch.c | 27 - src/userland/gui/terminal.c | 893 ++++++++++++++ src/userland/gui/txtedit.c | 46 +- src/userland/gui/viewer.c | 48 +- src/userland/libc/stdlib.c | 2 +- src/userland/libc/syscall.c | 40 + src/userland/libc/syscall.h | 25 + src/userland/net/telnet.c | 58 +- src/wm/cmd.c | 2332 ----------------------------------- src/wm/explorer.c | 3 +- src/wm/explorer.h | 1 - src/wm/wm.c | 18 +- 34 files changed, 3099 insertions(+), 2912 deletions(-) create mode 100644 src/library/bsh/boot.bsh create mode 100644 src/library/bsh/bshrc create mode 100644 src/library/bsh/startup.bsh rename src/{wm => sys}/cmd.h (77%) create mode 100644 src/sys/cmd_stub.c create mode 100644 src/sys/tty.c create mode 100644 src/sys/tty.h create mode 100644 src/userland/cli/bsh.c delete mode 100644 src/userland/cli/cat.c delete mode 100644 src/userland/cli/cp.c delete mode 100644 src/userland/cli/ls.c delete mode 100644 src/userland/cli/mkdir.c delete mode 100644 src/userland/cli/mv.c delete mode 100644 src/userland/cli/rm.c delete mode 100644 src/userland/cli/touch.c create mode 100644 src/userland/gui/terminal.c delete mode 100644 src/wm/cmd.c diff --git a/Makefile b/Makefile index 6ec10bd..ad1d384 100644 --- a/Makefile +++ b/Makefile @@ -139,6 +139,7 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) mkdir -p $(BUILD_DIR)/initrd/Library/images/gif mkdir -p $(BUILD_DIR)/initrd/Library/Fonts/Emoji mkdir -p $(BUILD_DIR)/initrd/Library/DOOM + mkdir -p $(BUILD_DIR)/initrd/Library/bsh mkdir -p $(BUILD_DIR)/initrd/docs @for f in $(SRC_DIR)/userland/bin/*.elf; do \ @@ -156,6 +157,9 @@ $(BUILD_DIR)/initrd.tar: $(KERNEL_ELF) @for f in $(SRC_DIR)/fonts/Emoji/*.ttf; do \ if [ -f "$$f" ]; then cp "$$f" $(BUILD_DIR)/initrd/Library/Fonts/Emoji/; fi \ done + @if [ -f $(SRC_DIR)/library/bsh/bshrc ]; then cp $(SRC_DIR)/library/bsh/bshrc $(BUILD_DIR)/initrd/Library/bsh/; fi + @if [ -f $(SRC_DIR)/library/bsh/startup.bsh ]; then cp $(SRC_DIR)/library/bsh/startup.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi + @if [ -f $(SRC_DIR)/library/bsh/boot.bsh ]; then cp $(SRC_DIR)/library/bsh/boot.bsh $(BUILD_DIR)/initrd/Library/bsh/; fi @if [ -f $(SRC_DIR)/userland/games/doom/doom1.wad ]; then cp $(SRC_DIR)/userland/games/doom/doom1.wad $(BUILD_DIR)/initrd/Library/DOOM/; fi @for f in $$(find docs -name '*.md' 2>/dev/null); do \ if [ -f "$$f" ]; then \ diff --git a/docs/usage/terminal.md b/docs/usage/terminal.md index 223d472..c335998 100644 --- a/docs/usage/terminal.md +++ b/docs/usage/terminal.md @@ -4,7 +4,7 @@ The BoredOS Terminal provides a powerful command-line interface (CLI) for advanc ## The Shell -The default shell in BoredOS is a custom-built, lightweight command processor integrated into the Window Manager. It features: +The default shell in BoredOS is **BoredShell (Bsh)**, a userspace shell with a dedicated terminal app. It features: - **ANSI Color Support**: Rich text output with colors and styles. - **Command History**: Use the **Up** and **Down** arrow keys to navigate through your previous commands (up to 64 history entries). - **Output Redirection**: @@ -13,6 +13,33 @@ The default shell in BoredOS is a custom-built, lightweight command processor in - **Piping**: - `command1 | command2`: Pass the output of the first command as input to the second. +### Bsh Configuration + +Bsh loads its configuration from: + +`/Library/bsh/bshrc` + +This file is similar to `.zshrc` and can define: +- `PATH` for command lookup +- `STARTUP` for interactive shell startup scripts +- `BOOT_SCRIPT` for a once-per-boot script +- prompt templates (`PROMPT_LEFT`, `PROMPT_RIGHT`) + +Prompt tokens: +- `%n` username +- `%h` hostname +- `%~` cwd ("~" for `/root`) +- `%T` time (HH:MM) + +Example: + +``` +PATH=/bin:/root/Apps +PROMPT_LEFT=%n@%h:%~$ +STARTUP=/Library/bsh/startup.bsh +BOOT_SCRIPT=/Library/bsh/boot.bsh +``` + ## Common Commands Below are some of the most used commands available in `/bin`: diff --git a/src/core/main.c b/src/core/main.c index fdc61ff..3f4d992 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -242,7 +242,6 @@ void kmain(void) { log_ok("Graphics and Console ready"); if (memmap_request.response != NULL) { - // The memory manager will now scan the memory map and manage all usable regions. memory_manager_init_from_memmap(memmap_request.response); log_ok("Memory manager ready"); smp_init_bsp(); @@ -278,17 +277,15 @@ void kmain(void) { fat32_mkdir("/Library/images/gif"); fat32_mkdir("/Library/Fonts"); fat32_mkdir("/Library/DOOM"); + fat32_mkdir("/Library/bsh"); fat32_mkdir("/docs"); - // Initialize Virtual Filesystems sysfs_init_subsystems(); vfs_mount("/sys", "sysfs", "sysfs", sysfs_get_ops(), NULL); vfs_mount("/proc", "procfs", "procfs", procfs_get_ops(), NULL); - // Initialize bootfs with default values bootfs_init(); - // Populate bootfs with real Limine bootloader information BEFORE mounting if (bootloader_info_request.response != NULL) { if (bootloader_info_request.response->name) { k_strcpy(g_bootfs_state.bootloader_name, bootloader_info_request.response->name); @@ -298,7 +295,6 @@ void kmain(void) { } } - // Get kernel size from kernel file request if (kernel_file_request.response != NULL && kernel_file_request.response->kernel_file != NULL) { g_bootfs_state.kernel_size = kernel_file_request.response->kernel_file->size; serial_write("[INIT] Kernel size from bootloader: "); @@ -306,16 +302,13 @@ void kmain(void) { serial_write(" bytes\n"); } - // Set boot time to current ticks extern uint32_t wm_get_ticks(void); g_bootfs_state.boot_time_ms = wm_get_ticks(); - // BEFORE mounting bootfs, capture initrd from Limine modules if (module_request.response != NULL) { g_bootfs_state.num_modules = module_request.response->module_count; serial_write("[INIT] Scanning modules for bootfs state...\n"); - // Scan modules to find initrd for (uint64_t i = 0; i < module_request.response->module_count; i++) { struct limine_file *mod = module_request.response->modules[i]; const char *path = mod->path; @@ -379,12 +372,10 @@ void kmain(void) { fat32_close(fh); } } - // Register all discovered modules in our module manager for /sys/module module_manager_register(clean_path, (uint64_t)mod->address, mod->size); } } - // Initialize fonts now that FAT32 and modules are loaded uint64_t current_rsp; asm volatile("mov %%rsp, %0" : "=r"(current_rsp)); serial_write("[INIT] Stack Alignment: 0x"); @@ -397,10 +388,8 @@ void kmain(void) { ps2_init(); asm("sti"); - // Initialize LAPIC for IPI support lapic_init(); - // Initialize SMP if (smp_request.response != NULL) { uint32_t online = smp_init(smp_request.response); log_ok("SMP initialized"); diff --git a/src/dev/ps2.c b/src/dev/ps2.c index 356a363..660ec37 100644 --- a/src/dev/ps2.c +++ b/src/dev/ps2.c @@ -92,17 +92,7 @@ uint64_t keyboard_handler(registers_t *regs) { extended_scancode = false; } - if (ps2_ctrl_pressed && scancode == 0x2E) { - extern process_t* process_get_current(void); - process_t* proc = process_get_current(); - if (proc && proc->is_user && proc->is_terminal_proc && proc->ui_window) { - if (((Window*)proc->ui_window)->focused) { - extern uint64_t process_terminate_current(void); - outb(0x20, 0x20); - return process_terminate_current(); - } - } - } + if (scancode == 0x2A || scancode == 0x36) { // Shift Down shift_pressed = true; diff --git a/src/fs/procfs.c b/src/fs/procfs.c index 0dab24d..a45022f 100644 --- a/src/fs/procfs.c +++ b/src/fs/procfs.c @@ -297,6 +297,9 @@ int procfs_read(void *fs_private, void *handle, void *buf, int size) { if (k_strcmp(h->type, "name") == 0 || k_strcmp(h->type, "cmdline") == 0) { k_strcpy(out, proc->name); k_strcpy(out + k_strlen(out), "\n"); + } else if (k_strcmp(h->type, "cwd") == 0) { + k_strcpy(out, proc->cwd); + k_strcpy(out + k_strlen(out), "\n"); } else if (k_strcmp(h->type, "status") == 0) { k_strcpy(out, "Name: "); k_strcpy(out + k_strlen(out), proc->name); @@ -387,6 +390,7 @@ int procfs_readdir(void *fs_private, const char *path, vfs_dirent_t *entries, in k_strcpy(entries[out++].name, "name"); k_strcpy(entries[out++].name, "status"); k_strcpy(entries[out++].name, "cmdline"); + k_strcpy(entries[out++].name, "cwd"); k_strcpy(entries[out++].name, "signal"); for(int i=0; i +#include +#include void cmd_init(void); void cmd_reset(void); -// IO Functions void cmd_write(const char *str); +void cmd_write_len(const char *str, size_t len); void cmd_putchar(char c); void cmd_write_int(int n); void cmd_write_hex(uint64_t n); @@ -22,10 +22,9 @@ void cmd_screen_clear(void); void cmd_increment_msg_count(void); void cmd_reset_msg_count(void); -void cmd_handle_resize(Window *win, int w, int h); -void cmd_handle_click(Window *win, int x, int y); - uint32_t cmd_get_config_value(const char *key); void cmd_set_current_color(uint32_t color); +void cmd_set_raw_mode(bool enabled); +void cmd_process_finished(void); #endif \ No newline at end of file diff --git a/src/sys/cmd_stub.c b/src/sys/cmd_stub.c new file mode 100644 index 0000000..0538392 --- /dev/null +++ b/src/sys/cmd_stub.c @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// 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 "cmd.h" +#include "core/kutils.h" + +extern void serial_write(const char *str); + +static void serial_write_char(char c) { + char buf[2] = { c, 0 }; + serial_write(buf); +} + +void cmd_init(void) { +} + +void cmd_reset(void) { +} + +void cmd_write(const char *str) { + if (!str) return; + serial_write(str); +} + +void cmd_write_len(const char *str, size_t len) { + if (!str || len == 0) return; + for (size_t i = 0; i < len; i++) { + serial_write_char(str[i]); + } +} + +void cmd_putchar(char c) { + serial_write_char(c); +} + +void cmd_write_int(int n) { + char buf[32]; + k_itoa(n, buf); + cmd_write(buf); +} + +void cmd_write_hex(uint64_t n) { + char buf[17]; + k_itoa_hex(n, buf); + cmd_write("0x"); + cmd_write(buf); +} + +int cmd_get_cursor_col(void) { + return 0; +} + +void cmd_screen_clear(void) { +} + +void cmd_increment_msg_count(void) { +} + +void cmd_reset_msg_count(void) { +} + +uint32_t cmd_get_config_value(const char *key) { + (void)key; + return 0; +} + +void cmd_set_current_color(uint32_t color) { + (void)color; +} + +void cmd_set_raw_mode(bool enabled) { + (void)enabled; +} + +void cmd_process_finished(void) { +} diff --git a/src/sys/process.c b/src/sys/process.c index e7268f5..057e048 100644 --- a/src/sys/process.c +++ b/src/sys/process.c @@ -27,23 +27,24 @@ static uint64_t free_pml4_later[MAX_CPUS_SCHED] = {0}; static spinlock_t runqueue_lock = SPINLOCK_INIT; static uint32_t next_cpu_assign = 1; +static void process_cleanup_inner(process_t *proc); + void process_init(void) { for (int i = 0; i < MAX_PROCESSES; i++) { processes[i].pid = 0xFFFFFFFF; } - // Current kernel execution is PID 0 process_t *kernel_proc = &processes[0]; kernel_proc->pid = next_pid++; kernel_proc->is_user = false; kernel_proc->is_idle = true; + kernel_proc->tty_id = -1; + kernel_proc->kill_pending = false; - // We don't have its RSP or PML4 yet, but it's already running. - // The timer interrupt will naturally capture its context on the first tick! + kernel_proc->pml4_phys = paging_get_pml4_phys(); kernel_proc->kernel_stack = 0; - // Initialize FPU/SSE state for kernel (first interrupt will capture it on stack) kernel_proc->fpu_initialized = true; for (int i = 0; i < MAX_PROCESS_FDS; i++) kernel_proc->fds[i] = NULL; @@ -78,11 +79,14 @@ process_t* process_create(void (*entry_point)(void), bool is_user) { new_proc->pid = next_pid++; new_proc->is_user = is_user; + new_proc->tty_id = -1; + new_proc->kill_pending = false; process_t *parent = process_get_current(); if (parent) { extern void mem_memcpy(void *dest, const void *src, size_t len); mem_memcpy(new_proc->cwd, parent->cwd, 1024); + new_proc->tty_id = parent->tty_id; } else { mem_memset(new_proc->cwd, 0, 1024); new_proc->cwd[0] = '/'; @@ -207,6 +211,18 @@ process_t* process_create_elf(const char* filepath, const char* args_str) { new_proc->heap_start = 0x20000000; // 512MB mark new_proc->heap_end = 0x20000000; new_proc->is_terminal_proc = false; + new_proc->tty_id = -1; + new_proc->kill_pending = false; + + process_t *parent = process_get_current(); + if (parent) { + extern void mem_memcpy(void *dest, const void *src, size_t len); + mem_memcpy(new_proc->cwd, parent->cwd, 1024); + } else { + extern void mem_memset(void *dest, int val, size_t len); + mem_memset(new_proc->cwd, 0, 1024); + new_proc->cwd[0] = '/'; + } // 2. Load ELF executable size_t elf_load_size = 0; @@ -426,6 +442,69 @@ uint64_t process_schedule(uint64_t current_rsp) { // Save context cur->rsp = current_rsp; + if (cur->kill_pending && cur->pid != 0xFFFFFFFF && cur->pid != 0) { + process_cleanup_inner(cur); + + process_t *prev = cur; + while (prev->next != cur) { + prev = prev->next; + } + + if (prev != cur) { + prev->next = cur->next; + + process_t *next_proc = cur->next; + while (next_proc != cur) { + if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF && !next_proc->kill_pending) break; + next_proc = next_proc->next; + } + + if (next_proc == cur || next_proc->cpu_affinity != my_cpu) { + for (int i = 0; i < MAX_PROCESSES; i++) { + if (processes[i].pid == 0 || (processes[i].cpu_affinity == my_cpu && processes[i].is_user == false)) { + next_proc = &processes[i]; + break; + } + } + } + + current_process[my_cpu] = next_proc; + + cur->pid = 0xFFFFFFFF; + cur->cpu_affinity = 0xFFFFFFFF; + cur->ui_window = NULL; + cur->is_terminal_proc = false; + cur->kill_pending = false; + + free_kernel_stack_later[my_cpu] = cur->kernel_stack_alloc; + cur->kernel_stack_alloc = NULL; + free_pml4_later[my_cpu] = cur->pml4_phys; + cur->pml4_phys = 0; + + if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) { + tss_set_stack_cpu(my_cpu, current_process[my_cpu]->kernel_stack); + cpu_state_t *cpu_state = smp_get_cpu(my_cpu); + if (cpu_state) { + cpu_state->kernel_syscall_stack = current_process[my_cpu]->kernel_stack; + } + } + + paging_switch_directory(current_process[my_cpu]->pml4_phys); + + current_process[my_cpu]->ticks++; + uint64_t next_rsp = current_process[my_cpu]->rsp; + + spinlock_release_irqrestore(&runqueue_lock, rflags); + if (cleanup_stack) kfree(cleanup_stack); + if (cleanup_pml4) { + extern void paging_destroy_user_pml4_phys(uint64_t pml4_phys); + paging_destroy_user_pml4_phys(cleanup_pml4); + } + + return next_rsp; + } + } + // Switch to next ready process assigned to this CPU extern uint32_t wm_get_ticks(void); uint32_t now = wm_get_ticks(); @@ -435,7 +514,7 @@ uint64_t process_schedule(uint64_t current_rsp) { while (next_proc != start) { // Only consider processes assigned to our CPU and not terminated - if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF) { + if (next_proc->cpu_affinity == my_cpu && next_proc->pid != 0xFFFFFFFF && !next_proc->kill_pending) { if (next_proc->pid == 0 || next_proc->sleep_until == 0 || next_proc->sleep_until <= now) { break; } @@ -497,12 +576,20 @@ process_t* process_get_by_pid(uint32_t pid) { return NULL; } +void process_kill_by_tty(int tty_id) { + if (tty_id < 0) return; + for (int i = 0; i < MAX_PROCESSES; i++) { + if (processes[i].pid != 0xFFFFFFFF && processes[i].pid != 0 && processes[i].tty_id == tty_id) { + process_terminate(&processes[i]); + } + } +} + static void process_cleanup_inner(process_t *proc) { if (!proc || proc->pid == 0xFFFFFFFF) return; // 1. Cleanup side effects - extern Window win_cmd; - if (proc->ui_window && (proc->ui_window != &win_cmd)) { + if (proc->ui_window) { wm_remove_window((Window *)proc->ui_window); proc->ui_window = NULL; } @@ -529,6 +616,14 @@ static void process_cleanup_inner(process_t *proc) { void process_terminate(process_t *to_delete) { if (!to_delete || to_delete->pid == 0xFFFFFFFF || to_delete->pid == 0) return; + uint32_t cpu_count = smp_cpu_count(); + for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) { + if (current_process[c] == to_delete) { + to_delete->kill_pending = true; + return; + } + } + uint64_t rflags = spinlock_acquire_irqsave(&runqueue_lock); process_cleanup_inner(to_delete); @@ -549,7 +644,6 @@ void process_terminate(process_t *to_delete) { prev->next = to_delete->next; // Update per-CPU current_process if this was the current on any CPU - uint32_t cpu_count = smp_cpu_count(); for (uint32_t c = 0; c < cpu_count && c < MAX_CPUS_SCHED; c++) { if (current_process[c] == to_delete) { process_t *np = to_delete->next; @@ -571,6 +665,7 @@ void process_terminate(process_t *to_delete) { // Mark slot as free to_delete->pid = 0xFFFFFFFF; to_delete->cpu_affinity = 0xFFFFFFFF; + to_delete->kill_pending = false; if (to_delete->user_stack_alloc) kfree(to_delete->user_stack_alloc); // Defer kernel stack until we switch away from it @@ -639,6 +734,7 @@ uint64_t process_terminate_current(void) { to_delete->cpu_affinity = 0xFFFFFFFF; to_delete->ui_window = NULL; to_delete->is_terminal_proc = false; + to_delete->kill_pending = false; // 4. Load context for the NEXT process if (current_process[my_cpu]->is_user && current_process[my_cpu]->kernel_stack) { diff --git a/src/sys/process.h b/src/sys/process.h index 7fef072..d014d45 100644 --- a/src/sys/process.h +++ b/src/sys/process.h @@ -43,6 +43,8 @@ typedef struct process { void *user_stack_alloc; bool is_terminal_proc; + int tty_id; + bool kill_pending; struct process *next; @@ -75,6 +77,7 @@ uint64_t process_schedule(uint64_t current_rsp); uint64_t process_terminate_current(void); void process_terminate(process_t *proc); process_t* process_get_by_pid(uint32_t pid); +void process_kill_by_tty(int tty_id); // SMP: IPI handler for AP scheduling uint64_t sched_ipi_handler(registers_t *regs); diff --git a/src/sys/syscall.c b/src/sys/syscall.c index 3f98fa4..7c96800 100644 --- a/src/sys/syscall.c +++ b/src/sys/syscall.c @@ -19,11 +19,16 @@ #include "network.h" #include "icmp.h" #include "cmd.h" +#include "tty.h" #include "font_manager.h" #include "graphics.h" extern bool ps2_ctrl_pressed; +#define SPAWN_FLAG_TERMINAL 0x1 +#define SPAWN_FLAG_INHERIT_TTY 0x2 +#define SPAWN_FLAG_TTY_ID 0x4 + // Read MSR static inline uint64_t rdmsr(uint32_t msr) { uint32_t low, high; @@ -180,6 +185,12 @@ static void user_window_resize(Window *win, int w, int h) { extern void mem_memset(void *dest, int val, size_t len); mem_memset(win->pixels, 0, w * h * sizeof(uint32_t)); } + + process_t *proc = process_get_by_ui_window(win); + if (proc) { + gui_event_t ev = { .type = GUI_EVENT_RESIZE, .arg1 = w, .arg2 = h }; + process_push_gui_event(proc, &ev); + } } @@ -197,9 +208,21 @@ static uint64_t syscall_handler_inner(registers_t *regs) { if (syscall_num == 1) { // SYS_WRITE extern void cmd_write_len(const char *str, size_t len); process_t *proc = process_get_current(); - if (!proc || !proc->is_user || proc->is_terminal_proc) { - cmd_write_len((const char*)arg2, (size_t)arg3); + const char *buf = (const char*)arg2; + size_t len = (size_t)arg3; + if (!proc || !proc->is_user) { + cmd_write_len(buf, len); + return len; } + if (proc->is_terminal_proc) { + if (proc->tty_id >= 0) { + tty_write_output(proc->tty_id, buf, len); + return len; + } + cmd_write_len(buf, len); + return len; + } + return len; } else if (syscall_num == 3) { // SYS_GUI int cmd = (int)arg1; process_t *proc = process_get_current(); @@ -1254,6 +1277,35 @@ static uint64_t syscall_handler_inner(registers_t *regs) { return cmd_get_config_value(key); } else if (cmd == 29) { // SYSTEM_CMD_SET_TEXT_COLOR uint32_t color = (uint32_t)arg2; + if (proc->is_terminal_proc && proc->tty_id >= 0) { + char seq[32]; + int pos = 0; + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + + seq[pos++] = 0x1B; + seq[pos++] = '['; + seq[pos++] = '3'; + seq[pos++] = '8'; + seq[pos++] = ';'; + seq[pos++] = '2'; + seq[pos++] = ';'; + + char num[8]; + k_itoa(r, num); + for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i]; + seq[pos++] = ';'; + k_itoa(g, num); + for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i]; + seq[pos++] = ';'; + k_itoa(b, num); + for (int i = 0; num[i] && pos < (int)sizeof(seq) - 1; i++) seq[pos++] = num[i]; + seq[pos++] = 'm'; + + tty_write_output(proc->tty_id, seq, (size_t)pos); + return 0; + } cmd_set_current_color(color); return 0; } else if (cmd == 31) { // SYSTEM_CMD_SET_WALLPAPER_PATH @@ -1374,6 +1426,85 @@ static uint64_t syscall_handler_inner(registers_t *regs) { } return -1; return -1; + } else if (cmd == 60) { // SYSTEM_CMD_TTY_CREATE + return tty_create(); + } else if (cmd == 61) { // SYSTEM_CMD_TTY_READ_OUT + int tty_id = (int)arg2; + char *buf = (char *)arg3; + size_t len = (size_t)arg4; + if (!buf || len == 0) return 0; + return tty_read_output(tty_id, buf, len); + } else if (cmd == 62) { // SYSTEM_CMD_TTY_WRITE_IN + int tty_id = (int)arg2; + const char *buf = (const char *)arg3; + size_t len = (size_t)arg4; + if (!buf || len == 0) return 0; + return tty_write_input(tty_id, buf, len); + } else if (cmd == 63) { // SYSTEM_CMD_TTY_READ_IN + char *buf = (char *)arg2; + size_t len = (size_t)arg3; + if (!buf || len == 0) return 0; + if (proc->tty_id < 0) return 0; + return tty_read_input(proc->tty_id, buf, len); + } else if (cmd == 65) { // SYSTEM_CMD_TTY_SET_FG + int tty_id = (int)arg2; + int pid = (int)arg3; + return tty_set_foreground(tty_id, pid); + } else if (cmd == 66) { // SYSTEM_CMD_TTY_GET_FG + int tty_id = (int)arg2; + return tty_get_foreground(tty_id); + } else if (cmd == 67) { // SYSTEM_CMD_TTY_KILL_FG + int tty_id = (int)arg2; + int pid = tty_get_foreground(tty_id); + if (pid <= 0) return 0; + process_t *target = process_get_by_pid((uint32_t)pid); + if (target) process_terminate(target); + tty_set_foreground(tty_id, 0); + return 0; + } else if (cmd == 68) { + int tty_id = (int)arg2; + process_kill_by_tty(tty_id); + tty_set_foreground(tty_id, 0); + return 0; + } else if (cmd == 69) { + int tty_id = (int)arg2; + return tty_destroy(tty_id); + } else if (cmd == 64) { + const char *user_path = (const char *)arg2; + const char *user_args = (const char *)arg3; + uint64_t flags = arg4; + int tty_id = (int)arg5; + + if (!user_path) return -1; + + char path_buf[256]; + int pi = 0; + while (pi < 255 && user_path[pi]) { + path_buf[pi] = user_path[pi]; + pi++; + } + path_buf[pi] = 0; + + char args_buf[512]; + const char *args_ptr = NULL; + if (user_args) { + int ai = 0; + while (ai < 511 && user_args[ai]) { + args_buf[ai] = user_args[ai]; + ai++; + } + args_buf[ai] = 0; + args_ptr = args_buf; + } + + process_t *child = process_create_elf(path_buf, args_ptr); + if (!child) return -1; + + if (flags & SPAWN_FLAG_TERMINAL) child->is_terminal_proc = true; + if (flags & SPAWN_FLAG_TTY_ID) child->tty_id = tty_id; + else if (flags & SPAWN_FLAG_INHERIT_TTY) child->tty_id = proc->tty_id; + + return (uint64_t)child->pid; } else if (cmd == SYSTEM_CMD_PARALLEL_RUN) { void (*user_fn)(void*) = (void (*)(void*))arg2; void **args = (void **)arg3; diff --git a/src/sys/tty.c b/src/sys/tty.c new file mode 100644 index 0000000..1cb81ab --- /dev/null +++ b/src/sys/tty.c @@ -0,0 +1,158 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// 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 "tty.h" +#include "spinlock.h" +#include +#include + +#define TTY_MAX 8 +#define TTY_OUT_SIZE 16384 +#define TTY_IN_SIZE 4096 + +typedef struct { + bool used; + int id; + char out_buf[TTY_OUT_SIZE]; + uint32_t out_head; + uint32_t out_tail; + char in_buf[TTY_IN_SIZE]; + uint32_t in_head; + uint32_t in_tail; + int fg_pid; + spinlock_t lock; +} tty_t; + +static tty_t ttys[TTY_MAX] = {0}; + +extern void mem_memset(void *dest, int val, size_t len); + +static tty_t *tty_get(int tty_id) { + if (tty_id < 0 || tty_id >= TTY_MAX) return NULL; + if (!ttys[tty_id].used) return NULL; + return &ttys[tty_id]; +} + +int tty_create(void) { + for (int i = 0; i < TTY_MAX; i++) { + if (!ttys[i].used) { + ttys[i].used = true; + ttys[i].id = i; + ttys[i].out_head = 0; + ttys[i].out_tail = 0; + ttys[i].in_head = 0; + ttys[i].in_tail = 0; + ttys[i].fg_pid = -1; + ttys[i].lock = SPINLOCK_INIT; + mem_memset(ttys[i].out_buf, 0, sizeof(ttys[i].out_buf)); + mem_memset(ttys[i].in_buf, 0, sizeof(ttys[i].in_buf)); + return i; + } + } + return -1; +} + +int tty_destroy(int tty_id) { + if (tty_id < 0 || tty_id >= TTY_MAX) return -1; + tty_t *tty = &ttys[tty_id]; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + if (!tty->used) { + spinlock_release_irqrestore(&tty->lock, rflags); + return -1; + } + + tty->used = false; + tty->id = -1; + tty->out_head = 0; + tty->out_tail = 0; + tty->in_head = 0; + tty->in_tail = 0; + tty->fg_pid = -1; + mem_memset(tty->out_buf, 0, sizeof(tty->out_buf)); + mem_memset(tty->in_buf, 0, sizeof(tty->in_buf)); + + spinlock_release_irqrestore(&tty->lock, rflags); + return 0; +} + +static int tty_write_ring(char *buf, uint32_t size, uint32_t *head, uint32_t *tail, const char *data, size_t len) { + int written = 0; + for (size_t i = 0; i < len; i++) { + uint32_t next = (*head + 1) % size; + if (next == *tail) break; + buf[*head] = data[i]; + *head = next; + written++; + } + return written; +} + +static int tty_read_ring(char *buf, uint32_t size, uint32_t *head, uint32_t *tail, char *out, size_t max_len) { + int read = 0; + while (*tail != *head && (size_t)read < max_len) { + out[read++] = buf[*tail]; + *tail = (*tail + 1) % size; + } + return read; +} + +int tty_write_output(int tty_id, const char *data, size_t len) { + tty_t *tty = tty_get(tty_id); + if (!tty || !data || len == 0) return 0; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + int written = tty_write_ring(tty->out_buf, TTY_OUT_SIZE, &tty->out_head, &tty->out_tail, data, len); + spinlock_release_irqrestore(&tty->lock, rflags); + return written; +} + +int tty_read_output(int tty_id, char *buf, size_t max_len) { + tty_t *tty = tty_get(tty_id); + if (!tty || !buf || max_len == 0) return 0; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + int read = tty_read_ring(tty->out_buf, TTY_OUT_SIZE, &tty->out_head, &tty->out_tail, buf, max_len); + spinlock_release_irqrestore(&tty->lock, rflags); + return read; +} + +int tty_write_input(int tty_id, const char *data, size_t len) { + tty_t *tty = tty_get(tty_id); + if (!tty || !data || len == 0) return 0; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + int written = tty_write_ring(tty->in_buf, TTY_IN_SIZE, &tty->in_head, &tty->in_tail, data, len); + spinlock_release_irqrestore(&tty->lock, rflags); + return written; +} + +int tty_read_input(int tty_id, char *buf, size_t max_len) { + tty_t *tty = tty_get(tty_id); + if (!tty || !buf || max_len == 0) return 0; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + int read = tty_read_ring(tty->in_buf, TTY_IN_SIZE, &tty->in_head, &tty->in_tail, buf, max_len); + spinlock_release_irqrestore(&tty->lock, rflags); + return read; +} + +int tty_set_foreground(int tty_id, int pid) { + tty_t *tty = tty_get(tty_id); + if (!tty) return -1; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + tty->fg_pid = pid; + spinlock_release_irqrestore(&tty->lock, rflags); + return 0; +} + +int tty_get_foreground(int tty_id) { + tty_t *tty = tty_get(tty_id); + if (!tty) return -1; + + uint64_t rflags = spinlock_acquire_irqsave(&tty->lock); + int pid = tty->fg_pid; + spinlock_release_irqrestore(&tty->lock, rflags); + return pid; +} diff --git a/src/sys/tty.h b/src/sys/tty.h new file mode 100644 index 0000000..54800cf --- /dev/null +++ b/src/sys/tty.h @@ -0,0 +1,18 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// 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. +#ifndef TTY_H +#define TTY_H + +#include + +int tty_create(void); +int tty_destroy(int tty_id); +int tty_write_output(int tty_id, const char *data, size_t len); +int tty_read_output(int tty_id, char *buf, size_t max_len); +int tty_write_input(int tty_id, const char *data, size_t len); +int tty_read_input(int tty_id, char *buf, size_t max_len); +int tty_set_foreground(int tty_id, int pid); +int tty_get_foreground(int tty_id); + +#endif diff --git a/src/userland/cli/bsh.c b/src/userland/cli/bsh.c new file mode 100644 index 0000000..ff58f6e --- /dev/null +++ b/src/userland/cli/bsh.c @@ -0,0 +1,1449 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// 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 +#include +#include + +#define MAX_LINE 512 +#define MAX_ARGS 32 +#define MAX_PATHS 16 +#define MAX_PATH_LEN 128 +#define MAX_HISTORY 256 +#define MAX_MATCHES 64 +#define MAX_MATCH_LEN 128 +#define MAX_ALIASES 32 +#define MAX_ALIAS_NAME 32 +#define MAX_ALIAS_VALUE 256 + +#define DEFAULT_PROMPT "%n@%h:%~$ " + +typedef struct { + char path[256]; + char startup[256]; + char boot_script[256]; + char prompt_left[128]; + char prompt_right[128]; + char history_file[128]; + int history_size; + bool glob_enabled; + bool complete_enabled; + bool suggest_enabled; +} bsh_config_t; + +static bsh_config_t g_cfg; +static char g_paths[MAX_PATHS][MAX_PATH_LEN]; +static int g_path_count = 0; + +static char g_history[MAX_HISTORY][MAX_LINE]; +static int g_history_count = 0; + +typedef struct { + char name[MAX_ALIAS_NAME]; + char value[MAX_ALIAS_VALUE]; +} alias_t; + +static alias_t g_aliases[MAX_ALIASES]; +static int g_alias_count = 0; + +static int g_tty_id = -1; +static uint32_t g_color_dir = 0; +static uint32_t g_color_file = 0; +static uint32_t g_color_size = 0; +static uint32_t g_color_error = 0; +static uint32_t g_color_success = 0; +static uint32_t g_color_default = 0; + +static void str_copy(char *dst, const char *src, int max_len) { + int i = 0; + if (max_len <= 0) return; + while (i < max_len - 1 && src && src[i]) { + dst[i] = src[i]; + i++; + } + dst[i] = 0; +} + +static void str_append(char *dst, const char *src, int max_len) { + if (!dst || !src || max_len <= 0) return; + int dlen = (int)strlen(dst); + int i = 0; + while (dlen + i < max_len - 1 && src[i]) { + dst[dlen + i] = src[i]; + i++; + } + dst[dlen + i] = 0; +} + +static bool str_eq(const char *a, const char *b) { + if (!a || !b) return false; + while (*a && *b) { + if (*a != *b) return false; + a++; + b++; + } + return *a == *b; +} + +static bool starts_with(const char *s, const char *prefix) { + if (!s || !prefix) return false; + while (*prefix) { + if (*s != *prefix) return false; + s++; + prefix++; + } + return true; +} + +static bool ends_with(const char *s, const char *suffix) { + if (!s || !suffix) return false; + int sl = (int)strlen(s); + int tl = (int)strlen(suffix); + if (tl > sl) return false; + return strcmp(s + sl - tl, suffix) == 0; +} + +static void trim(char *s) { + if (!s) return; + int len = (int)strlen(s); + int start = 0; + while (s[start] == ' ' || s[start] == '\t') start++; + int end = len - 1; + while (end >= start && (s[end] == ' ' || s[end] == '\t' || s[end] == '\r' || s[end] == '\n')) end--; + + int out = 0; + for (int i = start; i <= end; i++) { + s[out++] = s[i]; + } + s[out] = 0; +} + +static void alias_add(const char *name, const char *value) { + if (!name || !name[0] || !value) return; + for (int i = 0; i < g_alias_count; i++) { + if (str_eq(g_aliases[i].name, name)) { + str_copy(g_aliases[i].value, value, sizeof(g_aliases[i].value)); + return; + } + } + if (g_alias_count >= MAX_ALIASES) return; + str_copy(g_aliases[g_alias_count].name, name, sizeof(g_aliases[g_alias_count].name)); + str_copy(g_aliases[g_alias_count].value, value, sizeof(g_aliases[g_alias_count].value)); + g_alias_count++; +} + +static void alias_remove(const char *name) { + if (!name || !name[0]) return; + for (int i = 0; i < g_alias_count; i++) { + if (str_eq(g_aliases[i].name, name)) { + for (int j = i; j < g_alias_count - 1; j++) { + g_aliases[j] = g_aliases[j + 1]; + } + g_alias_count--; + return; + } + } +} + +static const char *alias_get(const char *name) { + if (!name || !name[0]) return NULL; + for (int i = 0; i < g_alias_count; i++) { + if (str_eq(g_aliases[i].name, name)) return g_aliases[i].value; + } + return NULL; +} + +static void config_defaults(void) { + str_copy(g_cfg.path, "/bin", sizeof(g_cfg.path)); + str_copy(g_cfg.startup, "", sizeof(g_cfg.startup)); + str_copy(g_cfg.boot_script, "", sizeof(g_cfg.boot_script)); + str_copy(g_cfg.prompt_left, DEFAULT_PROMPT, sizeof(g_cfg.prompt_left)); + str_copy(g_cfg.prompt_right, "", sizeof(g_cfg.prompt_right)); + str_copy(g_cfg.history_file, "/Library/bsh/history", sizeof(g_cfg.history_file)); + g_cfg.history_size = 200; + g_cfg.glob_enabled = true; + g_cfg.complete_enabled = true; + g_cfg.suggest_enabled = true; + g_alias_count = 0; +} + +static void parse_bool(const char *val, bool *out) { + if (!val || !out) return; + if (str_eq(val, "true") || str_eq(val, "1") || str_eq(val, "yes")) *out = true; + else *out = false; +} + +static void split_path(const char *path_str) { + g_path_count = 0; + if (!path_str) return; + int i = 0; + int start = 0; + while (1) { + if (path_str[i] == ':' || path_str[i] == 0) { + int len = i - start; + if (len > 0 && g_path_count < MAX_PATHS) { + int copy_len = len < (MAX_PATH_LEN - 1) ? len : (MAX_PATH_LEN - 1); + for (int j = 0; j < copy_len; j++) { + g_paths[g_path_count][j] = path_str[start + j]; + } + g_paths[g_path_count][copy_len] = 0; + g_path_count++; + } + start = i + 1; + } + if (path_str[i] == 0) break; + i++; + } +} + +static void load_shell_colors(void) { + g_color_dir = (uint32_t)sys_get_shell_config("dir_color"); + g_color_file = (uint32_t)sys_get_shell_config("file_color"); + g_color_size = (uint32_t)sys_get_shell_config("size_color"); + g_color_error = (uint32_t)sys_get_shell_config("error_color"); + g_color_success = (uint32_t)sys_get_shell_config("success_color"); + g_color_default = (uint32_t)sys_get_shell_config("default_text_color"); + + if (g_color_default == 0) g_color_default = 0xFFCCCCCC; + if (g_color_error == 0) g_color_error = 0xFFFF4444; + if (g_color_success == 0) g_color_success = 0xFF6A9955; + if (g_color_dir == 0) g_color_dir = 0xFF569CD6; + if (g_color_file == 0) g_color_file = g_color_default; + if (g_color_size == 0) g_color_size = 0xFF6A9955; +} + +static void set_color(uint32_t color) { + sys_set_text_color(color); +} + +static void reset_color(void) { + sys_set_text_color(g_color_default); +} + +static void config_load(void) { + config_defaults(); + + int fd = sys_open("/Library/bsh/bshrc", "r"); + if (fd < 0) { + split_path(g_cfg.path); + return; + } + + char buf[4096]; + int bytes = sys_read(fd, buf, sizeof(buf) - 1); + sys_close(fd); + if (bytes <= 0) { + split_path(g_cfg.path); + return; + } + buf[bytes] = 0; + + char *line = buf; + while (*line) { + char *end = line; + while (*end && *end != '\n' && *end != '\r') end++; + char saved = *end; + *end = 0; + + trim(line); + if (line[0] == '#' || line[0] == 0) { + line = end + (saved ? 1 : 0); + if (saved == '\r' && *line == '\n') line++; + continue; + } + + if (starts_with(line, "alias ") || starts_with(line, "alias\t")) { + char *def = line + 5; + while (*def == ' ' || *def == '\t') def++; + char *eq = def; + while (*eq && *eq != '=') eq++; + if (*eq == '=') { + *eq = 0; + char *name = def; + char *val = eq + 1; + trim(name); + trim(val); + if (name[0]) alias_add(name, val); + } + line = end + (saved ? 1 : 0); + if (saved == '\r' && *line == '\n') line++; + continue; + } + + char *sep = line; + while (*sep && *sep != '=') sep++; + if (*sep == '=') { + *sep = 0; + char *key = line; + char *val = sep + 1; + trim(key); + trim(val); + + if (str_eq(key, "PATH")) str_copy(g_cfg.path, val, sizeof(g_cfg.path)); + else if (str_eq(key, "STARTUP")) str_copy(g_cfg.startup, val, sizeof(g_cfg.startup)); + else if (str_eq(key, "BOOT_SCRIPT")) str_copy(g_cfg.boot_script, val, sizeof(g_cfg.boot_script)); + else if (str_eq(key, "PROMPT_LEFT")) str_copy(g_cfg.prompt_left, val, sizeof(g_cfg.prompt_left)); + else if (str_eq(key, "PROMPT_RIGHT")) str_copy(g_cfg.prompt_right, val, sizeof(g_cfg.prompt_right)); + else if (str_eq(key, "HISTORY_FILE")) str_copy(g_cfg.history_file, val, sizeof(g_cfg.history_file)); + else if (str_eq(key, "HISTORY_SIZE")) g_cfg.history_size = atoi(val); + else if (str_eq(key, "GLOB")) parse_bool(val, &g_cfg.glob_enabled); + else if (str_eq(key, "COMPLETE")) parse_bool(val, &g_cfg.complete_enabled); + else if (str_eq(key, "SUGGEST")) parse_bool(val, &g_cfg.suggest_enabled); + } + + line = end + (saved ? 1 : 0); + if (saved == '\r' && *line == '\n') line++; + } + + split_path(g_cfg.path); +} + +static void history_load(void) { + g_history_count = 0; + if (!g_cfg.history_file[0]) return; + + int fd = sys_open(g_cfg.history_file, "r"); + if (fd < 0) return; + + char buf[4096]; + int bytes = sys_read(fd, buf, sizeof(buf) - 1); + sys_close(fd); + if (bytes <= 0) return; + buf[bytes] = 0; + + char *line = buf; + while (*line && g_history_count < MAX_HISTORY) { + char *end = line; + while (*end && *end != '\n' && *end != '\r') end++; + char saved = *end; + *end = 0; + trim(line); + if (line[0]) { + str_copy(g_history[g_history_count++], line, MAX_LINE); + } + line = end + (saved ? 1 : 0); + if (saved == '\r' && *line == '\n') line++; + } +} + +static void history_save(void) { + if (!g_cfg.history_file[0]) return; + + int fd = sys_open(g_cfg.history_file, "w"); + if (fd < 0) return; + + for (int i = 0; i < g_history_count; i++) { + int len = (int)strlen(g_history[i]); + if (len > 0) { + sys_write_fs(fd, g_history[i], len); + sys_write_fs(fd, "\n", 1); + } + } + sys_close(fd); +} + +static void history_add(const char *line) { + if (!line || !line[0]) return; + if (g_history_count > 0 && str_eq(g_history[g_history_count - 1], line)) return; + + if (g_history_count < MAX_HISTORY) { + str_copy(g_history[g_history_count++], line, MAX_LINE); + } else { + for (int i = 1; i < MAX_HISTORY; i++) { + str_copy(g_history[i - 1], g_history[i], MAX_LINE); + } + str_copy(g_history[MAX_HISTORY - 1], line, MAX_LINE); + } + if (g_history_count > g_cfg.history_size && g_cfg.history_size > 0) { + int excess = g_history_count - g_cfg.history_size; + for (int i = excess; i < g_history_count; i++) { + str_copy(g_history[i - excess], g_history[i], MAX_LINE); + } + g_history_count -= excess; + } + history_save(); +} + +static void get_time_string(char *out, int max_len) { + int dt[6] = {0}; + sys_system(11, (uint64_t)dt, 0, 0, 0); + char hh[4], mm[4]; + itoa(dt[3], hh); + itoa(dt[4], mm); + out[0] = 0; + if (dt[3] < 10) str_copy(out, "0", max_len); + str_append(out, hh, max_len); + str_append(out, ":", max_len); + if (dt[4] < 10) str_append(out, "0", max_len); + str_append(out, mm, max_len); +} + +static void format_prompt(const char *tmpl, char *out, int max_len) { + char cwd[256]; + if (!getcwd(cwd, sizeof(cwd))) str_copy(cwd, "/", sizeof(cwd)); + + int out_idx = 0; + for (int i = 0; tmpl[i] && out_idx < max_len - 1; i++) { + if (tmpl[i] == '%' && tmpl[i + 1]) { + char token = tmpl[i + 1]; + if (token == '~') { + if (starts_with(cwd, "/root") && (cwd[5] == 0 || cwd[5] == '/')) { + out[out_idx++] = '~'; + int j = 5; + while (cwd[j] && out_idx < max_len - 1) { + out[out_idx++] = cwd[j++]; + } + } else { + int j = 0; + while (cwd[j] && out_idx < max_len - 1) { + out[out_idx++] = cwd[j++]; + } + } + i++; + continue; + } + if (token == 'n') { + const char *user = "root"; + int j = 0; + while (user[j] && out_idx < max_len - 1) out[out_idx++] = user[j++]; + i++; + continue; + } + if (token == 'h') { + const char *host = "boredos"; + int j = 0; + while (host[j] && out_idx < max_len - 1) out[out_idx++] = host[j++]; + i++; + continue; + } + if (token == 'T') { + char time_buf[16]; + get_time_string(time_buf, sizeof(time_buf)); + int j = 0; + while (time_buf[j] && out_idx < max_len - 1) out[out_idx++] = time_buf[j++]; + i++; + continue; + } + } + out[out_idx++] = tmpl[i]; + } + out[out_idx] = 0; +} + +static int split_args(char *line, char *argv[], int max_args) { + int argc = 0; + int i = 0; + while (line[i] && argc < max_args) { + while (line[i] == ' ' || line[i] == '\t') i++; + if (!line[i]) break; + + if (line[i] == '"') { + i++; + argv[argc++] = &line[i]; + while (line[i] && line[i] != '"') i++; + if (line[i]) { + line[i] = 0; + i++; + } + } else { + argv[argc++] = &line[i]; + while (line[i] && line[i] != ' ' && line[i] != '\t') i++; + if (line[i]) { + line[i] = 0; + i++; + } + } + } + return argc; +} + +static bool is_file_path(const char *path) { + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) == 0 && !info.is_directory) return true; + return false; +} + +static bool run_script(const char *path); + +static void resolve_path(const char *input, char *out, int max_len) { + if (!out || max_len <= 0) return; + if (!input || !input[0]) { + out[0] = 0; + return; + } + if (input[0] == '/') { + str_copy(out, input, max_len); + return; + } + + char cwd[256]; + if (!getcwd(cwd, sizeof(cwd))) { + str_copy(out, input, max_len); + return; + } + + str_copy(out, cwd, max_len); + int len = (int)strlen(out); + if (len > 0 && out[len - 1] != '/') str_append(out, "/", max_len); + str_append(out, input, max_len); +} + +static bool resolve_script_path(const char *input, char *out, int max_len) { + char candidate[256]; + resolve_path(input, candidate, sizeof(candidate)); + if (is_file_path(candidate)) { + str_copy(out, candidate, max_len); + return true; + } + if (!ends_with(candidate, ".bsh")) { + char with_ext[256]; + str_copy(with_ext, candidate, sizeof(with_ext)); + str_append(with_ext, ".bsh", sizeof(with_ext)); + if (is_file_path(with_ext)) { + str_copy(out, with_ext, max_len); + return true; + } + } + return false; +} + +static bool is_elf_file(const char *path) { + unsigned char hdr[4]; + int fd = sys_open(path, "r"); + if (fd < 0) return false; + int bytes = sys_read(fd, hdr, 4); + sys_close(fd); + if (bytes < 4) return false; + 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; + for (int i = 0; cmd[i]; i++) { + if (cmd[i] == '/') { has_slash = true; break; } + } + + 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 -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; + } + } + } + + return -1; +} + +static void build_args_string(int argc, char *argv[], int start, char *out, int out_len) { + int pos = 0; + for (int i = start; i < argc; i++) { + if (i > start && pos < out_len - 1) out[pos++] = ' '; + + bool need_quotes = false; + for (int j = 0; argv[i][j]; j++) { + if (argv[i][j] == ' ') { need_quotes = true; break; } + } + if (need_quotes && pos < out_len - 1) out[pos++] = '"'; + + for (int j = 0; argv[i][j] && pos < out_len - 1; j++) { + out[pos++] = argv[i][j]; + } + + if (need_quotes && pos < out_len - 1) out[pos++] = '"'; + } + out[pos] = 0; +} + +static bool is_space_char(char c) { + return c == ' ' || c == '\t'; +} + +static int find_token_start(const char *line, int len) { + int i = len - 1; + while (i >= 0 && !is_space_char(line[i])) i--; + return i + 1; +} + +static int common_prefix_len(char matches[][MAX_MATCH_LEN], int count) { + if (count <= 0) return 0; + int len = (int)strlen(matches[0]); + for (int i = 1; i < count; i++) { + int j = 0; + while (j < len && matches[i][j] && matches[i][j] == matches[0][j]) j++; + len = j; + if (len == 0) break; + } + return len; +} + +static int add_match_unique(char matches[][MAX_MATCH_LEN], int count, const char *candidate) { + if (!candidate || !candidate[0]) return count; + for (int i = 0; i < count; i++) { + if (str_eq(matches[i], candidate)) return count; + } + if (count >= MAX_MATCHES) return count; + str_copy(matches[count], candidate, MAX_MATCH_LEN); + return count + 1; +} + +static void build_path_match(const char *dir_part, const char *entry, bool is_dir, char *out, int out_len) { + out[0] = 0; + if (dir_part && dir_part[0] && !(dir_part[0] == '.' && dir_part[1] == 0)) { + str_copy(out, dir_part, out_len); + int len = (int)strlen(out); + if (len > 0 && out[len - 1] != '/') str_append(out, "/", out_len); + } + str_append(out, entry, out_len); + if (is_dir) str_append(out, "/", out_len); +} + +static int collect_command_matches(const char *prefix, char matches[][MAX_MATCH_LEN]) { + int count = 0; + const char *builtins[] = { + "cd", "pwd", "ls", "cat", "echo", "clear", "mkdir", "rm", + "touch", "cp", "mv", "man", "alias", "unalias", ".", "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]); + } + + for (int i = 0; i < g_alias_count; i++) { + if (starts_with(g_aliases[i].name, prefix)) { + count = add_match_unique(matches, count, g_aliases[i].name); + } + } + + FAT32_FileInfo entries[128]; + for (int i = 0; i < g_path_count; i++) { + int got = sys_list(g_paths[i], entries, 128); + if (got <= 0) continue; + for (int j = 0; j < got; j++) { + if (entries[j].is_directory) continue; + if (starts_with(entries[j].name, prefix)) { + count = add_match_unique(matches, count, entries[j].name); + } + } + } + return count; +} + +static int collect_path_matches(const char *dir_part, const char *prefix, char matches[][MAX_MATCH_LEN]) { + const char *list_dir = (dir_part && dir_part[0]) ? dir_part : "."; + FAT32_FileInfo entries[128]; + int got = sys_list(list_dir, entries, 128); + if (got <= 0) return 0; + + int count = 0; + for (int i = 0; i < got; i++) { + if (!starts_with(entries[i].name, prefix)) continue; + char full[MAX_MATCH_LEN]; + build_path_match(dir_part, entries[i].name, entries[i].is_directory, full, sizeof(full)); + count = add_match_unique(matches, count, full); + } + return count; +} + +static void redraw_input(const char *prompt, const char *line, int len) { + sys_write(1, "\r", 1); + sys_write(1, prompt, (int)strlen(prompt)); + sys_write(1, line, len); + sys_write(1, "\x1b[K", 3); +} + +static void show_matches(const char *prompt, const char *line, int len, char matches[][MAX_MATCH_LEN], int count) { + sys_write(1, "\n", 1); + for (int i = 0; i < count; i++) { + sys_write(1, matches[i], (int)strlen(matches[i])); + sys_write(1, " ", 2); + } + sys_write(1, "\n", 1); + redraw_input(prompt, line, len); +} + +static int pid_exists(int pid) { + char path[64]; + path[0] = 0; + strcat(path, "/proc/"); + char pid_buf[16]; + itoa(pid, pid_buf); + strcat(path, pid_buf); + strcat(path, "/status"); + int fd = sys_open(path, "r"); + if (fd < 0) return 0; + char buf[8]; + int bytes = sys_read(fd, buf, sizeof(buf)); + sys_close(fd); + return bytes > 0; +} + +static void wait_for_pid(int pid) { + while (pid_exists(pid)) { + if (g_tty_id >= 0) { + int fg = sys_tty_get_fg(g_tty_id); + if (fg != pid) break; + } + sleep(10); + } +} + +static void cmd_clear(void) { + sys_write(1, "\x1b[2J\x1b[H", 7); +} + +static int builtin_cd(int argc, char *argv[]) { + const char *path = (argc > 1) ? argv[1] : "/"; + if (chdir(path) != 0) { + set_color(g_color_error); + printf("cd: no such directory: %s\n", path); + reset_color(); + return 1; + } + return 0; +} + +static int builtin_pwd(void) { + char cwd[256]; + if (getcwd(cwd, sizeof(cwd))) { + printf("%s\n", cwd); + return 0; + } + return 1; +} + +static int builtin_echo(int argc, char *argv[]) { + for (int i = 1; i < argc; i++) { + if (i > 1) sys_write(1, " ", 1); + sys_write(1, argv[i], (int)strlen(argv[i])); + } + sys_write(1, "\n", 1); + return 0; +} + +static int builtin_ls(int argc, char *argv[]) { + char path[256]; + if (argc > 1) str_copy(path, argv[1], sizeof(path)); + else if (!getcwd(path, sizeof(path))) str_copy(path, "/", sizeof(path)); + + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) < 0) { + set_color(g_color_error); + printf("ls: cannot access %s\n", path); + reset_color(); + return 1; + } + if (!info.is_directory) { + set_color(g_color_file); + printf("%s\n", info.name); + reset_color(); + return 0; + } + + FAT32_FileInfo entries[128]; + int count = sys_list(path, entries, 128); + if (count < 0) { + set_color(g_color_error); + printf("ls: cannot list %s\n", path); + reset_color(); + return 1; + } + + for (int i = 0; i < count; i++) { + if (entries[i].is_directory) { + set_color(g_color_dir); + printf("[DIR] %s\n", entries[i].name); + } else { + set_color(g_color_file); + printf("[FILE] %s", entries[i].name); + set_color(g_color_size); + printf(" (%d bytes)\n", entries[i].size); + } + } + reset_color(); + return 0; +} + +static int builtin_cat(int argc, char *argv[]) { + if (argc < 2) { + set_color(g_color_error); + printf("Usage: cat \n"); + reset_color(); + return 1; + } + + for (int i = 1; i < argc; i++) { + int fd = sys_open(argv[i], "r"); + if (fd < 0) { + set_color(g_color_error); + printf("cat: cannot open %s\n", argv[i]); + reset_color(); + continue; + } + char buffer[4096]; + int bytes; + while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) { + sys_write(1, buffer, bytes); + } + sys_close(fd); + } + reset_color(); + return 0; +} + +static int builtin_mkdir(int argc, char *argv[]) { + if (argc < 2) { + set_color(g_color_error); + printf("Usage: mkdir \n"); + reset_color(); + return 1; + } + if (sys_mkdir(argv[1]) == 0) return 0; + set_color(g_color_error); + printf("mkdir: cannot create %s\n", argv[1]); + reset_color(); + return 1; +} + +static int builtin_rm(int argc, char *argv[]) { + if (argc < 2) { + set_color(g_color_error); + printf("Usage: rm \n"); + reset_color(); + return 1; + } + if (sys_delete(argv[1]) == 0) return 0; + set_color(g_color_error); + printf("rm: cannot delete %s\n", argv[1]); + reset_color(); + return 1; +} + +static int builtin_touch(int argc, char *argv[]) { + if (argc < 2) { + set_color(g_color_error); + printf("Usage: touch \n"); + reset_color(); + return 1; + } + int fd = sys_open(argv[1], "w"); + if (fd < 0) { + set_color(g_color_error); + printf("touch: cannot create %s\n", argv[1]); + reset_color(); + return 1; + } + sys_close(fd); + return 0; +} + +static void combine_path(char *dest, const char *path1, const char *path2) { + int i = 0; + while (path1[i]) { + dest[i] = path1[i]; + i++; + } + if (i > 0 && dest[i - 1] != '/') dest[i++] = '/'; + int j = 0; + while (path2[j]) dest[i++] = path2[j++]; + dest[i] = 0; +} + +static const char* get_basename(const char *path) { + const char *last = NULL; + int len = 0; + while (path[len]) { + if (path[len] == '/') last = path + len; + len++; + } + if (!last) return path; + if (last[1] == 0) { + if (len <= 1) return path; + int i = len - 2; + while (i >= 0 && path[i] != '/') i--; + if (i < 0) return path; + return path + i + 1; + } + return last + 1; +} + +static void copy_recursive(const char *src, const char *dst) { + FAT32_FileInfo info; + if (sys_get_file_info(src, &info) < 0) return; + + if (info.is_directory) { + sys_mkdir(dst); + FAT32_FileInfo entries[64]; + int count = sys_list(src, entries, 64); + for (int i = 0; i < count; i++) { + if (str_eq(entries[i].name, ".") || str_eq(entries[i].name, "..")) continue; + char sub_src[512], sub_dst[512]; + combine_path(sub_src, src, entries[i].name); + combine_path(sub_dst, dst, entries[i].name); + copy_recursive(sub_src, sub_dst); + } + } else { + int fd_in = sys_open(src, "r"); + if (fd_in < 0) return; + int fd_out = sys_open(dst, "w"); + if (fd_out < 0) { sys_close(fd_in); return; } + char buffer[4096]; + int bytes; + while ((bytes = sys_read(fd_in, buffer, sizeof(buffer))) > 0) { + sys_write_fs(fd_out, buffer, bytes); + } + sys_close(fd_in); + sys_close(fd_out); + } +} + +static void delete_recursive(const char *path) { + FAT32_FileInfo info; + if (sys_get_file_info(path, &info) < 0) return; + + if (info.is_directory) { + FAT32_FileInfo entries[64]; + int count = sys_list(path, entries, 64); + for (int i = 0; i < count; i++) { + if (str_eq(entries[i].name, ".") || str_eq(entries[i].name, "..")) continue; + char sub_path[512]; + combine_path(sub_path, path, entries[i].name); + delete_recursive(sub_path); + } + sys_delete(path); + } else { + sys_delete(path); + } +} + +static int builtin_cp(int argc, char *argv[]) { + bool recursive = false; + char *src = NULL; + char *dst = NULL; + for (int i = 1; i < argc; i++) { + if (str_eq(argv[i], "-r")) recursive = true; + else if (!src) src = argv[i]; + else if (!dst) dst = argv[i]; + } + if (!src || !dst) { + set_color(g_color_error); + printf("Usage: cp [-r] \n"); + reset_color(); + return 1; + } + + FAT32_FileInfo info_src; + if (sys_get_file_info(src, &info_src) < 0) { + set_color(g_color_error); + printf("cp: source does not exist\n"); + reset_color(); + return 1; + } + if (info_src.is_directory && !recursive) { + set_color(g_color_error); + printf("cp: %s is a directory (use -r)\n", src); + reset_color(); + return 1; + } + + char actual_dst[512]; + FAT32_FileInfo info_dst; + if (sys_get_file_info(dst, &info_dst) == 0 && info_dst.is_directory) { + const char *base = get_basename(src); + combine_path(actual_dst, dst, base); + } else { + str_copy(actual_dst, dst, sizeof(actual_dst)); + } + + if (recursive) copy_recursive(src, actual_dst); + else copy_recursive(src, actual_dst); + return 0; +} + +static int builtin_mv(int argc, char *argv[]) { + if (argc < 3) { + set_color(g_color_error); + printf("Usage: mv \n"); + reset_color(); + return 1; + } + + char *src = argv[1]; + char *dst = argv[2]; + + char actual_dst[512]; + FAT32_FileInfo info_dst; + if (sys_get_file_info(dst, &info_dst) == 0 && info_dst.is_directory) { + const char *base = get_basename(src); + combine_path(actual_dst, dst, base); + } else { + str_copy(actual_dst, dst, sizeof(actual_dst)); + } + + copy_recursive(src, actual_dst); + delete_recursive(src); + return 0; +} + +static int builtin_man(int argc, char *argv[]) { + if (argc < 2) { + set_color(g_color_error); + printf("What manual page do you want? Example: man ls\n"); + reset_color(); + return 0; + } + + char path[128]; + str_copy(path, "/Library/man/", sizeof(path)); + str_append(path, argv[1], sizeof(path)); + str_append(path, ".txt", sizeof(path)); + + int fd = sys_open(path, "r"); + if (fd < 0) { + set_color(g_color_error); + printf("No manual entry for %s\n", argv[1]); + reset_color(); + return 1; + } + + char buffer[4096]; + int bytes; + while ((bytes = sys_read(fd, buffer, sizeof(buffer))) > 0) { + sys_write(1, buffer, bytes); + } + sys_close(fd); + printf("\n"); + return 0; +} + +static int builtin_alias(int argc, char *argv[]) { + if (argc == 1) { + for (int i = 0; i < g_alias_count; i++) { + printf("alias %s=%s\n", g_aliases[i].name, g_aliases[i].value); + } + return 0; + } + + for (int i = 1; i < argc; i++) { + char *eq = argv[i]; + while (*eq && *eq != '=') eq++; + if (*eq != '=') { + const char *val = alias_get(argv[i]); + if (val) { + printf("alias %s=%s\n", argv[i], val); + continue; + } + set_color(g_color_error); + printf("alias: invalid definition: %s\n", argv[i]); + reset_color(); + return 1; + } + *eq = 0; + alias_add(argv[i], eq + 1); + } + return 0; +} + +static int builtin_unalias(int argc, char *argv[]) { + if (argc < 2) { + set_color(g_color_error); + printf("Usage: unalias \n"); + reset_color(); + return 1; + } + for (int i = 1; i < argc; i++) alias_remove(argv[i]); + return 0; +} + +static int execute_builtin(int argc, char *argv[]) { + if (argc == 0) return 0; + if (str_eq(argv[0], "cd")) return builtin_cd(argc, argv); + if (str_eq(argv[0], "pwd")) return builtin_pwd(); + if (str_eq(argv[0], "ls")) return builtin_ls(argc, argv); + if (str_eq(argv[0], "cat")) return builtin_cat(argc, argv); + if (str_eq(argv[0], "echo")) return builtin_echo(argc, argv); + if (str_eq(argv[0], "clear")) { cmd_clear(); return 0; } + if (str_eq(argv[0], "mkdir")) return builtin_mkdir(argc, argv); + if (str_eq(argv[0], "rm")) return builtin_rm(argc, argv); + if (str_eq(argv[0], "touch")) return builtin_touch(argc, argv); + if (str_eq(argv[0], "cp")) return builtin_cp(argc, argv); + if (str_eq(argv[0], "mv")) return builtin_mv(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], "unalias")) return builtin_unalias(argc, argv); + if (str_eq(argv[0], ".")) { + if (argc < 2) { + set_color(g_color_error); + printf("Usage: .