push
This commit is contained in:
+179
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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); }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user