#include #include #include #include #include #include #include #include #include #include #include #ifndef VFS_MAX_PATH #define VFS_MAX_PATH 1024 #endif #define TIOCGWINSZ 0x5413 #define TIOCGCURSOR 0x5480 typedef struct { uint16_t ws_row, ws_col, ws_xpixel, ws_ypixel; } cervus_winsize_t; typedef struct { uint32_t row, col; } cervus_cursor_pos_t; static inline int sh_ioctl(int fd, unsigned long req, void *arg) { return (int)syscall3(SYS_IOCTL, fd, req, arg); } static int g_cols = 80; static int g_rows = 25; static void term_update_size(void) { cervus_winsize_t ws; if (sh_ioctl(1, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 8 && ws.ws_row >= 2) { g_cols = (int)ws.ws_col; g_rows = (int)ws.ws_row; } } static int term_get_cursor_row(void) { cervus_cursor_pos_t cp; if (sh_ioctl(1, TIOCGCURSOR, &cp) == 0) return (int)cp.row; return 0; } static void vt_goto(int row, int col) { char b[24]; snprintf(b, sizeof(b), "\x1b[%d;%dH", row + 1, col + 1); fputs(b, stdout); } static void vt_eol(void) { fputs("\x1b[K", stdout); } #define HIST_MAX 1024 #define LINE_MAX 1024 static char history[HIST_MAX][LINE_MAX]; static int hist_count = 0, hist_head = 0; static const char *g_hist_file = NULL; static void hist_load(const char *path) { int fd = open(path, O_RDONLY, 0); if (fd < 0) return; char line[LINE_MAX]; int li = 0; char ch; while (read(fd, &ch, 1) > 0) { if (ch == '\n' || li >= LINE_MAX - 1) { line[li] = '\0'; if (li > 0) { int idx = (hist_head + hist_count) % HIST_MAX; strncpy(history[idx], line, LINE_MAX - 1); history[idx][LINE_MAX - 1] = '\0'; if (hist_count < HIST_MAX) hist_count++; else hist_head = (hist_head + 1) % HIST_MAX; } li = 0; } else { line[li++] = ch; } } close(fd); } static void hist_save_entry(const char *path, const char *l) { int fd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0600); if (fd < 0) return; int n = 0; while (l[n]) n++; write(fd, l, n); write(fd, "\n", 1); close(fd); } static void hist_push(const char *l) { if (!l[0]) return; if (hist_count > 0) { int last = (hist_head + hist_count - 1) % HIST_MAX; if (strcmp(history[last], l) == 0) return; } int idx = (hist_head + hist_count) % HIST_MAX; strncpy(history[idx], l, LINE_MAX - 1); history[idx][LINE_MAX - 1] = '\0'; if (hist_count < HIST_MAX) hist_count++; else hist_head = (hist_head + 1) % HIST_MAX; if (g_hist_file) hist_save_entry(g_hist_file, l); } static const char *hist_get(int n) { if (n < 1 || n > hist_count) return NULL; return history[(hist_head + hist_count - n) % HIST_MAX]; } #define ENV_MAX_VARS 128 #define ENV_NAME_MAX 64 #define ENV_VAL_MAX 512 typedef struct { char name[ENV_NAME_MAX]; char value[ENV_VAL_MAX]; } env_var_t; static env_var_t g_env[ENV_MAX_VARS]; static int g_env_count = 0; static int env_find(const char *name) { for (int i = 0; i < g_env_count; i++) if (strcmp(g_env[i].name, name) == 0) return i; return -1; } static const char *env_get(const char *name) { int i = env_find(name); return i >= 0 ? g_env[i].value : ""; } static void env_set(const char *name, const char *value) { int i = env_find(name); if (i >= 0) { strncpy(g_env[i].value, value, ENV_VAL_MAX - 1); return; } if (g_env_count >= ENV_MAX_VARS) return; strncpy(g_env[g_env_count].name, name, ENV_NAME_MAX - 1); strncpy(g_env[g_env_count].value, value, ENV_VAL_MAX - 1); g_env_count++; } static void env_unset(const char *name) { int i = env_find(name); if (i < 0) return; g_env[i] = g_env[--g_env_count]; } static int g_last_rc = 0; static void expand_vars(const char *src, char *dst, size_t dsz) { size_t di = 0; for (const char *p = src; *p && di + 1 < dsz; ) { if (*p != '$') { dst[di++] = *p++; continue; } p++; if (*p == '?') { char tmp[12]; int len = snprintf(tmp, sizeof(tmp), "%d", g_last_rc); for (int x = 0; x < len && di + 1 < dsz; x++) dst[di++] = tmp[x]; p++; continue; } int braced = (*p == '{'); if (braced) p++; char name[ENV_NAME_MAX]; int ni = 0; while (*p && ni + 1 < (int)ENV_NAME_MAX) { char c = *p; if (braced) { if (c == '}') { p++; break; } } else if (!isalnum((unsigned char)c) && c != '_') break; name[ni++] = c; p++; } name[ni] = '\0'; if (ni == 0) { dst[di++] = '$'; continue; } const char *val = env_get(name); for (; *val && di + 1 < dsz; val++) dst[di++] = *val; } dst[di] = '\0'; } static char cwd[VFS_MAX_PATH]; static int prompt_len = 0; static const char *display_path(void) { static char dpbuf[VFS_MAX_PATH]; const char *home = env_get("HOME"); size_t hlen = home ? strlen(home) : 0; if (hlen > 1 && strncmp(cwd, home, hlen) == 0 && (cwd[hlen] == '/' || cwd[hlen] == '\0')) { dpbuf[0] = '~'; strncpy(dpbuf + 1, cwd + hlen, sizeof(dpbuf) - 2); dpbuf[sizeof(dpbuf) - 1] = '\0'; if (dpbuf[1] == '\0') { dpbuf[0] = '~'; dpbuf[1] = '\0'; } return dpbuf; } return cwd; } static void print_prompt(void) { const char *dp = display_path(); fputs(C_GREEN "cervus" C_RESET ":" C_BLUE, stdout); fputs(dp, stdout); fputs(C_RESET "$ ", stdout); prompt_len = 9 + (int)strlen(dp); } static int g_start_row = 0; static void sync_start_row(int cur_logical_pos) { int real_row = term_get_cursor_row(); int row_offset = (prompt_len + cur_logical_pos) / g_cols; g_start_row = real_row - row_offset; if (g_start_row < 0) g_start_row = 0; } static void input_pos_to_screen(int pos, int *row, int *col) { int abs = prompt_len + pos; *row = g_start_row + abs / g_cols; *col = abs % g_cols; } static void cursor_to(int pos) { int row, col; input_pos_to_screen(pos, &row, &col); if (row >= g_rows) row = g_rows - 1; if (row < 0) row = 0; vt_goto(row, col); } static int last_row_of(int len) { int abs = prompt_len + len; return g_start_row + (abs > 0 ? (abs - 1) : 0) / g_cols; } static void redraw(const char *buf, int from, int new_len, int old_len, int pos) { cursor_to(from); if (new_len > from) write(1, buf + from, new_len - from); sync_start_row(new_len); if (old_len > new_len) { int old_last = last_row_of(old_len); int new_last = last_row_of(new_len); cursor_to(new_len); vt_eol(); for (int r = new_last + 1; r <= old_last; r++) { if (r >= g_rows) break; vt_goto(r, 0); vt_eol(); } } cursor_to(pos); } static void replace_line(char *buf, int *len, int *pos, const char *newtext, int newlen) { int old_len = *len; for (int i = 0; i < newlen; i++) buf[i] = newtext[i]; buf[newlen] = '\0'; *len = newlen; *pos = newlen; redraw(buf, 0, newlen, old_len, newlen); } static void insert_str(char *buf, int *len, int *pos, int maxlen, const char *s, int slen) { if (*len + slen >= maxlen) return; int old_len = *len; for (int i = *len; i >= *pos; i--) buf[i + slen] = buf[i]; for (int i = 0; i < slen; i++) buf[*pos + i] = s[i]; *len += slen; buf[*len] = '\0'; cursor_to(*pos); write(1, buf + *pos, *len - *pos); sync_start_row(*len); *pos += slen; cursor_to(*pos); } static int find_word_start(const char *buf, int pos) { int p = pos; while (p > 0 && buf[p - 1] != ' ') p--; return p; } static void list_dir_matches(const char *dir, const char *prefix, int plen, char matches[][256], int *nmatch, int max) { DIR *d = opendir(dir); if (!d) return; struct dirent *de; while (*nmatch < max && (de = readdir(d)) != NULL) { if (strncmp(de->d_name, prefix, plen) == 0) { strncpy(matches[*nmatch], de->d_name, 255); matches[*nmatch][255] = '\0'; (*nmatch)++; } } closedir(d); } static void do_tab_complete(char *buf, int *len, int *pos, int maxlen) { int ws_start = find_word_start(buf, *pos); int wlen = *pos - ws_start; if (wlen <= 0) { insert_str(buf, len, pos, maxlen, " ", 4); return; } char word[256]; if (wlen > 255) wlen = 255; memcpy(word, buf + ws_start, wlen); word[wlen] = '\0'; int is_first_word = 1; for (int i = 0; i < ws_start; i++) { if (buf[i] != ' ') { is_first_word = 0; break; } } char matches[32][256]; int nmatch = 0; if (is_first_word) { const char *pathvar = env_get("PATH"); char ptmp[ENV_VAL_MAX]; strncpy(ptmp, pathvar, sizeof(ptmp) - 1); char *p = ptmp; while (*p && nmatch < 32) { char *seg = p; while (*p && *p != ':') p++; if (*p == ':') *p++ = '\0'; if (seg[0]) list_dir_matches(seg, word, wlen, matches, &nmatch, 32); } const char *builtins[] = {"help","exit","cd","export","unset",NULL}; for (int i = 0; builtins[i] && nmatch < 32; i++) { if (strncmp(builtins[i], word, wlen) == 0) { strncpy(matches[nmatch], builtins[i], 255); nmatch++; } } } else { char dirp[VFS_MAX_PATH]; const char *prefix = word; char *last_slash = NULL; for (int i = 0; word[i]; i++) if (word[i] == '/') last_slash = &word[i]; if (last_slash) { int dlen = (int)(last_slash - word); char raw_dir[256]; memcpy(raw_dir, word, dlen); raw_dir[dlen] = '\0'; if (raw_dir[0] == '\0') strcpy(raw_dir, "/"); resolve_path(cwd, raw_dir, dirp, sizeof(dirp)); prefix = last_slash + 1; } else { strncpy(dirp, cwd, sizeof(dirp) - 1); } int plen = (int)strlen(prefix); list_dir_matches(dirp, prefix, plen, matches, &nmatch, 32); } if (nmatch == 0) { insert_str(buf, len, pos, maxlen, " ", 4); return; } if (nmatch == 1) { const char *m = matches[0]; int mlen = (int)strlen(m); int tail = mlen - wlen; if (tail > 0) { const char *suffix = m + wlen; if (is_first_word) { int need = tail; if (*len + need >= maxlen) return; int old_len = *len; for (int i = *len; i >= *pos; i--) buf[i + need] = buf[i]; for (int i = 0; i < tail; i++) buf[*pos + i] = suffix[i]; *len += need; buf[*len] = '\0'; cursor_to(*pos); write(1, buf + *pos, *len - *pos); sync_start_row(*len); *pos += need; cursor_to(*pos); } else { insert_str(buf, len, pos, maxlen, suffix, tail); } } return; } int common = (int)strlen(matches[0]); for (int i = 1; i < nmatch; i++) { int j = 0; while (j < common && matches[0][j] == matches[i][j]) j++; common = j; } int extra = common - wlen; if (extra > 0) { insert_str(buf, len, pos, maxlen, matches[0] + wlen, extra); return; } putchar(10); for (int i = 0; i < nmatch; i++) { fputs(" ", stdout); fputs(matches[i], stdout); } putchar(10); print_prompt(); write(1, buf, *len); sync_start_row(*len); cursor_to(*pos); } static int readline_edit(char *buf, int maxlen) { term_update_size(); { int real_row = term_get_cursor_row(); g_start_row = real_row - prompt_len / g_cols; if (g_start_row < 0) g_start_row = 0; } int len = 0, pos = 0, hidx = 0; static char saved[LINE_MAX]; saved[0] = '\0'; buf[0] = '\0'; for (;;) { char c; if (read(0, &c, 1) <= 0) return -1; if (c == '\x1b') { char s[4]; if (read(0, &s[0], 1) <= 0) continue; if (s[0] != '[') continue; if (read(0, &s[1], 1) <= 0) continue; if (s[1] == 'A') { if (hidx == 0) strncpy(saved, buf, LINE_MAX - 1); if (hidx < hist_count) { hidx++; const char *h = hist_get(hidx); if (h) { int hl = (int)strlen(h); if (hl > maxlen - 1) hl = maxlen - 1; replace_line(buf, &len, &pos, h, hl); } } continue; } if (s[1] == 'B') { if (hidx > 0) { hidx--; const char *h = hidx == 0 ? saved : hist_get(hidx); if (!h) h = ""; int hl = (int)strlen(h); if (hl > maxlen - 1) hl = maxlen - 1; replace_line(buf, &len, &pos, h, hl); } continue; } if (s[1] == 'C') { if (pos < len) { pos++; cursor_to(pos); } continue; } if (s[1] == 'D') { if (pos > 0) { pos--; cursor_to(pos); } continue; } if (s[1] == 'H') { pos = 0; cursor_to(0); continue; } if (s[1] == 'F') { pos = len; cursor_to(len); continue; } if (s[1] >= '1' && s[1] <= '6') { char tilde; read(0, &tilde, 1); if (tilde != '~') continue; if (s[1] == '3' && pos < len) { for (int i = pos; i < len - 1; i++) buf[i] = buf[i + 1]; len--; buf[len] = '\0'; redraw(buf, pos, len, len + 1, pos); } else if (s[1] == '1') { pos = 0; cursor_to(0); } else if (s[1] == '4') { pos = len; cursor_to(len); } } continue; } if (c == '\n' || c == '\r') { buf[len] = '\0'; cursor_to(len); putchar(10); return len; } if (c == 3) { fputs("^C", stdout); putchar(10); buf[0] = '\0'; return 0; } if (c == 4) { if (len == 0) return -1; continue; } if (c == 1) { pos = 0; cursor_to(0); continue; } if (c == 5) { pos = len; cursor_to(len); continue; } if (c == '\t') { do_tab_complete(buf, &len, &pos, maxlen); continue; } if (c == 11) { if (pos < len) { int old_len = len; len = pos; buf[len] = '\0'; redraw(buf, pos, len, old_len, pos); } continue; } if (c == 21) { if (pos > 0) { int old_len = len, del = pos; for (int i = 0; i < len - del; i++) buf[i] = buf[i + del]; len -= del; pos = 0; buf[len] = '\0'; redraw(buf, 0, len, old_len, 0); } continue; } if (c == 23) { if (pos > 0) { int p = pos; while (p > 0 && buf[p - 1] == ' ') p--; while (p > 0 && buf[p - 1] != ' ') p--; int old_len = len, del = pos - p; for (int i = p; i < len - del; i++) buf[i] = buf[i + del]; len -= del; pos = p; buf[len] = '\0'; redraw(buf, p, len, old_len, p); } continue; } if (c == '\b' || c == 0x7F) { if (pos > 0) { int old_len = len; for (int i = pos - 1; i < len - 1; i++) buf[i] = buf[i + 1]; len--; pos--; buf[len] = '\0'; redraw(buf, pos, len, old_len, pos); } continue; } if (c >= 0x20 && c < 0x7F) { if (len >= maxlen - 1) continue; for (int i = len; i > pos; i--) buf[i] = buf[i - 1]; buf[pos] = c; len++; buf[len] = '\0'; cursor_to(pos); write(1, buf + pos, len - pos); sync_start_row(len); pos++; cursor_to(pos); } } } #define MAX_ARGS 32 static int tokenize(char *line, char *argv[], int maxargs) { int argc = 0; char *p = line; while (*p) { while (isspace((unsigned char)*p)) p++; if (!*p) break; if (argc >= maxargs - 1) break; char *out = p; argv[argc++] = out; int in_dquote = 0, in_squote = 0; while (*p) { if (in_dquote) { if (*p == '"') { in_dquote = 0; p++; } else { *out++ = *p++; } } else if (in_squote) { if (*p == '\'') { in_squote = 0; p++; } else { *out++ = *p++; } } else { if (*p == '"') { in_dquote = 1; p++; } else if (*p == '\'') { in_squote = 1; p++; } else if (isspace((unsigned char)*p)) { p++; break; } else { *out++ = *p++; } } } *out = '\0'; if (in_dquote || in_squote) { argv[argc] = NULL; return -1; } } argv[argc] = NULL; return argc; } static void cmd_help(void) { putchar(10); fputs(" " C_CYAN "Cervus Shell" C_RESET " - commands\n", stdout); fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); fputs(" " C_BOLD "help" C_RESET " show this message\n", stdout); fputs(" " C_BOLD "cd" C_RESET " change directory\n", stdout); fputs(" " C_BOLD "export" C_RESET " N=V set variable N to value V\n", stdout); fputs(" " C_BOLD "unset" C_RESET " N delete variable N\n", stdout); fputs(" " C_BOLD "env" C_RESET " list variables\n", stdout); fputs(" " C_BOLD "exit" C_RESET " quit shell\n", stdout); fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); fputs(" " C_BOLD "Programs:" C_RESET " ls, cat, echo, pwd, clear, uname\n", stdout); fputs(" meminfo, cpuinfo, ps, kill, find, stat, wc, yes, sleep\n", stdout); fputs(" mount, umount, mkfs, lsblk, mv, rm, mkdir, touch\n", stdout); fputs(" " C_RED "shutdown" C_RESET ", " C_CYAN "reboot" C_RESET "\n", stdout); fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); fputs(" " C_BOLD "Operators:" C_RESET " cmd1 " C_YELLOW ";" C_RESET " cmd2 " C_YELLOW "&&" C_RESET " " C_YELLOW "||" C_RESET "\n", stdout); fputs(" " C_BOLD "Tab" C_RESET " auto-complete / 4 spaces\n", stdout); fputs(" " C_BOLD "Ctrl+C" C_RESET " interrupt\n", stdout); fputs(" " C_BOLD "Ctrl+A/E" C_RESET " beginning/end of line\n", stdout); fputs(" " C_BOLD "Ctrl+K/U" C_RESET " delete to end/beginning\n", stdout); fputs(" " C_BOLD "Ctrl+W" C_RESET " delete word\n", stdout); fputs(" " C_BOLD "Arrows" C_RESET " cursor / history\n", stdout); fputs(" " C_GRAY "-----------------------------------" C_RESET "\n", stdout); putchar(10); } static int cmd_cd(const char *path) { if (!path || !path[0] || strcmp(path, "~") == 0) { const char *home = env_get("HOME"); path = (home && home[0]) ? home : "/"; } char np[VFS_MAX_PATH]; resolve_path(cwd, path, np, sizeof(np)); struct stat st; if (stat(np, &st) < 0) { fputs(C_RED "cd: not found: " C_RESET, stdout); fputs(path, stdout); putchar(10); return 1; } if (st.st_type != 1) { fputs(C_RED "cd: not a dir: " C_RESET, stdout); fputs(path, stdout); putchar(10); return 1; } strncpy(cwd, np, sizeof(cwd) - 1); cwd[sizeof(cwd) - 1] = '\0'; chdir(np); return 0; } static int valid_varname(const char *s) { if (!s || !*s) return 0; if (!isalpha((unsigned char)*s) && *s != '_') return 0; for (s++; *s; s++) if (!isalnum((unsigned char)*s) && *s != '_') return 0; return 1; } static int cmd_export(int argc, char *argv[]) { if (argc < 2) { fputs(C_RED "export: usage: export NAME=VALUE\n" C_RESET, stdout); return 1; } if (argc > 2) { fputs(C_RED "export: invalid syntax\n" C_RESET, stdout); return 1; } char *arg = argv[1]; char *eq_pos = strchr(arg, '='); if (!eq_pos) { if (!valid_varname(arg)) { fputs(C_RED "export: not a valid identifier\n" C_RESET, stdout); return 1; } env_set(arg, ""); return 0; } *eq_pos = '\0'; const char *name = arg, *val = eq_pos + 1; if (!valid_varname(name)) { *eq_pos = '='; fputs(C_RED "export: not a valid identifier\n" C_RESET, stdout); return 1; } env_set(name, val); *eq_pos = '='; return 0; } static int cmd_unset(int argc, char *argv[]) { if (argc < 2) { fputs(C_RED "unset: usage: unset NAME\n" C_RESET, stdout); return 1; } for (int i = 1; i < argc; i++) env_unset(argv[i]); return 0; } static int find_in_path(const char *cmd, char *out, size_t outsz) { const char *pathvar = env_get("PATH"); if (!pathvar || !pathvar[0]) { path_join("/bin", cmd, out, outsz); struct stat st; return stat(out, &st) == 0 && st.st_type != 1; } char tmp[ENV_VAL_MAX]; strncpy(tmp, pathvar, sizeof(tmp) - 1); char *p = tmp; while (*p) { char *seg = p; while (*p && *p != ':') p++; if (*p == ':') *p++ = '\0'; if (!seg[0]) continue; char candidate[VFS_MAX_PATH]; path_join(seg, cmd, candidate, sizeof(candidate)); struct stat st; if (stat(candidate, &st) == 0 && st.st_type != 1) { strncpy(out, candidate, outsz - 1); return 1; } } return 0; } typedef enum { REDIR_NONE = 0, REDIR_OUT, REDIR_APPEND, REDIR_IN } redir_type_t; typedef struct { redir_type_t type; char path[VFS_MAX_PATH]; } redir_t; static int parse_redirects(char *argv[], int *argc, redir_t redirs[], int max_redirs, int *nredirs) { *nredirs = 0; int new_argc = 0; for (int i = 0; i < *argc; i++) { const char *a = argv[i]; redir_type_t rt = REDIR_NONE; const char *target = NULL; if (strcmp(a, ">>") == 0) { rt = REDIR_APPEND; if (i + 1 < *argc) target = argv[++i]; } else if (strcmp(a, ">") == 0) { rt = REDIR_OUT; if (i + 1 < *argc) target = argv[++i]; } else if (strcmp(a, "<") == 0) { rt = REDIR_IN; if (i + 1 < *argc) target = argv[++i]; } else if (a[0] == '>' && a[1] == '>' && a[2] != '\0') { rt = REDIR_APPEND; target = a + 2; } else if (a[0] == '>' && a[1] != '>' && a[1] != '\0') { rt = REDIR_OUT; target = a + 1; } else if (a[0] == '<' && a[1] != '\0') { rt = REDIR_IN; target = a + 1; } else { argv[new_argc++] = argv[i]; continue; } if (!target || !target[0]) { fputs(C_RED "syntax error: missing redirection target\n" C_RESET, stdout); return -1; } if (*nredirs < max_redirs) { redirs[*nredirs].type = rt; char resolved[VFS_MAX_PATH]; resolve_path(cwd, target, resolved, sizeof(resolved)); strncpy(redirs[*nredirs].path, resolved, VFS_MAX_PATH - 1); redirs[*nredirs].path[VFS_MAX_PATH - 1] = '\0'; (*nredirs)++; } } argv[new_argc] = NULL; *argc = new_argc; return 0; } static int run_single(char *line) { char expanded[LINE_MAX]; expand_vars(line, expanded, sizeof(expanded)); char buf[LINE_MAX]; strncpy(buf, expanded, LINE_MAX - 1); char *argv[MAX_ARGS]; int argc = tokenize(buf, argv, MAX_ARGS); if (argc < 0) { fputs(C_RED "syntax error: unclosed quote\n" C_RESET, stdout); return 1; } if (!argc) return 0; redir_t redirs[8]; int nredirs = 0; if (parse_redirects(argv, &argc, redirs, 8, &nredirs) < 0) return 1; if (!argc) return 0; const char *cmd = argv[0]; if (strcmp(cmd, "help") == 0) { cmd_help(); return 0; } if (strcmp(cmd, "exit") == 0) { fputs("Goodbye!\n", stdout); exit(0); } if (strcmp(cmd, "cd") == 0) return cmd_cd(argc > 1 ? argv[1] : NULL); if (strcmp(cmd, "export") == 0) return cmd_export(argc, argv); if (strcmp(cmd, "unset") == 0) return cmd_unset(argc, argv); char binpath[VFS_MAX_PATH]; if (cmd[0] == '/') { strncpy(binpath, cmd, sizeof(binpath) - 1); binpath[sizeof(binpath) - 1] = '\0'; } else if (cmd[0] == '.') { resolve_path(cwd, cmd, binpath, sizeof(binpath)); } else { if (!find_in_path(cmd, binpath, sizeof(binpath))) { char t_cwd[VFS_MAX_PATH]; resolve_path(cwd, cmd, t_cwd, sizeof(t_cwd)); struct stat st; if (stat(t_cwd, &st) == 0 && st.st_type != 1) { strncpy(binpath, t_cwd, sizeof(binpath) - 1); binpath[sizeof(binpath) - 1] = '\0'; } else { fputs(C_RED "not found: " C_RESET, stdout); fputs(cmd, stdout); putchar(10); return 127; } } } #define REAL_ARGV_MAX (MAX_ARGS + ENV_MAX_VARS + 4) char *real_argv_buf[REAL_ARGV_MAX]; static char _cwd_flag[VFS_MAX_PATH + 8]; static char _env_flags[ENV_MAX_VARS][ENV_NAME_MAX + ENV_VAL_MAX + 8]; int ri = 0; real_argv_buf[ri++] = binpath; for (int i = 1; i < argc; i++) real_argv_buf[ri++] = argv[i]; snprintf(_cwd_flag, sizeof(_cwd_flag), "--cwd=%s", cwd); real_argv_buf[ri++] = _cwd_flag; for (int ei = 0; ei < g_env_count && ri < REAL_ARGV_MAX - 1; ei++) { snprintf(_env_flags[ei], sizeof(_env_flags[ei]), "--env:%s=%s", g_env[ei].name, g_env[ei].value); real_argv_buf[ri++] = _env_flags[ei]; } real_argv_buf[ri] = NULL; pid_t child = fork(); if (child < 0) { fputs(C_RED "fork failed" C_RESET "\n", stdout); return 1; } if (child == 0) { for (int i = 0; i < nredirs; i++) { int fd = -1; int target_fd = -1; if (redirs[i].type == REDIR_OUT) { fd = open(redirs[i].path, O_WRONLY | O_CREAT | O_TRUNC, 0644); target_fd = 1; } else if (redirs[i].type == REDIR_APPEND) { fd = open(redirs[i].path, O_WRONLY | O_CREAT | O_APPEND, 0644); target_fd = 1; } else if (redirs[i].type == REDIR_IN) { fd = open(redirs[i].path, O_RDONLY, 0); target_fd = 0; } if (fd < 0) { fputs(C_RED "redirect: cannot open: " C_RESET, stdout); fputs(redirs[i].path, stdout); putchar('\n'); exit(1); } dup2(fd, target_fd); close(fd); } execve(binpath, (char *const *)real_argv_buf, NULL); fputs(C_RED "exec failed: " C_RESET, stdout); fputs(binpath, stdout); putchar(10); exit(127); } int status = 0; waitpid(child, &status, 0); return (status >> 8) & 0xFF; } typedef enum { CH_NONE = 0, CH_SEQ, CH_AND, CH_OR } chain_t; static void run_command(char *line) { char work[LINE_MAX]; strncpy(work, line, LINE_MAX - 1); char *segs[64]; chain_t ops[64]; int ns = 1; segs[0] = work; ops[0] = CH_NONE; char *p = work; while (*p) { if (*p == '"') { p++; while (*p && *p != '"') p++; if (*p) p++; continue; } if (*p == '\'') { p++; while (*p && *p != '\'') p++; if (*p) p++; continue; } if (*p == '&' && *(p+1) == '&') { *p='\0'; p+=2; while(isspace((unsigned char)*p))p++; ops[ns]=CH_AND; segs[ns]=p; ns++; continue; } if (*p == '|' && *(p+1) == '|') { *p='\0'; p+=2; while(isspace((unsigned char)*p))p++; ops[ns]=CH_OR; segs[ns]=p; ns++; continue; } if (*p == ';') { *p='\0'; p++; while(isspace((unsigned char)*p))p++; ops[ns]=CH_SEQ; segs[ns]=p; ns++; continue; } p++; } int rc = 0; for (int i = 0; i < ns; i++) { char *s = segs[i]; while (isspace((unsigned char)*s)) s++; size_t sl = strlen(s); while (sl > 0 && isspace((unsigned char)s[sl - 1])) s[--sl] = '\0'; if (!s[0]) continue; if (i > 0) { if (ops[i] == CH_AND && rc != 0) continue; if (ops[i] == CH_OR && rc == 0) continue; } rc = run_single(s); } g_last_rc = rc; } static void print_motd(void) { int fd = open("/mnt/etc/motd", O_RDONLY, 0); if (fd < 0) fd = open("/etc/motd", O_RDONLY, 0); if (fd < 0) { putchar(10); fputs(" Cervus OS v0.0.2\n Type 'help' for commands.\n", stdout); putchar(10); return; } char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf) - 1); close(fd); if (n > 0) { buf[n] = '\0'; write(1, buf, n); } } static int g_installed = 0; static int sys_disk_mount(const char *dev, const char *path) { return (int)syscall2(SYS_DISK_MOUNT, dev, path); } static int launch_installer(void) { const char *path = "/bin/install-on-disk"; struct stat st; if (stat(path, &st) != 0) { fputs(C_RED " install-on-disk not found on system.\n" C_RESET, stdout); return -1; } const char *argv[4]; argv[0] = path; argv[1] = "--env:MODE=live"; argv[2] = "--cwd=/"; argv[3] = NULL; pid_t child = fork(); if (child < 0) { fputs(C_RED " fork failed\n" C_RESET, stdout); return -1; } if (child == 0) { execve(path, (char *const *)argv, NULL); fputs(C_RED " exec install-on-disk failed\n" C_RESET, stdout); exit(127); } int status = 0; waitpid(child, &status, 0); return (status >> 8) & 0xFF; } static int ask_install_or_live(void) { fputs("\x1b[2J\x1b[H", stdout); fputs("\n", stdout); fputs(C_CYAN " Cervus OS" C_RESET " - Live ISO\n", stdout); fputs(C_GRAY " -----------------------------------" C_RESET "\n\n", stdout); fputs(" A disk has been detected on this machine.\n", stdout); fputs(" What would you like to do?\n\n", stdout); fputs(" [" C_BOLD "1" C_RESET "] Install Cervus to disk\n", stdout); fputs(" [" C_BOLD "2" C_RESET "] Continue in Live mode\n\n", stdout); fputs(" Choice [1-2]: ", stdout); char c = 0; while (1) { if (read(0, &c, 1) <= 0) continue; if (c == '1' || c == '2') { putchar(c); putchar(10); break; } } return (c == '1') ? 1 : 0; } int main(int argc, char **argv) { (void)argc; (void)argv; struct stat dev_st; int has_disk = (stat("/dev/hda", &dev_st) == 0); int has_hda2 = (stat("/dev/hda2", &dev_st) == 0); int has_hda_legacy = 0; int disk_mounted = 0; if (has_hda2) { int mr = sys_disk_mount("hda2", "/mnt"); if (mr == 0) { disk_mounted = 1; g_installed = 1; } } else if (has_disk) { int mr = sys_disk_mount("hda", "/mnt"); if (mr == 0) { disk_mounted = 1; g_installed = 1; has_hda_legacy = 1; } } (void)has_hda_legacy; if (!disk_mounted && has_disk) { if (ask_install_or_live() == 1) { launch_installer(); struct stat retry_st; if (stat("/dev/hda2", &retry_st) == 0) { if (sys_disk_mount("hda2", "/mnt") == 0) { disk_mounted = 1; g_installed = 1; } } } } if (disk_mounted) { strncpy(cwd, "/mnt/home", sizeof(cwd)); env_set("HOME", "/mnt/home"); env_set("PATH", "/mnt/bin:/mnt/apps:/mnt/usr/bin"); env_set("SHELL", "/mnt/bin/shell"); } else { strncpy(cwd, "/", sizeof(cwd)); env_set("HOME", "/"); env_set("PATH", "/bin:/apps:/usr/bin"); env_set("SHELL", "/bin/shell"); } if (!disk_mounted && has_disk) env_set("MODE", "live"); else if (!has_disk) env_set("MODE", "live"); else env_set("MODE", "installed"); print_motd(); { static char hist_path[VFS_MAX_PATH]; const char *h = env_get("HOME"); if (h && h[0]) { path_join(h, ".history", hist_path, sizeof(hist_path)); g_hist_file = hist_path; hist_load(hist_path); } } if (!has_disk) { fputs(C_YELLOW " [Live Mode]" C_RESET " No disk detected. All changes are in RAM.\n\n", stdout); } else if (!disk_mounted) { fputs(C_YELLOW " [Live Mode]" C_RESET " Disk not mounted.\n\n", stdout); } char line[LINE_MAX]; for (;;) { print_prompt(); int n = readline_edit(line, LINE_MAX); if (n < 0) { fputs("\nSession ended. Restarting shell...\n", stdout); const char *h = env_get("HOME"); strncpy(cwd, (h && h[0]) ? h : "/", sizeof(cwd)); print_motd(); continue; } int len = (int)strlen(line); while (len > 0 && isspace((unsigned char)line[len - 1])) line[--len] = '\0'; if (len > 0) { hist_push(line); run_command(line); } } }