This commit is contained in:
alexvoste
2026-05-07 02:22:25 +03:00
commit 1a9fd27a31
226 changed files with 29188 additions and 0 deletions
+975
View File
@@ -0,0 +1,975 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/cervus.h>
#include <sys/syscall.h>
#include <cervus_util.h>
#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 " <dir> 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); }
}
}