Files
claude-os/apps/sh/sh.c
AI c25ba1fccd feat: shell (sh) with keyboard driver, SYS_READ, SYS_EXEC
- PS/2 keyboard driver: IRQ1 handler, scancode set 1 translation,
  ring buffer, shift key support
- SYS_READ (fd=0): non-blocking keyboard read for stdin
- SYS_YIELD: calls schedule_tick directly (no nested INT 0x80)
- SYS_EXEC: loads binary from CPIO initrd, replaces process image
- User-space libc: crt0.S (C runtime startup), syscalls.h (inline
  syscall wrappers, basic string functions)
- Shell app (sh): readline with echo/backspace, builtins (cd, env,
  help, exit), fork+exec for external commands
- Updated build_apps.sh: C app support with crt0 linking, libc
  include path, .bss section in objcopy

Tested: shell boots, keyboard input works, hello-world runs via
fork+exec, env shows CWD, exit cleanly terminates.
2026-02-23 13:08:06 +00:00

181 lines
4.4 KiB
C

/**
* @file sh.c
* @brief ClaudeOS shell.
*
* A simple interactive shell that reads commands from stdin,
* supports built-in commands (cd, exit, help, env), and
* executes external programs via fork+exec.
*/
#include "syscalls.h"
/** Maximum command line length. */
#define CMD_MAX 256
/** Read a line from stdin with echo and basic line editing.
* Returns length of the line (excluding newline). */
static int readline(char *buf, int maxlen) {
int pos = 0;
while (pos < maxlen - 1) {
char c;
int n = read(0, &c, 1);
if (n <= 0) {
/* No data yet: yield CPU and retry */
yield();
continue;
}
if (c == '\n' || c == '\r') {
putchar('\n');
break;
} else if (c == '\b' || c == 127) {
/* Backspace */
if (pos > 0) {
pos--;
puts("\b \b"); /* Move back, overwrite with space, move back */
}
} else if (c >= 32) {
/* Printable character */
buf[pos++] = c;
putchar(c);
}
}
buf[pos] = '\0';
return pos;
}
/** Skip leading whitespace, return pointer to first non-space. */
static char *skip_spaces(char *s) {
while (*s == ' ' || *s == '\t') s++;
return s;
}
/** Find the next space or end of string. */
static char *find_space(char *s) {
while (*s && *s != ' ' && *s != '\t') s++;
return s;
}
/** Built-in: cd <path> */
static void builtin_cd(char *arg) {
if (!arg || !*arg) {
arg = "/";
}
/* Update CWD environment variable */
setenv("CWD", arg);
/* Verify it was set */
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) >= 0) {
puts("cd: ");
puts(cwd);
putchar('\n');
}
}
/** Built-in: env - print all known env vars. */
static void builtin_env(void) {
char buf[128];
if (getenv("CWD", buf, sizeof(buf)) >= 0) {
puts("CWD=");
puts(buf);
putchar('\n');
}
if (getenv("PATH", buf, sizeof(buf)) >= 0) {
puts("PATH=");
puts(buf);
putchar('\n');
}
if (getenv("TEST", buf, sizeof(buf)) >= 0) {
puts("TEST=");
puts(buf);
putchar('\n');
}
}
/** Built-in: help */
static void builtin_help(void) {
puts("ClaudeOS Shell\n");
puts("Built-in commands:\n");
puts(" cd <path> - change working directory\n");
puts(" env - show environment variables\n");
puts(" help - show this message\n");
puts(" exit - exit the shell\n");
puts("External commands are loaded from initrd.\n");
}
/** Execute an external command via fork+exec. */
static void run_command(const char *cmd) {
int32_t pid = fork();
if (pid < 0) {
puts("sh: fork failed\n");
return;
}
if (pid == 0) {
/* Child: exec the command */
int32_t ret = exec(cmd);
if (ret < 0) {
puts("sh: ");
puts(cmd);
puts(": not found\n");
exit(127);
}
/* exec doesn't return on success */
exit(1);
}
/* Parent: wait for child */
waitpid(pid);
}
int main(void) {
puts("ClaudeOS Shell v0.1\n");
puts("Type 'help' for available commands.\n\n");
char cmd[CMD_MAX];
for (;;) {
/* Print prompt with CWD */
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
puts(cwd);
puts("$ ");
/* Read command */
int len = readline(cmd, CMD_MAX);
if (len == 0) continue;
/* Parse command and arguments */
char *line = skip_spaces(cmd);
if (*line == '\0') continue;
char *arg_start = find_space(line);
char *arg = (char *)0;
if (*arg_start) {
*arg_start = '\0';
arg = skip_spaces(arg_start + 1);
if (*arg == '\0') arg = (char *)0;
}
/* Check built-in commands */
if (strcmp(line, "exit") == 0) {
puts("Goodbye!\n");
exit(0);
} else if (strcmp(line, "cd") == 0) {
builtin_cd(arg);
} else if (strcmp(line, "env") == 0) {
builtin_env();
} else if (strcmp(line, "help") == 0) {
builtin_help();
} else {
/* External command */
run_command(line);
}
}
return 0;
}