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
+179
View File
@@ -0,0 +1,179 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <sys/cervus.h>
#include <cervus_util.h>
#define A_INVERT "\x1b[7m"
#define A_BOLD "\x1b[1m"
#define A_RESET "\x1b[0m"
static const char *MNAME[12] = {
"January","February","March","April","May","June",
"July","August","September","October","November","December"
};
static int MDAYS[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
static int is_leap(int y)
{
return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}
static int days_in_month(int y, int m)
{
if (m == 2 && is_leap(y)) return 29;
return MDAYS[m - 1];
}
static int first_dow(int y, int m)
{
int64_t days = 0;
for (int yr = 1970; yr < y; yr++) days += is_leap(yr) ? 366 : 365;
int md[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
md[1] = is_leap(y) ? 29 : 28;
for (int mo = 0; mo < m - 1; mo++) days += md[mo];
return (int)((days + 4) % 7);
}
static void get_today(int *out_y, int *out_m, int *out_d)
{
*out_y = 0; *out_m = 0; *out_d = 0;
cervus_timespec_t ts;
if (cervus_clock_gettime(CLOCK_REALTIME, &ts) != 0 || ts.tv_sec <= 0)
return;
int64_t t = ts.tv_sec;
int64_t days = t / 86400;
int y = 1970;
while (1) {
int dy = is_leap(y) ? 366 : 365;
if (days < dy) break;
days -= dy;
y++;
}
int md[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
md[1] = is_leap(y) ? 29 : 28;
int mo = 0;
while (mo < 12) { if (days < md[mo]) break; days -= md[mo]; mo++; }
*out_y = y;
*out_m = mo + 1;
*out_d = (int)(days + 1);
}
static void print_header(int y, int m, int today_y, int today_m)
{
char title[64];
snprintf(title, sizeof(title), "%s %d", MNAME[m - 1], y);
int tlen = (int)strlen(title);
int pad = (20 - tlen) / 2;
for (int i = 0; i < pad; i++) putchar(' ');
if (y == today_y && m == today_m) {
fputs(A_INVERT A_BOLD, stdout);
fputs(title, stdout);
fputs(A_RESET, stdout);
} else {
fputs(A_BOLD, stdout);
fputs(title, stdout);
fputs(A_RESET, stdout);
}
putchar('\n');
fputs(C_GRAY " Su Mo Tu We Th Fr Sa" A_RESET "\n", stdout);
}
static void print_month(int y, int m, int today_y, int today_m, int today_d)
{
print_header(y, m, today_y, today_m);
int mdays = days_in_month(y, m);
int dow = first_dow(y, m);
for (int i = 0; i < dow; i++) fputs(" ", stdout);
for (int d = 1; d <= mdays; d++) {
int is_today = (y == today_y && m == today_m && d == today_d);
if (is_today) {
fprintf(stdout, " " A_INVERT A_BOLD "%2d" A_RESET, d);
} else {
fprintf(stdout, " %2d", d);
}
if (++dow == 7) {
putchar('\n');
dow = 0;
}
}
if (dow != 0) putchar('\n');
}
static void print_help(void)
{
fputs(
"Usage: cal [OPTION] [[MONTH] YEAR]\n"
"Display a calendar.\n"
"\n"
" (no args) current month\n"
" YEAR all 12 months of YEAR\n"
" MONTH YEAR specific month (MONTH = 1-12)\n"
" --help display this help and exit\n"
"\n"
"Today's day is highlighted with " A_INVERT "inverted colours" A_RESET ".\n"
"The current month header is also " A_INVERT "inverted" A_RESET ".\n",
stdout
);
}
int main(int argc, char **argv)
{
int today_y, today_m, today_d;
get_today(&today_y, &today_m, &today_d);
int year = today_y ? today_y : 2025;
int month = today_m ? today_m : 1;
const char *args[2] = {NULL, NULL};
int real_argc = 0;
int flag_help = 0;
for (int i = 1; i < argc; i++) {
if (is_shell_flag(argv[i])) continue;
if (strcmp(argv[i], "--help") == 0) { flag_help = 1; continue; }
if (real_argc < 2) args[real_argc] = argv[i];
real_argc++;
}
if (flag_help) { print_help(); return 0; }
if (real_argc == 2) {
month = atoi(args[0]);
year = atoi(args[1]);
} else if (real_argc == 1) {
year = atoi(args[0]);
putchar('\n');
fprintf(stdout, " " A_BOLD "%d" A_RESET "\n\n", year);
for (int m = 1; m <= 12; m++) {
print_month(year, m, today_y, today_m, today_d);
putchar('\n');
}
return 0;
}
if (month < 1 || month > 12) {
fputs("cal: invalid month (must be 1-12)\n", stderr);
return 1;
}
putchar('\n');
print_month(year, month, today_y, today_m, today_d);
putchar('\n');
return 0;
}
+206
View File
@@ -0,0 +1,206 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <cervus_util.h>
#define SCALE 1000000LL
typedef long long fx;
static const char *p;
static void skip_ws(void) { while (*p && isspace((unsigned char)*p)) p++; }
static fx parse_number(int *err)
{
skip_ws();
long long intpart = 0, frac = 0, fscale = 1;
int had_digit = 0;
while (isdigit((unsigned char)*p)) {
intpart = intpart * 10 + (*p - '0');
had_digit = 1; p++;
}
if (*p == '.') {
p++;
while (isdigit((unsigned char)*p)) {
if (fscale < SCALE) { frac = frac * 10 + (*p - '0'); fscale *= 10; }
had_digit = 1; p++;
}
}
if (!had_digit) { *err = 1; return 0; }
return intpart * SCALE + (frac * SCALE) / fscale;
}
static fx parse_expr(int *err);
static fx parse_primary(int *err)
{
skip_ws();
if (*p == '(') {
p++;
fx v = parse_expr(err);
skip_ws();
if (*p == ')') p++;
else *err = 1;
return v;
}
if (*p == '-') { p++; return -parse_primary(err); }
if (*p == '+') { p++; return parse_primary(err); }
return parse_number(err);
}
static fx parse_term(int *err)
{
fx v = parse_primary(err);
while (!*err) {
skip_ws();
if (*p == '*') {
p++;
fx r = parse_primary(err);
v = (v * r) / SCALE;
} else if (*p == '/') {
p++;
fx r = parse_primary(err);
if (r == 0) { *err = 2; return 0; }
v = (v * SCALE) / r;
} else break;
}
return v;
}
static fx parse_expr(int *err)
{
fx v = parse_term(err);
while (!*err) {
skip_ws();
if (*p == '+') { p++; v += parse_term(err); }
else if (*p == '-') { p++; v -= parse_term(err); }
else break;
}
return v;
}
static void print_fx(fx v)
{
if (v < 0) { putchar('-'); v = -v; }
long long ip = v / SCALE;
long long fp = v % SCALE;
char ibuf[32];
int ilen = 0;
if (ip == 0) {
ibuf[ilen++] = '0';
} else {
long long tmp = ip;
while (tmp > 0) { ibuf[ilen++] = '0' + (int)(tmp % 10); tmp /= 10; }
for (int a = 0, b = ilen - 1; a < b; a++, b--) {
char t = ibuf[a]; ibuf[a] = ibuf[b]; ibuf[b] = t;
}
}
ibuf[ilen] = '\0';
fputs(ibuf, stdout);
if (fp != 0) {
char fbuf[8];
int flen = 0;
long long tmp = fp;
long long scale = SCALE;
fbuf[flen++] = '.';
while (scale > 1) {
scale /= 10;
fbuf[flen++] = '0' + (int)(tmp / scale);
tmp %= scale;
}
fbuf[flen] = '\0';
while (flen > 1 && fbuf[flen - 1] == '0') { fbuf[--flen] = '\0'; }
fputs(fbuf, stdout);
}
putchar('\n');
}
static void calc_help(void)
{
putchar('\n');
fputs(C_CYAN "Cervus calc" C_RESET " - fixed-point calculator (6 decimal digits)\n", stdout);
fputs(C_GRAY "-------------------------------------------" C_RESET "\n", stdout);
fputs(C_BOLD "Operators:" C_RESET " + - * / ( )\n", stdout);
fputs(C_GRAY "-------------------------------------------" C_RESET "\n", stdout);
fputs(C_BOLD "Examples:" C_RESET "\n", stdout);
fputs(" calc> " C_YELLOW "3.14 + 5" C_RESET " = 8.14\n", stdout);
fputs(" calc> " C_YELLOW "10 / 3" C_RESET " = 3.333333\n", stdout);
fputs(" calc> " C_YELLOW "2 * (3 + 4)" C_RESET " = 14\n", stdout);
fputs(" calc> " C_YELLOW "(1.5 + 2.5) * 4" C_RESET " = 16\n", stdout);
fputs(" calc> " C_YELLOW "100 / 7" C_RESET " = 14.285714\n", stdout);
fputs(" calc> " C_YELLOW "-5 + 3" C_RESET " = -2\n", stdout);
fputs(C_GRAY "-------------------------------------------" C_RESET "\n", stdout);
fputs(" Type " C_BOLD "q" C_RESET " or " C_BOLD "exit" C_RESET " to quit.\n", stdout);
putchar('\n');
}
static int readline_calc(char *buf, int maxlen)
{
int i = 0;
for (;;) {
char c;
ssize_t r = read(0, &c, 1);
if (r <= 0) {
buf[i] = '\0';
return (i > 0) ? i : -1;
}
if (c == '\r') continue;
if (c == '\n') {
write(1, "\n", 1);
buf[i] = '\0';
return i;
}
if (c == '\b' || c == 0x7F) {
if (i > 0) {
i--;
write(1, "\b \b", 3);
}
continue;
}
if (c == 0x03) {
write(1, "^C\n", 3);
buf[0] = '\0';
return 0;
}
if (c >= 0x20 && c < 0x7F && i < maxlen - 1) {
buf[i++] = c;
write(1, &c, 1);
}
}
}
int main(int argc, char **argv)
{
(void)argc; (void)argv;
fputs(C_CYAN "Cervus calc" C_RESET " - fixed-point (6 digits). "
"Type " C_BOLD "help" C_RESET " for examples, " C_BOLD "q" C_RESET " to exit.\n", stdout);
char line[256];
for (;;) {
fputs("calc> ", stdout);
int n = readline_calc(line, sizeof(line));
if (n < 0) { putchar('\n'); break; }
while (n > 0 && isspace((unsigned char)line[n - 1])) line[--n] = '\0';
if (n == 0) continue;
if (strcmp(line, "q") == 0 || strcmp(line, "quit") == 0 ||
strcmp(line, "exit") == 0) break;
if (strcmp(line, "help") == 0) { calc_help(); continue; }
p = line;
int err = 0;
fx v = parse_expr(&err);
skip_ws();
if (*p != '\0') err = 1;
if (err == 1) fputs(C_RED " parse error\n" C_RESET, stdout);
else if (err == 2) fputs(C_RED " division by zero\n" C_RESET, stdout);
else { fputs(" = ", stdout); print_fx(v); }
}
putchar('\n');
return 0;
}
+98
View File
@@ -0,0 +1,98 @@
#include <stdio.h>
#include <stdint.h>
#include <sys/cervus.h>
static const int MDAYS[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
static const char *MNAME[12] = {
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"
};
static const char *WDAY[7] = {"Thu","Fri","Sat","Sun","Mon","Tue","Wed"};
static int is_leap(int y) { return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); }
static int bcd2bin(int v) { return (v & 0x0F) + ((v >> 4) * 10); }
static int cmos_read(int reg)
{
if (cervus_ioport_write(0x70, 1, (uint32_t)(reg & 0x7F)) < 0) return -1;
return (int)(cervus_ioport_read(0x71, 1) & 0xFF);
}
static void cmos_wait_ready(void)
{
for (int i = 0; i < 2000; i++) {
cervus_ioport_write(0x70, 1, 0x0A);
uint32_t sta = cervus_ioport_read(0x71, 1);
if (!(sta & 0x80)) return;
}
}
static int64_t rtc_read_unix(void)
{
cmos_wait_ready();
int sec = cmos_read(0x00);
int min = cmos_read(0x02);
int hour = cmos_read(0x04);
int mday = cmos_read(0x07);
int mon = cmos_read(0x08);
int year = cmos_read(0x09);
if (sec < 0 || min < 0 || hour < 0 || mday < 0 || mon < 0 || year < 0) return 0;
cervus_ioport_write(0x70, 1, 0x0B);
int regb = (int)cervus_ioport_read(0x71, 1);
int binary_mode = (regb >= 0) && (regb & 0x04);
int hour24 = (regb >= 0) && (regb & 0x02);
if (!binary_mode) {
sec = bcd2bin(sec);
min = bcd2bin(min);
mday = bcd2bin(mday);
mon = bcd2bin(mon);
year = bcd2bin(year);
if (!hour24 && (hour & 0x80)) hour = bcd2bin(hour & 0x7F) + 12;
else hour = bcd2bin(hour);
}
year += (year < 70) ? 2000 : 1900;
if (sec < 0 || sec > 59 || min < 0 || min > 59) return 0;
if (hour < 0 || hour > 23) return 0;
if (mday < 1 || mday > 31) return 0;
if (mon < 1 || mon > 12) return 0;
if (year < 2000) return 0;
int64_t days = 0;
for (int y = 1970; y < year; y++) days += is_leap(y) ? 366 : 365;
for (int m = 1; m < mon; m++) days += MDAYS[m-1] + (m == 2 && is_leap(year) ? 1 : 0);
days += mday - 1;
return days * 86400LL + (int64_t)hour * 3600LL + (int64_t)min * 60LL + (int64_t)sec;
}
int main(int argc, char **argv)
{
(void)argc; (void)argv;
int64_t t = rtc_read_unix();
if (t <= 0) {
uint64_t up = cervus_uptime_ns() / 1000000000ULL;
fputs(" RTC not available.\n", stdout);
printf(" Uptime: %lus\n", (unsigned long)up);
return 0;
}
int wday = (int)((t / 86400 + 4) % 7);
int64_t days = t / 86400;
int64_t rem = t % 86400;
if (rem < 0) { rem += 86400; days--; }
int hour = (int)(rem / 3600);
int min = (int)((rem % 3600) / 60);
int sec = (int)(rem % 60);
int year = 1970;
while (1) { int dy = is_leap(year) ? 366 : 365; if (days < dy) break; days -= dy; year++; }
int mon = 0;
while (mon < 12) {
int dm = MDAYS[mon] + (mon == 1 && is_leap(year) ? 1 : 0);
if (days < dm) break;
days -= dm; mon++;
}
int mday = (int)days + 1;
printf(" %s %s %2d %02d:%02d:%02d UTC %04d\n",
WDAY[wday], MNAME[mon], mday, hour, min, sec, year);
return 0;
}
+15
View File
@@ -0,0 +1,15 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char **argv)
{
puts("=== execve_target: STARTED ===");
printf(" PID: %d\n", (int)getpid());
printf(" PPID: %d\n", (int)getppid());
printf(" argc: %d\n", argc);
for (int i = 0; i < argc; i++)
printf(" argv[%d] = %s\n", i, argv[i]);
puts("=== execve_target: DONE, exit(99) ===");
return 99;
}
+93
View File
@@ -0,0 +1,93 @@
#include <stdio.h>
#include <stdint.h>
#include <sys/cervus.h>
#include <cervus_util.h>
static const char *logo[] = {
" L ",
" 'k.i , ",
" \";\"+U., ",
" \\_' -. ",
" .f ,_.;.",
" I ,f ",
" ' ",
NULL
};
static void cpuid_leaf(uint32_t leaf, uint32_t *a, uint32_t *b,
uint32_t *c, uint32_t *d)
{
asm volatile ("cpuid"
: "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d)
: "0"(leaf), "2"(0));
}
static void print_uptime(void)
{
uint64_t ns = cervus_uptime_ns();
uint64_t total_s = ns / 1000000000ULL;
uint64_t ms = (ns / 1000000ULL) % 1000ULL;
uint64_t secs = total_s % 60;
uint64_t mins = (total_s / 60) % 60;
uint64_t hours = (total_s / 3600) % 24;
uint64_t days = total_s / 86400;
fputs("uptime: ", stdout);
if (days > 0) printf("%lud, ", (unsigned long)days);
printf("%02lu:%02lu:%02lu (%lus %lums)",
(unsigned long)hours, (unsigned long)mins, (unsigned long)secs,
(unsigned long)total_s, (unsigned long)ms);
}
static void print_cpu(void)
{
uint32_t a, b, c, d;
cpuid_leaf(0x80000000, &a, &b, &c, &d);
if (a >= 0x80000004) {
char brand[49];
uint32_t *p = (uint32_t *)brand;
cpuid_leaf(0x80000002, &p[0], &p[1], &p[2], &p[3]);
cpuid_leaf(0x80000003, &p[4], &p[5], &p[6], &p[7]);
cpuid_leaf(0x80000004, &p[8], &p[9], &p[10], &p[11]);
brand[48] = '\0';
const char *br = brand;
while (*br == ' ') br++;
printf("cpu: %s", br);
}
}
static void print_mem(void)
{
cervus_meminfo_t mi;
if (cervus_meminfo(&mi) != 0) return;
uint64_t used = mi.used_bytes;
uint64_t total = mi.total_bytes;
const uint64_t MiB = 1024ULL * 1024;
const uint64_t GiB = 1024ULL * 1024 * 1024;
fputs("mem: ", stdout);
if (total >= GiB)
printf("%lu.%02lu / %lu.%02lu GiB",
(unsigned long)(used / GiB), (unsigned long)((used % GiB) * 100 / GiB),
(unsigned long)(total / GiB), (unsigned long)((total % GiB) * 100 / GiB));
else
printf("%lu / %lu MiB",
(unsigned long)(used / MiB), (unsigned long)(total / MiB));
}
int main(int argc, char **argv)
{
(void)argc; (void)argv;
putchar('\n');
for (int i = 0; logo[i]; i++) {
printf(" %s ", logo[i]);
switch (i) {
case 1: fputs("os: Cervus OS", stdout); break;
case 2: print_uptime(); break;
case 3: print_cpu(); break;
case 4: fputs(C_RESET "shell: CSH", stdout); break;
case 5: print_mem(); break;
}
putchar('\n');
}
putchar('\n');
return 0;
}
+10
View File
@@ -0,0 +1,10 @@
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
(void)argv;
printf("Hello, Cervus world!\n");
printf("argc = %d\n", argc);
return 0;
}
+908
View File
@@ -0,0 +1,908 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <termios.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <cervus_util.h>
#define NEO_VERSION "0.1"
#define NEO_TABSTOP 4
#define NEO_QUIT_CONFIRM 1
#define KEY_NONE 0
#define KEY_ESC 0x1B
#define KEY_BACKSPACE 127
#define KEY_CTRL(k) ((k) & 0x1f)
#define KEY_ARROW_UP 1000
#define KEY_ARROW_DOWN 1001
#define KEY_ARROW_LEFT 1002
#define KEY_ARROW_RIGHT 1003
#define KEY_HOME 1004
#define KEY_END 1005
#define KEY_DEL 1006
#define KEY_PAGE_UP 1007
#define KEY_PAGE_DOWN 1008
#define TIOCGWINSZ 0x5413
typedef struct { uint16_t ws_row, ws_col, ws_xpixel, ws_ypixel; } neo_winsize_t;
typedef struct {
int size;
int cap;
char *chars;
int rsize;
char *render;
} neo_row_t;
typedef struct {
int cx, cy;
int rx;
int rowoff;
int coloff;
int screenrows;
int screencols;
int numrows;
int rowscap;
neo_row_t *row;
int dirty;
char *filename;
char statusmsg[256];
int statusmsg_visible;
int quit_pending;
long disk_size;
struct termios orig_termios;
} neo_t;
static neo_t E;
static const char *g_cwd = "/";
static void die(const char *msg)
{
write(1, "\x1b[2J", 4);
write(1, "\x1b[H", 3);
if (msg) {
write(2, "neo: ", 5);
write(2, msg, strlen(msg));
write(2, "\n", 1);
}
exit(1);
}
static void disable_raw_mode(void)
{
tcsetattr(0, TCSAFLUSH, &E.orig_termios);
write(1, "\x1b[?7h\x1b[?25h", 11);
}
static void enable_raw_mode(void)
{
if (tcgetattr(0, &E.orig_termios) < 0) die("tcgetattr");
atexit(disable_raw_mode);
struct termios raw = E.orig_termios;
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
raw.c_oflag &= ~(OPOST);
raw.c_cflag |= (CS8);
raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
raw.c_cc[VMIN] = 1;
raw.c_cc[VTIME] = 0;
if (tcsetattr(0, TCSAFLUSH, &raw) < 0) die("tcsetattr");
write(1, "\x1b[?7l", 5);
}
static int read_key(void)
{
char c;
ssize_t n;
while ((n = read(0, &c, 1)) == 0) { }
if (n < 0) return KEY_NONE;
if (c != 0x1B) return (unsigned char)c;
char seq[4];
if (read(0, &seq[0], 1) != 1) return KEY_ESC;
if (read(0, &seq[1], 1) != 1) return KEY_ESC;
if (seq[0] == '[') {
if (seq[1] >= '0' && seq[1] <= '9') {
if (read(0, &seq[2], 1) != 1) return KEY_ESC;
if (seq[2] == '~') {
switch (seq[1]) {
case '1':
case '7': return KEY_HOME;
case '3': return KEY_DEL;
case '4':
case '8': return KEY_END;
case '5': return KEY_PAGE_UP;
case '6': return KEY_PAGE_DOWN;
}
}
} else {
switch (seq[1]) {
case 'A': return KEY_ARROW_UP;
case 'B': return KEY_ARROW_DOWN;
case 'C': return KEY_ARROW_RIGHT;
case 'D': return KEY_ARROW_LEFT;
case 'H': return KEY_HOME;
case 'F': return KEY_END;
}
}
} else if (seq[0] == 'O') {
switch (seq[1]) {
case 'H': return KEY_HOME;
case 'F': return KEY_END;
}
}
return KEY_ESC;
}
static void get_window_size(void)
{
neo_winsize_t ws;
if (syscall3(SYS_IOCTL, 1, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0 && ws.ws_row > 0) {
E.screencols = ws.ws_col;
E.screenrows = ws.ws_row;
} else {
E.screencols = 80;
E.screenrows = 24;
}
E.screenrows -= 2;
if (E.screenrows < 1) E.screenrows = 1;
}
static int row_cx_to_rx(neo_row_t *row, int cx)
{
int rx = 0;
for (int j = 0; j < cx && j < row->size; j++) {
if (row->chars[j] == '\t') rx += (NEO_TABSTOP - (rx % NEO_TABSTOP));
else rx++;
}
return rx;
}
static int row_rx_to_cx(neo_row_t *row, int rx)
{
int cur_rx = 0;
int cx;
for (cx = 0; cx < row->size; cx++) {
if (row->chars[cx] == '\t') cur_rx += (NEO_TABSTOP - (cur_rx % NEO_TABSTOP));
else cur_rx++;
if (cur_rx > rx) return cx;
}
return cx;
}
static void row_update(neo_row_t *row)
{
int tabs = 0;
for (int j = 0; j < row->size; j++) if (row->chars[j] == '\t') tabs++;
free(row->render);
row->render = malloc(row->size + tabs * (NEO_TABSTOP - 1) + 1);
int idx = 0;
for (int j = 0; j < row->size; j++) {
if (row->chars[j] == '\t') {
row->render[idx++] = ' ';
while (idx % NEO_TABSTOP != 0) row->render[idx++] = ' ';
} else {
row->render[idx++] = row->chars[j];
}
}
row->render[idx] = '\0';
row->rsize = idx;
}
static void rows_reserve(int want)
{
if (want <= E.rowscap) return;
int nc = E.rowscap ? E.rowscap * 2 : 32;
while (nc < want) nc *= 2;
neo_row_t *nr = malloc(sizeof(neo_row_t) * nc);
if (!nr) die("out of memory");
if (E.row) {
memcpy(nr, E.row, sizeof(neo_row_t) * E.numrows);
}
for (int i = E.numrows; i < nc; i++) {
nr[i].size = 0; nr[i].cap = 0; nr[i].chars = NULL;
nr[i].rsize = 0; nr[i].render = NULL;
}
E.row = nr;
E.rowscap = nc;
}
static void row_insert_at(int at, const char *s, int len)
{
if (at < 0 || at > E.numrows) return;
rows_reserve(E.numrows + 1);
for (int i = E.numrows; i > at; i--) E.row[i] = E.row[i - 1];
neo_row_t *r = &E.row[at];
r->size = len;
r->cap = len + 1;
r->chars = malloc(r->cap);
if (!r->chars) die("out of memory");
if (len > 0) memcpy(r->chars, s, len);
r->chars[len] = '\0';
r->render = NULL;
r->rsize = 0;
row_update(r);
E.numrows++;
E.dirty = 1;
}
static void row_free(neo_row_t *r)
{
free(r->chars);
free(r->render);
r->chars = NULL; r->render = NULL;
r->size = 0; r->cap = 0; r->rsize = 0;
}
static void row_delete_at(int at)
{
if (at < 0 || at >= E.numrows) return;
row_free(&E.row[at]);
for (int i = at; i < E.numrows - 1; i++) E.row[i] = E.row[i + 1];
E.numrows--;
E.dirty = 1;
}
static void row_reserve(neo_row_t *r, int want)
{
if (want <= r->cap) return;
int nc = r->cap ? r->cap * 2 : 16;
while (nc < want) nc *= 2;
char *nb = malloc(nc);
if (!nb) die("out of memory");
if (r->chars) memcpy(nb, r->chars, r->size);
nb[r->size] = '\0';
free(r->chars);
r->chars = nb;
r->cap = nc;
}
static void row_insert_char(neo_row_t *r, int at, int ch)
{
if (at < 0 || at > r->size) at = r->size;
row_reserve(r, r->size + 2);
memmove(&r->chars[at + 1], &r->chars[at], r->size - at + 1);
r->chars[at] = (char)ch;
r->size++;
row_update(r);
E.dirty = 1;
}
static void row_append_string(neo_row_t *r, const char *s, int len)
{
row_reserve(r, r->size + len + 1);
memcpy(&r->chars[r->size], s, len);
r->size += len;
r->chars[r->size] = '\0';
row_update(r);
E.dirty = 1;
}
static void row_delete_char(neo_row_t *r, int at)
{
if (at < 0 || at >= r->size) return;
memmove(&r->chars[at], &r->chars[at + 1], r->size - at);
r->size--;
row_update(r);
E.dirty = 1;
}
static void editor_insert_char(int ch)
{
if (E.cy == E.numrows) row_insert_at(E.numrows, "", 0);
row_insert_char(&E.row[E.cy], E.cx, ch);
E.cx++;
}
static void editor_insert_newline(void)
{
if (E.cx == 0) {
row_insert_at(E.cy, "", 0);
} else {
neo_row_t *r = &E.row[E.cy];
row_insert_at(E.cy + 1, &r->chars[E.cx], r->size - E.cx);
r = &E.row[E.cy];
r->size = E.cx;
r->chars[r->size] = '\0';
row_update(r);
}
E.cy++;
E.cx = 0;
}
static void editor_delete_char(void)
{
if (E.cy == E.numrows) return;
if (E.cx == 0 && E.cy == 0) return;
neo_row_t *r = &E.row[E.cy];
if (E.cx > 0) {
row_delete_char(r, E.cx - 1);
E.cx--;
} else {
E.cx = E.row[E.cy - 1].size;
row_append_string(&E.row[E.cy - 1], r->chars, r->size);
row_delete_at(E.cy);
E.cy--;
}
}
static char *rows_to_string(int *len)
{
int total = 0;
for (int j = 0; j < E.numrows; j++) total += E.row[j].size + 1;
char *buf = malloc(total + 1);
if (!buf) die("out of memory");
char *p = buf;
for (int j = 0; j < E.numrows; j++) {
memcpy(p, E.row[j].chars, E.row[j].size);
p += E.row[j].size;
*p++ = '\n';
}
*p = '\0';
*len = total;
return buf;
}
static void set_status(const char *fmt, ...);
static void editor_open(const char *filename)
{
free(E.filename);
size_t fl = strlen(filename);
E.filename = malloc(fl + 1);
memcpy(E.filename, filename, fl + 1);
char full[512];
resolve_path(g_cwd, filename, full, sizeof(full));
int fd = open(full, O_RDONLY, 0);
if (fd < 0) {
E.disk_size = -1;
set_status("New file: %s", filename);
return;
}
struct stat st;
if (fstat(fd, &st) < 0) { close(fd); E.disk_size = -1; return; }
size_t sz = (size_t)st.st_size;
E.disk_size = (long)sz;
char *buf = malloc(sz + 1);
if (!buf) { close(fd); die("out of memory"); }
size_t total = 0;
while (total < sz) {
ssize_t r = read(fd, buf + total, sz - total);
if (r <= 0) break;
total += (size_t)r;
}
close(fd);
buf[total] = '\0';
size_t i = 0;
while (i < total) {
size_t start = i;
while (i < total && buf[i] != '\n' && buf[i] != '\r') i++;
int len = (int)(i - start);
row_insert_at(E.numrows, buf + start, len);
if (i < total && buf[i] == '\r') i++;
if (i < total && buf[i] == '\n') i++;
}
free(buf);
E.dirty = 0;
}
static char *prompt(const char *prompt_fmt);
static int editor_save(void)
{
if (!E.filename) {
char *name = prompt("Save as (ESC to cancel): %s");
if (!name) {
set_status("Save cancelled");
return -1;
}
if (name[0] == '\0') {
free(name);
set_status("Save cancelled (empty filename)");
return -1;
}
E.filename = name;
E.disk_size = -1;
}
char full[512];
resolve_path(g_cwd, E.filename, full, sizeof(full));
if (E.disk_size >= 0) {
struct stat st;
if (stat(full, &st) == 0 && (long)st.st_size != E.disk_size) {
char *ans = prompt("File changed on disk! Overwrite? [y/N]: %s");
if (!ans) { set_status("Save cancelled"); return -1; }
int yes = (ans[0] == 'y' || ans[0] == 'Y');
free(ans);
if (!yes) { set_status("Save cancelled"); return -1; }
}
}
int len = 0;
char *buf = rows_to_string(&len);
int fd = open(full, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
free(buf);
set_status("Save failed: errno=%d (%s)", errno, full);
return -1;
}
ssize_t written = 0;
while (written < len) {
ssize_t w = write(fd, buf + written, len - written);
if (w <= 0) { close(fd); free(buf); set_status("Save failed (write err)"); return -1; }
written += w;
}
close(fd);
free(buf);
E.dirty = 0;
E.disk_size = len;
set_status("Saved %d bytes to %s", len, E.filename);
return 0;
}
typedef struct { char *b; int len; int cap; } abuf_t;
static void ab_append(abuf_t *ab, const char *s, int len)
{
if (ab->len + len > ab->cap) {
int nc = ab->cap ? ab->cap * 2 : 1024;
while (nc < ab->len + len) nc *= 2;
char *nb = malloc(nc);
if (!nb) die("out of memory");
if (ab->b) memcpy(nb, ab->b, ab->len);
free(ab->b);
ab->b = nb;
ab->cap = nc;
}
memcpy(ab->b + ab->len, s, len);
ab->len += len;
}
static void ab_free(abuf_t *ab) { free(ab->b); ab->b = NULL; ab->len = 0; ab->cap = 0; }
static void scroll(void)
{
E.rx = 0;
if (E.cy < E.numrows) E.rx = row_cx_to_rx(&E.row[E.cy], E.cx);
if (E.cy < E.rowoff) E.rowoff = E.cy;
if (E.cy >= E.rowoff + E.screenrows) E.rowoff = E.cy - E.screenrows + 1;
if (E.rx < E.coloff) E.coloff = E.rx;
if (E.rx >= E.coloff + E.screencols) E.coloff = E.rx - E.screencols + 1;
}
static void draw_rows(abuf_t *ab)
{
char pos[16];
int limit = E.screencols - 1;
if (limit < 1) limit = 1;
for (int y = 0; y < E.screenrows; y++) {
int n = snprintf(pos, sizeof(pos), "\x1b[%d;1H", y + 1);
ab_append(ab, pos, n);
int filerow = y + E.rowoff;
if (filerow >= E.numrows) {
if (E.numrows == 0 && y == E.screenrows / 3) {
char welcome[80];
int wl = snprintf(welcome, sizeof(welcome),
"neo editor -- version %s -- press ESC to exit", NEO_VERSION);
if (wl > limit) wl = limit;
int padding = (limit - wl) / 2;
if (padding > 0) { ab_append(ab, "~", 1); padding--; }
while (padding-- > 0) ab_append(ab, " ", 1);
ab_append(ab, welcome, wl);
} else {
ab_append(ab, "~", 1);
}
} else {
int len = E.row[filerow].rsize - E.coloff;
if (len < 0) len = 0;
if (len > limit) len = limit;
ab_append(ab, E.row[filerow].render + E.coloff, len);
}
ab_append(ab, "\x1b[K", 3);
}
}
static void draw_status(abuf_t *ab)
{
char pos[16];
int n = snprintf(pos, sizeof(pos), "\x1b[%d;1H", E.screenrows + 1);
ab_append(ab, pos, n);
ab_append(ab, "\x1b[7m", 4);
char status[256], rstatus[80];
int len = snprintf(status, sizeof(status), " %.40s%s ",
E.filename ? E.filename : "[No Name]",
E.dirty ? " [modified]" : "");
int rlen = snprintf(rstatus, sizeof(rstatus), "%d/%d ",
E.cy + 1, E.numrows);
int limit = E.screencols - 1;
if (limit < 1) limit = 1;
if (len > limit) len = limit;
ab_append(ab, status, len);
while (len < limit) {
if (limit - len == rlen) { ab_append(ab, rstatus, rlen); break; }
ab_append(ab, " ", 1);
len++;
}
ab_append(ab, "\x1b[m", 3);
ab_append(ab, "\x1b[K", 3);
}
static void draw_message(abuf_t *ab)
{
char pos[16];
int n = snprintf(pos, sizeof(pos), "\x1b[%d;1H", E.screenrows + 2);
ab_append(ab, pos, n);
ab_append(ab, "\x1b[K", 3);
if (E.statusmsg_visible) {
int mlen = strlen(E.statusmsg);
if (mlen > E.screencols) mlen = E.screencols;
ab_append(ab, E.statusmsg, mlen);
} else {
const char *hint = " ^S=save ^Q=quit ^F=find ^G=goto ^B=top ^E=end ESC=exit";
int mlen = strlen(hint);
if (mlen > E.screencols) mlen = E.screencols;
ab_append(ab, hint, mlen);
}
}
static void refresh_screen(void)
{
scroll();
abuf_t ab = {0};
ab_append(&ab, "\x1b[?25l", 6);
draw_rows(&ab);
draw_status(&ab);
draw_message(&ab);
int cursor_row = (E.cy - E.rowoff) + 1;
int cursor_col = (E.rx - E.coloff) + 1;
char curbuf[32];
int n = snprintf(curbuf, sizeof(curbuf), "\x1b[%d;%dH", cursor_row, cursor_col);
ab_append(&ab, curbuf, n);
ab_append(&ab, "\x1b[?25h", 6);
write(1, ab.b, ab.len);
ab_free(&ab);
}
static void set_status(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
va_end(ap);
E.statusmsg_visible = 1;
}
static char *prompt_cb(const char *prompt_fmt, void (*callback)(char *, int))
{
size_t bufcap = 128;
size_t buflen = 0;
char *buf = malloc(bufcap);
buf[0] = '\0';
for (;;) {
set_status(prompt_fmt, buf);
refresh_screen();
int c = read_key();
if (c == KEY_DEL || c == KEY_CTRL('h') || c == KEY_BACKSPACE) {
if (buflen > 0) buf[--buflen] = '\0';
} else if (c == KEY_ESC) {
set_status("");
E.statusmsg_visible = 0;
if (callback) callback(buf, c);
free(buf);
return NULL;
} else if (c == '\r' || c == '\n') {
if (buflen != 0 || callback) {
set_status("");
E.statusmsg_visible = 0;
if (callback) callback(buf, c);
return buf;
}
} else if (!iscntrl(c) && c < 128) {
if (buflen + 1 >= bufcap) {
bufcap *= 2;
char *nb = malloc(bufcap);
memcpy(nb, buf, buflen);
free(buf);
buf = nb;
}
buf[buflen++] = (char)c;
buf[buflen] = '\0';
}
if (callback) callback(buf, c);
}
}
static char *prompt(const char *prompt_fmt)
{
return prompt_cb(prompt_fmt, NULL);
}
static void move_cursor(int key)
{
neo_row_t *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
switch (key) {
case KEY_ARROW_LEFT:
if (E.cx > 0) E.cx--;
else if (E.cy > 0) { E.cy--; E.cx = E.row[E.cy].size; }
break;
case KEY_ARROW_RIGHT:
if (row && E.cx < row->size) E.cx++;
else if (row && E.cx == row->size) { E.cy++; E.cx = 0; }
break;
case KEY_ARROW_UP:
if (E.cy > 0) E.cy--;
break;
case KEY_ARROW_DOWN:
if (E.cy < E.numrows) E.cy++;
break;
}
row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
int rowlen = row ? row->size : 0;
if (E.cx > rowlen) E.cx = rowlen;
}
static void goto_line(void)
{
char *p = prompt("Go to line: %s (ESC cancels)");
if (!p) return;
int n = atoi(p);
free(p);
if (n < 1) n = 1;
if (n > E.numrows) n = E.numrows == 0 ? 1 : E.numrows;
E.cy = n - 1;
E.cx = 0;
}
static int __find_last_match = -1;
static int __find_direction = 1;
static int __find_saved_cx, __find_saved_cy;
static int __find_saved_rowoff, __find_saved_coloff;
static char *__find_last_query = NULL;
static void editor_find_callback(char *query, int key)
{
if (key == '\r' || key == '\n' || key == KEY_ESC) {
__find_last_match = -1;
__find_direction = 1;
return;
}
if (key == KEY_ARROW_DOWN || key == KEY_ARROW_RIGHT) {
__find_direction = 1;
} else if (key == KEY_ARROW_UP || key == KEY_ARROW_LEFT) {
__find_direction = -1;
} else {
__find_last_match = -1;
__find_direction = 1;
}
if (!query || !query[0]) return;
int current = __find_last_match;
if (current == -1) current = E.cy;
int qlen = (int)strlen(query);
for (int i = 0; i < E.numrows; i++) {
current += __find_direction;
if (current == -1) current = E.numrows - 1;
else if (current == E.numrows) current = 0;
neo_row_t *row = &E.row[current];
char *match = strstr(row->render, query);
if (match) {
__find_last_match = current;
E.cy = current;
int rx = (int)(match - row->render);
E.cx = row_rx_to_cx(row, rx);
E.rowoff = E.numrows;
(void)qlen;
return;
}
}
}
static void editor_find(void)
{
__find_saved_cx = E.cx;
__find_saved_cy = E.cy;
__find_saved_rowoff = E.rowoff;
__find_saved_coloff = E.coloff;
__find_last_match = -1;
__find_direction = 1;
char *query = prompt_cb(
"Search: %s (Up/Down=prev/next, Enter=keep, ESC=cancel)",
editor_find_callback);
if (query) {
if (query[0] == '\0' && __find_last_query) {
free(query);
query = strdup(__find_last_query);
if (query) {
editor_find_callback(query, 0);
}
}
if (query && query[0]) {
free(__find_last_query);
__find_last_query = strdup(query);
}
if (query) free(query);
} else {
E.cx = __find_saved_cx;
E.cy = __find_saved_cy;
E.rowoff = __find_saved_rowoff;
E.coloff = __find_saved_coloff;
}
}
static int process_key(void)
{
int c = read_key();
switch (c) {
case '\r':
case '\n':
editor_insert_newline();
break;
case KEY_ESC:
if (E.dirty && NEO_QUIT_CONFIRM && !E.quit_pending) {
set_status("Unsaved changes. ESC again to exit without saving, Ctrl-S to save.");
E.quit_pending = 1;
return 1;
}
return 0;
case KEY_CTRL('s'):
editor_save();
E.quit_pending = 0;
break;
case KEY_CTRL('q'):
if (E.dirty && NEO_QUIT_CONFIRM && !E.quit_pending) {
set_status("Unsaved changes. Ctrl-Q again to force quit.");
E.quit_pending = 1;
return 1;
}
return 0;
case KEY_CTRL('g'):
goto_line();
break;
case KEY_CTRL('f'):
editor_find();
break;
case KEY_CTRL('b'):
E.cy = 0;
E.cx = 0;
E.rowoff = 0;
E.coloff = 0;
break;
case KEY_CTRL('e'):
E.cy = E.numrows == 0 ? 0 : E.numrows - 1;
if (E.cy < E.numrows) E.cx = E.row[E.cy].size;
else E.cx = 0;
break;
case KEY_HOME:
E.cx = 0;
break;
case KEY_END:
if (E.cy < E.numrows) E.cx = E.row[E.cy].size;
break;
case KEY_BACKSPACE:
case KEY_CTRL('h'):
editor_delete_char();
break;
case KEY_DEL:
move_cursor(KEY_ARROW_RIGHT);
editor_delete_char();
break;
case KEY_PAGE_UP:
case KEY_PAGE_DOWN: {
if (c == KEY_PAGE_UP) {
E.cy = E.rowoff;
} else {
E.cy = E.rowoff + E.screenrows - 1;
if (E.cy > E.numrows) E.cy = E.numrows;
}
int times = E.screenrows;
while (times--) move_cursor(c == KEY_PAGE_UP ? KEY_ARROW_UP : KEY_ARROW_DOWN);
break;
}
case KEY_ARROW_UP:
case KEY_ARROW_DOWN:
case KEY_ARROW_LEFT:
case KEY_ARROW_RIGHT:
move_cursor(c);
break;
case KEY_CTRL('l'):
case 0:
break;
default:
if (c >= 32 && c < 127) editor_insert_char(c);
else if (c == '\t') editor_insert_char('\t');
break;
}
E.quit_pending = 0;
return 1;
}
static void init_editor(void)
{
E.cx = 0; E.cy = 0; E.rx = 0;
E.rowoff = 0; E.coloff = 0;
E.numrows = 0; E.rowscap = 0; E.row = NULL;
E.dirty = 0;
E.filename = NULL;
E.statusmsg[0] = '\0';
E.statusmsg_visible = 0;
E.quit_pending = 0;
E.disk_size = -1;
get_window_size();
}
int main(int argc, char **argv)
{
g_cwd = get_cwd_flag(argc, argv);
init_editor();
enable_raw_mode();
const char *file_to_open = NULL;
for (int i = 1; i < argc; i++) {
if (is_shell_flag(argv[i])) continue;
file_to_open = argv[i];
break;
}
if (file_to_open) editor_open(file_to_open);
write(1, "\x1b[2J", 4);
write(1, "\x1b[H", 3);
refresh_screen();
while (process_key()) {
refresh_screen();
}
write(1, "\x1b[2J", 4);
write(1, "\x1b[H", 3);
disable_raw_mode();
return 0;
}
+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); }
}
}
+91
View File
@@ -0,0 +1,91 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/cervus.h>
static int failed = 0;
static void ok(const char *s) { printf(" [OK] %s\n", s); }
static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; }
static int file_exists(const char *path)
{
struct stat st;
return stat(path, &st) == 0;
}
static const char *resolve_app(const char *name, char *buf, size_t bufsz)
{
const char *prefixes[] = { "/mnt/apps/", "/apps/", "/mnt/bin/", "/bin/", NULL };
for (int i = 0; prefixes[i]; i++) {
size_t pl = strlen(prefixes[i]);
size_t nl = strlen(name);
if (pl + nl + 1 > bufsz) continue;
memcpy(buf, prefixes[i], pl);
memcpy(buf + pl, name, nl + 1);
if (file_exists(buf)) return buf;
}
return NULL;
}
int main(int argc, char **argv)
{
(void)argc; (void)argv;
puts("--- test_execve ---");
char target[256];
if (!resolve_app("execve_target", target, sizeof(target))) {
printf(" [SKIP] execve_target not found\n");
puts("--- test_execve done ---");
return 0;
}
{
char *const cargv[] = { target, (char *)"hello", (char *)"from", (char *)"execve", NULL };
pid_t child = fork();
if (child < 0) { fail("fork"); return 1; }
if (child == 0) {
execve(target, cargv, NULL);
printf(" [FATAL] execve failed\n");
return 127;
}
int status = 0;
waitpid(child, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 99) ok("execve target exits 99");
else {
printf(" exit status = %d\n", WEXITSTATUS(status));
fail("execve exit code");
}
}
{
pid_t child = fork();
if (child < 0) { fail("fork"); return 1; }
if (child == 0) {
char *const cargv[] = { (char *)"/no/such/binary", NULL };
int r = execve("/no/such/binary", cargv, NULL);
return (r < 0) ? 0 : 1;
}
int status = 0;
waitpid(child, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) ok("execve nonexistent returns error");
else fail("execve nonexistent should return error");
}
{
pid_t child = fork();
if (child < 0) { fail("fork"); return 1; }
if (child == 0) {
execve(target, NULL, NULL);
return 127;
}
int status = 0;
waitpid(child, &status, 0);
if (WIFEXITED(status) && WEXITSTATUS(status) == 99) ok("execve with NULL argv");
else fail("execve NULL argv");
}
puts("--- test_execve done ---");
return failed ? 1 : 0;
}
+164
View File
@@ -0,0 +1,164 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
static int failed = 0;
static void ok(const char *s) { printf(" [OK] %s\n", s); }
static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; }
static int file_exists(const char *path)
{
struct stat st;
return stat(path, &st) == 0;
}
static const char *resolve_etc(const char *name, char *buf, size_t bufsz)
{
const char *prefixes[] = { "/mnt/etc/", "/etc/", NULL };
for (int i = 0; prefixes[i]; i++) {
size_t pl = strlen(prefixes[i]);
size_t nl = strlen(name);
if (pl + nl + 1 > bufsz) continue;
memcpy(buf, prefixes[i], pl);
memcpy(buf + pl, name, nl + 1);
if (file_exists(buf)) return buf;
}
return NULL;
}
int main(int argc, char **argv)
{
(void)argc; (void)argv;
puts("--- test_files ---");
char hostname_path[256], passwd_path[256];
const char *hostname = resolve_etc("hostname", hostname_path, sizeof(hostname_path));
const char *passwd = resolve_etc("passwd", passwd_path, sizeof(passwd_path));
if (!hostname) printf(" [WARN] hostname not found in /etc or /mnt/etc\n");
if (!passwd) printf(" [WARN] passwd not found in /etc or /mnt/etc\n");
{
const char *msg = "hello from test_files\n";
ssize_t w = write(1, msg, strlen(msg));
if (w == (ssize_t)strlen(msg)) ok("write to stdout");
else fail("write to stdout");
}
{
const char *msg = "stderr line\n";
ssize_t w = write(2, msg, strlen(msg));
if (w == (ssize_t)strlen(msg)) ok("write to stderr");
else fail("write to stderr");
}
if (hostname) {
int fd = open(hostname, O_RDONLY);
if (fd >= 0) {
ok("open hostname");
char buf[64];
memset(buf, 0, sizeof(buf));
ssize_t r = read(fd, buf, sizeof(buf) - 1);
if (r > 0) { printf(" hostname = '%s'\n", buf); ok("read hostname"); }
else fail("read hostname");
struct stat st;
if (fstat(fd, &st) == 0) {
printf(" fstat: size=%lu\n", (unsigned long)st.st_size);
ok("fstat");
} else fail("fstat");
close(fd);
ok("close");
} else fail("open hostname");
}
if (passwd) {
struct stat st;
if (stat(passwd, &st) == 0) {
printf(" stat: ino=%lu size=%lu\n",
(unsigned long)st.st_ino, (unsigned long)st.st_size);
ok("stat passwd");
} else fail("stat passwd");
}
{
int fd = open("/no/such/file", O_RDONLY);
if (fd < 0) ok("open nonexistent returns error");
else { close(fd); fail("open nonexistent should fail"); }
}
if (hostname) {
int fd = open(hostname, O_RDONLY);
if (fd >= 0) {
char buf[4]; memset(buf, 0, sizeof(buf));
read(fd, buf, 2);
off_t pos = lseek(fd, 0, SEEK_CUR);
if (pos == 2) ok("lseek SEEK_CUR");
else fail("lseek SEEK_CUR");
lseek(fd, 0, SEEK_SET);
char buf2[4]; memset(buf2, 0, sizeof(buf2));
read(fd, buf2, 2);
if (buf[0] == buf2[0] && buf[1] == buf2[1]) ok("lseek SEEK_SET re-read");
else fail("lseek SEEK_SET re-read");
close(fd);
} else fail("open for lseek test");
}
if (hostname) {
int fd = open(hostname, O_RDONLY);
if (fd >= 0) {
int fd2 = dup(fd);
if (fd2 >= 0 && fd2 != fd) {
ok("dup");
char buf[32]; memset(buf, 0, sizeof(buf));
ssize_t r = read(fd2, buf, sizeof(buf) - 1);
if (r > 0) ok("read from dup'd fd");
else fail("read from dup'd fd");
close(fd2);
} else fail("dup");
close(fd);
} else fail("open for dup test");
}
if (hostname) {
int fd = open(hostname, O_RDONLY);
if (fd >= 0) {
int r = dup2(fd, 10);
if (r == 10) {
ok("dup2");
char buf[32]; memset(buf, 0, sizeof(buf));
ssize_t n = read(10, buf, sizeof(buf) - 1);
if (n > 0) ok("read from dup2 fd");
else fail("read from dup2 fd");
close(10);
} else fail("dup2");
close(fd);
} else fail("open for dup2 test");
}
{
int fd = open("/dev/null", O_WRONLY);
if (fd >= 0) {
ssize_t w = write(fd, "garbage", 7);
if (w == 7) ok("write to /dev/null");
else fail("write to /dev/null");
close(fd);
} else fail("open /dev/null");
}
{
int fd = open("/dev/zero", O_RDONLY);
if (fd >= 0) {
char buf[8]; memset(buf, 0xFF, 8);
ssize_t r = read(fd, buf, 8);
int all_zero = 1;
for (int i = 0; i < 8; i++) if (buf[i]) all_zero = 0;
if (r == 8 && all_zero) ok("read from /dev/zero");
else fail("read from /dev/zero");
close(fd);
} else fail("open /dev/zero");
}
puts("--- test_files done ---");
return failed ? 1 : 0;
}
+78
View File
@@ -0,0 +1,78 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/cervus.h>
static int failed = 0;
static void ok(const char *s) { printf(" [OK] %s\n", s); }
static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; }
int main(int argc, char **argv)
{
(void)argc; (void)argv;
puts("--- test_mem ---");
void *brk0 = sbrk(0);
printf(" initial brk = %p\n", brk0);
if ((uintptr_t)brk0 > 0) ok("sbrk(0) returns valid address");
else fail("sbrk(0)");
void *old = sbrk(4096);
if (old == brk0) ok("sbrk(4096) returns old brk");
else fail("sbrk(4096)");
volatile uint8_t *heap = (volatile uint8_t *)brk0;
heap[0] = 0xAB; heap[4095] = 0xCD;
if (heap[0] == 0xAB && heap[4095] == 0xCD) ok("write/read heap page");
else fail("write/read heap page");
void *cur = sbrk(0);
sbrk(-4096);
void *after = sbrk(0);
if ((uintptr_t)after == (uintptr_t)cur - 4096) ok("sbrk shrink");
else fail("sbrk shrink");
void *m = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (m != MAP_FAILED) {
ok("mmap anonymous 8 KiB");
volatile uint8_t *p = (volatile uint8_t *)m;
p[0] = 0x11; p[8191] = 0x22;
if (p[0] == 0x11 && p[8191] == 0x22) ok("write/read mmap pages");
else fail("write/read mmap pages");
if (munmap(m, 8192) == 0) ok("munmap");
else fail("munmap");
} else fail("mmap anonymous");
void *big = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (big != MAP_FAILED) {
ok("mmap 1 MiB");
volatile uint8_t *bp = (volatile uint8_t *)big;
int ok_all = 1;
for (int i = 0; i < 256; i++) {
bp[i * 4096] = (uint8_t)i;
if (bp[i * 4096] != (uint8_t)i) ok_all = 0;
}
if (ok_all) ok("touch 256 pages of 1 MiB mmap");
else fail("touch mmap pages");
munmap(big, 1024 * 1024);
} else fail("mmap 1 MiB");
{
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr != MAP_FAILED) {
munmap(addr, 4096);
void *fixed = mmap(addr, 4096, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (fixed == addr) ok("MAP_FIXED at specific address");
else fail("MAP_FIXED");
if (fixed != MAP_FAILED) munmap(fixed, 4096);
}
}
puts("--- test_mem done ---");
return failed ? 1 : 0;
}
+82
View File
@@ -0,0 +1,82 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/cervus.h>
static int failed = 0;
static void ok(const char *s) { printf(" [OK] %s\n", s); }
static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; }
int main(int argc, char **argv)
{
(void)argc; (void)argv;
puts("--- test_pipe ---");
int fds[2] = { -1, -1 };
if (pipe(fds) == 0 && fds[0] >= 0 && fds[1] >= 0) ok("pipe() creates two fds");
else { fail("pipe() failed"); return 1; }
printf(" read_fd=%d write_fd=%d\n", fds[0], fds[1]);
{
const char *msg = "hello pipe";
ssize_t w = write(fds[1], msg, strlen(msg));
if (w == (ssize_t)strlen(msg)) ok("write to pipe");
else fail("write to pipe");
char buf[32];
memset(buf, 0, sizeof(buf));
ssize_t r = read(fds[0], buf, sizeof(buf) - 1);
if (r == (ssize_t)strlen(msg) && strcmp(buf, msg) == 0)
ok("read from pipe matches written data");
else fail("read from pipe");
}
close(fds[0]); close(fds[1]);
{
int p[2];
if (pipe(p) != 0) { fail("pipe for fork test"); return 1; }
pid_t child = fork();
if (child < 0) { fail("fork"); return 1; }
if (child == 0) {
close(p[0]);
const char *s = "world";
write(p[1], s, strlen(s));
close(p[1]);
return 0;
}
close(p[1]);
char buf[32]; memset(buf, 0, sizeof(buf));
ssize_t r = read(p[0], buf, sizeof(buf) - 1);
close(p[0]);
int status = 0; waitpid(child, &status, 0);
if (r > 0 && strcmp(buf, "world") == 0) ok("pipe IPC parent<-child");
else fail("pipe IPC");
}
{
int p[2];
if (pipe(p) != 0) { fail("pipe for dup2 test"); return 1; }
pid_t child = fork();
if (child < 0) { fail("fork for dup2"); return 1; }
if (child == 0) {
close(p[0]);
dup2(p[1], 1);
close(p[1]);
const char *s = "from child stdout\n";
write(1, s, strlen(s));
return 0;
}
close(p[1]);
char buf[64]; memset(buf, 0, sizeof(buf));
ssize_t r = read(p[0], buf, sizeof(buf) - 1);
close(p[0]);
int status = 0; waitpid(child, &status, 0);
if (r > 0 && strncmp(buf, "from child stdout", 17) == 0)
ok("dup2 redirects child stdout through pipe");
else fail("dup2 redirect");
}
puts("--- test_pipe done ---");
return failed ? 1 : 0;
}
+48
View File
@@ -0,0 +1,48 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/cervus.h>
static int failed = 0;
static void ok(const char *s) { printf(" [OK] %s\n", s); }
static void fail(const char *s) { printf(" [FAIL] %s\n", s); failed = 1; }
int main(int argc, char **argv)
{
(void)argc; (void)argv;
puts("--- test_process ---");
pid_t my_pid = getpid();
if (my_pid > 0) ok("getpid > 0"); else fail("getpid > 0");
pid_t my_ppid = getppid();
if (my_ppid > 0) ok("getppid > 0"); else fail("getppid > 0");
pid_t child = fork();
if (child < 0) { fail("fork"); return 1; }
if (child == 0) {
if (getppid() == my_pid) ok("child: ppid == parent pid");
else fail("child: ppid == parent pid");
return 42;
}
int status = 0;
pid_t reaped = waitpid(child, &status, 0);
if (reaped == child) ok("waitpid returns child pid");
else fail("waitpid returns child pid");
if (WIFEXITED(status)) ok("WIFEXITED");
else fail("WIFEXITED");
if (WEXITSTATUS(status) == 42) ok("exit code 42");
else { printf(" exit code = %d\n", WEXITSTATUS(status)); fail("exit code 42"); }
pid_t r = waitpid(-1, &status, WNOHANG);
if (r == 0) ok("WNOHANG returns 0 when no zombie");
else fail("WNOHANG returns 0 when no zombie");
if (getuid() == 0) ok("getuid == 0 (root)");
else fail("getuid == 0");
puts("--- test_process done ---");
return failed ? 1 : 0;
}
+22
View File
@@ -0,0 +1,22 @@
#include <stdio.h>
#include <sys/cervus.h>
int main(int argc, char **argv)
{
(void)argc; (void)argv;
uint64_t ns = cervus_uptime_ns();
uint64_t total_s = ns / 1000000000ULL;
uint64_t ms = (ns / 1000000ULL) % 1000ULL;
uint64_t secs = total_s % 60;
uint64_t mins = (total_s / 60) % 60;
uint64_t hours = (total_s / 3600) % 24;
uint64_t days = total_s / 86400;
fputs(" Uptime: ", stdout);
if (days > 0)
printf("%lu day%s, ", (unsigned long)days, days != 1 ? "s" : "");
printf("%02lu:%02lu:%02lu (%lus %lums)\n",
(unsigned long)hours, (unsigned long)mins, (unsigned long)secs,
(unsigned long)total_s, (unsigned long)ms);
return 0;
}