#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IMAGE_NAME "Cervus" #define VERSION "v0.0.2" #define QEMUFLAGS "-m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on " \ "-drive file=cervus_disk.img,format=raw,if=ide,index=0,media=disk " #define LIMINE_CONF_PATH "/boot/limine/limine.conf" #define LIMINE_CONF_BACKUP "/boot/limine/limine.conf.cervus-backup" #define CERVUS_BOOT_DIR "/boot/cervus" #define CERVUS_MARKER "# --- CERVUS OS ENTRY ---" #define WALLPAPER_SRC "wallpapers/cervus1280x720.png" #define WALLPAPER_DST "boot():/boot/wallpapers/cervus.png" #define APPS_DIR "usr/apps" #define BIN_APPS_DIR "usr/bin" #define INSTALLER_DIR "usr/installer" #define SYSROOT_DIR "usr/sysroot" #define SYSROOT_INC "usr/sysroot/usr/include" #define SYSROOT_LIB "usr/sysroot/usr/lib" #define LIBCERVUS_DIR "usr/lib/libcervus" #define SHELL_SRC "usr/apps/shell.c" #define SHELL_ELF "usr/apps/shell.elf" #define INSTALLER_SRC "usr/installer/install-on-disk.c" #define INSTALLER_ELF "usr/installer/install-on-disk.elf" #define INITRAMFS_TAR "initramfs.tar" #define INITRAMFS_ROOTFS "rootfs" #define BIGPATH (PATH_MAX * 2) #define SHORTPATH 512 static void path_join2(char *dst, size_t dst_size, const char *a, const char *b) { if (dst_size == 0) return; size_t la = strlen(a); size_t lb = strlen(b); if (la >= dst_size) la = dst_size - 1; memcpy(dst, a, la); size_t off = la; if (off + 1 < dst_size) dst[off++] = '/'; if (lb > dst_size - off - 1) lb = dst_size - off - 1; memcpy(dst + off, b, lb); dst[off + lb] = '\0'; } #define COLOR_RESET "\033[0m" #define COLOR_RED "\033[91m" #define COLOR_GREEN "\033[92m" #define COLOR_YELLOW "\033[93m" #define COLOR_BLUE "\033[94m" #define COLOR_MAGENTA "\033[95m" #define COLOR_CYAN "\033[96m" #define COLOR_BOLD "\033[1m" const char *DIRS_TO_CLEAN[] = { "bin", "obj", "iso_root", "limine", "kernel/linker-scripts", "demo_iso", "limine-tools", "edk2-ovmf", INITRAMFS_ROOTFS, NULL }; const char *FILES_TO_CLEAN[] = { "Cervus.iso", "Cervus.hdd", "kernel/.deps-obtained", "limine.conf", "OS-TREE.txt", "log.txt", SHELL_ELF, INSTALLER_ELF, SYSROOT_LIB "/libcervus.a", SYSROOT_LIB "/crt0.o", SYSROOT_DIR "/usr/bin/tcc", SYSROOT_LIB "/tcc/libtcc1.a", INITRAMFS_TAR, "cervus_disk.img", NULL }; const char *SSE_FILES[] = { "sse.c", "fpu.c", "fabs.c", "pow.c", "pow10.c", "serial.c", "snprintf.c", "printf.c", NULL }; struct Dependency { const char *name; const char *url; const char *commit; }; struct Dependency DEPENDENCIES[] = { {"freestnd-c-hdrs", "https://codeberg.org/OSDev/freestnd-c-hdrs-0bsd.git", "5df91dd7062ad0c54f5ffd86193bb9f008677631"}, {"cc-runtime", "https://codeberg.org/OSDev/cc-runtime.git", "dae79833b57a01b9fd3e359ee31def69f5ae899b"}, {"limine-protocol", "https://codeberg.org/Limine/limine-protocol.git", "c4616df2572d77c60020bdefa617dd9bdcc6566a"}, {NULL, NULL, NULL} }; bool ARG_NO_CLEAN = false; bool ARG_TREE = false; bool ARG_STRUCTURE_ONLY = false; bool ARG_NO_INITRAMFS = false; bool ARG_RESET_HW_CONF = false; bool ARG_RESET_DISK = false; bool ARG_LIVE = false; char **TREE_FILES = NULL; int TREE_FILES_COUNT = 0; int TREE_FILES_CAP = 0; void print_color(const char *color, const char *fmt, ...) { va_list args; va_start(args, fmt); printf("%s", color); vprintf(fmt, args); printf("%s\n", COLOR_RESET); va_end(args); } void add_tree_file(const char *filename) { if (TREE_FILES_COUNT >= TREE_FILES_CAP) { TREE_FILES_CAP = (TREE_FILES_CAP == 0) ? 8 : TREE_FILES_CAP * 2; TREE_FILES = realloc(TREE_FILES, TREE_FILES_CAP * sizeof(char *)); } TREE_FILES[TREE_FILES_COUNT++] = strdup(filename); } bool should_print_content(const char *filename) { if (TREE_FILES_COUNT == 0) return true; for (int i = 0; i < TREE_FILES_COUNT; i++) if (strcmp(filename, TREE_FILES[i]) == 0) return true; return false; } int cmd_run(bool verbose, const char *fmt, ...) { char cmd[8192]; va_list args; va_start(args, fmt); vsnprintf(cmd, sizeof(cmd), fmt, args); va_end(args); if (verbose) print_color(COLOR_BLUE, "Running: %s", cmd); int ret = system(cmd); return (ret != 0) ? WEXITSTATUS(ret) : 0; } void ensure_dir(const char *path) { char tmp[1024]; snprintf(tmp, sizeof(tmp), "mkdir -p %s", path); int r = system(tmp); (void)r; } bool file_exists(const char *path) { return access(path, F_OK) == 0; } time_t get_mtime(const char *path) { struct stat attr; if (stat(path, &attr) == 0) return attr.st_mtime; return 0; } void rm_rf(const char *path) { if (file_exists(path)) { print_color(COLOR_BLUE, "Removing %s", path); cmd_run(false, "rm -rf %s", path); } } bool setup_dependencies(void) { print_color(COLOR_GREEN, "Checking dependencies..."); ensure_dir("limine-tools"); for (int i = 0; DEPENDENCIES[i].name != NULL; i++) { char path[PATH_MAX]; snprintf(path, sizeof(path), "limine-tools/%s", DEPENDENCIES[i].name); if (!file_exists(path)) { print_color(COLOR_YELLOW, "Missing %s, setting up...", DEPENDENCIES[i].name); if (cmd_run(true, "git clone %s %s", DEPENDENCIES[i].url, path) != 0) return false; char gc[PATH_MAX + 128]; snprintf(gc, sizeof(gc), "git -C %s -c advice.detachedHead=false checkout %s", path, DEPENDENCIES[i].commit); if (system(gc) != 0) return false; } } return true; } static bool extract_limine_hdd_bin(void) { const char *src = "limine/limine-bios-hdd.h"; const char *dst = "limine/limine-bios-hdd.bin"; if (file_exists(dst) && file_exists(src) && get_mtime(src) <= get_mtime(dst)) { return true; } if (!file_exists(src)) { print_color(COLOR_RED, "limine-bios-hdd.h not found in limine/"); return false; } FILE *in = fopen(src, "r"); if (!in) { print_color(COLOR_RED, "cannot open %s", src); return false; } FILE *out = fopen(dst, "wb"); if (!out) { fclose(in); print_color(COLOR_RED, "cannot create %s", dst); return false; } char line[4096]; size_t total = 0; while (fgets(line, sizeof(line), in)) { char *p = line; while (*p) { if (p[0] == '0' && p[1] == 'x') { unsigned v = 0; p += 2; if (sscanf(p, "%2x", &v) == 1) { unsigned char b = (unsigned char)v; fwrite(&b, 1, 1, out); total++; p += 2; } else { p++; } } else { p++; } } } fclose(in); fclose(out); print_color(COLOR_GREEN, "Extracted limine-bios-hdd.bin (%zu bytes)", total); return true; } bool build_limine(void) { if (file_exists("limine/limine")) { print_color(COLOR_GREEN, "Limine already built"); extract_limine_hdd_bin(); return true; } print_color(COLOR_GREEN, "Building Limine..."); if (file_exists("limine")) rm_rf("limine"); if (cmd_run(true, "git clone https://codeberg.org/Limine/Limine.git limine " "--branch=v11.2.1-binary --depth=1") != 0) return false; if (cmd_run(true, "make -C limine") != 0) return false; if (!extract_limine_hdd_bin()) return false; return true; } void ensure_linker_script(void) { ensure_dir("kernel/linker-scripts"); const char *lds_path = "kernel/linker-scripts/x86_64.lds"; if (file_exists(lds_path)) return; const char *script = "OUTPUT_FORMAT(elf64-x86-64)\n" "ENTRY(kernel_main)\n" "PHDRS {\n" " limine_requests PT_LOAD;\n" " text PT_LOAD;\n" " rodata PT_LOAD;\n" " data PT_LOAD;\n" "}\n" "SECTIONS {\n" " . = 0xffffffff80000000;\n" " .limine_requests : {\n" " KEEP(*(.limine_requests_start))\n" " KEEP(*(.limine_requests))\n" " KEEP(*(.limine_requests_end))\n" " } :limine_requests\n" " . = ALIGN(CONSTANT(MAXPAGESIZE));\n" " .text : {\n" " __start_isr_handlers = .;\n" " KEEP(*(.isr_handlers))\n" " __stop_isr_handlers = .;\n" " __start_irq_handlers = .;\n" " KEEP(*(.irq_handlers))\n" " __stop_irq_handlers = .;\n" " *(.text .text.*)\n" " } :text\n" " . = ALIGN(CONSTANT(MAXPAGESIZE));\n" " .rodata : { *(.rodata .rodata.*) } :rodata\n" " .note.gnu.build-id : { *(.note.gnu.build-id) } :rodata\n" " . = ALIGN(CONSTANT(MAXPAGESIZE));\n" " .data : {\n" " *(.data .data.*)\n" " . = ALIGN(4096);\n" " __percpu_start = .;\n" " KEEP(*(.percpu .percpu.*))\n" " . = ALIGN(4096);\n" " __percpu_end = .;\n" " } :data\n" " .bss : { *(.bss .bss.*) *(COMMON) } :data\n" " /DISCARD/ : { *(.eh_frame*) *(.note .note.*) }\n" "}\n"; FILE *f = fopen(lds_path, "w"); if (!f) return; fprintf(f, "%s", script); fclose(f); print_color(COLOR_GREEN, "x86_64.lds created"); } typedef struct { char src[512]; char elf[512]; char name[256]; } app_entry_t; #define MAX_APPS 64 static app_entry_t g_apps[MAX_APPS]; static int g_naps = 0; static int scan_apps(void) { g_naps = 0; DIR *d = opendir(APPS_DIR); if (!d) { print_color(COLOR_RED, "[apps] Cannot open directory '%s'", APPS_DIR); return 0; } struct dirent *de; while ((de = readdir(d)) != NULL && g_naps < MAX_APPS) { const char *nm = de->d_name; size_t nlen = strlen(nm); if (nlen < 3) continue; if (nm[0] == '.') continue; if (strcmp(nm + nlen - 2, ".c") != 0) continue; app_entry_t *e = &g_apps[g_naps]; snprintf(e->src, sizeof(e->src), "%s/%s", APPS_DIR, nm); snprintf(e->elf, sizeof(e->elf), "%s/%.*s.elf", APPS_DIR, (int)(nlen-2), nm); snprintf(e->name, sizeof(e->name), "%.*s", (int)(nlen-2), nm); g_naps++; } closedir(d); for (int i = 1; i < g_naps; i++) { app_entry_t tmp = g_apps[i]; int j = i-1; while (j >= 0 && strcmp(g_apps[j].name, tmp.name) > 0) { g_apps[j+1] = g_apps[j]; j--; } g_apps[j+1] = tmp; } return g_naps; } static bool build_one_app(const app_entry_t *e) { if (file_exists(e->elf) && get_mtime(e->src) <= get_mtime(e->elf)) { print_color(COLOR_GREEN, "[ELF] %s is up to date", e->elf); return true; } print_color(COLOR_CYAN, "[ELF] Compiling %s -> %s", e->src, e->elf); int ret = cmd_run(false, "gcc -ffreestanding -nostdlib -static -fno-stack-protector" " -fno-pie -fno-pic" " -mno-sse -mno-sse2 -mno-mmx -mno-avx -mno-avx2" " -mno-red-zone" " -O0 -g" " -nostdinc -isystem " SYSROOT_INC " -Wl,-Ttext-segment=0x401000" " -o %s %s " SYSROOT_LIB "/crt0.o " SYSROOT_LIB "/libcervus.a", e->elf, e->src); if (ret != 0) { print_color(COLOR_RED, "[ELF] Failed to compile %s", e->src); return false; } print_color(COLOR_GREEN, "[ELF] %s built successfully", e->elf); return true; } bool build_all_apps(void) { scan_apps(); if (g_naps == 0) { print_color(COLOR_YELLOW, "[apps] No .c files found in '%s'", APPS_DIR); return true; } print_color(COLOR_CYAN, "[apps] Found %d app(s) in '%s'", g_naps, APPS_DIR); for (int i = 0; i < g_naps; i++) { if (!build_one_app(&g_apps[i])) return false; } return true; } void clean_apps_elfs(void) { DIR *d = opendir(APPS_DIR); if (!d) return; struct dirent *de; while ((de = readdir(d)) != NULL) { const char *nm = de->d_name; size_t nlen = strlen(nm); if (nlen < 5) continue; if (strcmp(nm + nlen - 4, ".elf") != 0) continue; char path[512]; snprintf(path, sizeof(path), "%s/%s", APPS_DIR, nm); remove(path); print_color(COLOR_YELLOW, "[clean] removed %s", path); } closedir(d); } static app_entry_t g_bin_apps[MAX_APPS]; static int g_nbin = 0; static int scan_bin_apps(void) { g_nbin = 0; DIR *d = opendir(BIN_APPS_DIR); if (!d) { print_color(COLOR_YELLOW, "[bin] No '%s' directory found, skipping", BIN_APPS_DIR); return 0; } struct dirent *de; while ((de = readdir(d)) != NULL && g_nbin < MAX_APPS) { const char *nm = de->d_name; size_t nlen = strlen(nm); if (nlen < 3) continue; if (nm[0] == '.') continue; if (strcmp(nm + nlen - 2, ".c") != 0) continue; app_entry_t *e = &g_bin_apps[g_nbin]; snprintf(e->src, sizeof(e->src), "%s/%s", BIN_APPS_DIR, nm); snprintf(e->elf, sizeof(e->elf), "%s/%.*s.elf", BIN_APPS_DIR, (int)(nlen-2), nm); snprintf(e->name, sizeof(e->name), "%.*s", (int)(nlen-2), nm); g_nbin++; } closedir(d); for (int i = 1; i < g_nbin; i++) { app_entry_t tmp = g_bin_apps[i]; int j = i-1; while (j >= 0 && strcmp(g_bin_apps[j].name, tmp.name) > 0) { g_bin_apps[j+1] = g_bin_apps[j]; j--; } g_bin_apps[j+1] = tmp; } return g_nbin; } static bool build_one_bin_app(const app_entry_t *e) { if (file_exists(e->elf) && get_mtime(e->src) <= get_mtime(e->elf)) { print_color(COLOR_GREEN, "[bin] %s is up to date", e->elf); return true; } print_color(COLOR_CYAN, "[bin] Compiling %s -> %s", e->src, e->elf); int ret = cmd_run(false, "gcc -ffreestanding -nostdlib -static -fno-stack-protector" " -fno-pie -fno-pic" " -mno-sse -mno-sse2 -mno-mmx -mno-avx -mno-avx2" " -mno-red-zone" " -O0 -g" " -nostdinc -isystem " SYSROOT_INC " -Wl,-Ttext-segment=0x401000" " -o %s %s " SYSROOT_LIB "/crt0.o " SYSROOT_LIB "/libcervus.a", e->elf, e->src); if (ret != 0) { print_color(COLOR_RED, "[bin] Failed to compile %s", e->src); return false; } print_color(COLOR_GREEN, "[bin] %s built successfully", e->elf); return true; } bool build_all_bin_apps(void) { scan_bin_apps(); if (g_nbin == 0) { print_color(COLOR_YELLOW, "[bin] No .c files found in '%s'", BIN_APPS_DIR); return true; } print_color(COLOR_CYAN, "[bin] Found %d program(s) in '%s'", g_nbin, BIN_APPS_DIR); for (int i = 0; i < g_nbin; i++) { if (!build_one_bin_app(&g_bin_apps[i])) return false; } return true; } void clean_bin_elfs(void) { DIR *d = opendir(BIN_APPS_DIR); if (!d) return; struct dirent *de; while ((de = readdir(d)) != NULL) { const char *nm = de->d_name; size_t nlen = strlen(nm); if (nlen < 5) continue; if (strcmp(nm + nlen - 4, ".elf") != 0) continue; char path[512]; snprintf(path, sizeof(path), "%s/%s", BIN_APPS_DIR, nm); remove(path); print_color(COLOR_YELLOW, "[clean] removed %s", path); } closedir(d); } #define LCV_CFLAGS_INT "-ffreestanding -nostdlib -static -fno-stack-protector " \ "-mno-sse -mno-sse2 -mno-mmx -mno-avx -mno-avx2 " \ "-mno-red-zone -fno-pie -fno-pic " \ "-O0 -g -Wall -Wextra" #define LCV_CFLAGS_FLT "-ffreestanding -nostdlib -static -fno-stack-protector " \ "-mno-red-zone -fno-pie -fno-pic " \ "-O0 -g -Wall -Wextra" typedef struct { const char *src; const char *obj; int is_float; } libcervus_unit_t; static const libcervus_unit_t LCV_UNITS[] = { { "libcervus.c", "libcervus.o", 0 }, { "compat.c", "compat.o", 0 }, { "ctype/ctype.c", "ctype/ctype.o", 0 }, { "string/string.c", "string/string.o", 0 }, { "memory/memory.c", "memory/memory.o", 0 }, { "stdlib/stdlib.c", "stdlib/stdlib.o", 0 }, { "stdlib/strtod.c", "stdlib/strtod.o", 1 }, { "stdio/stdio.c", "stdio/stdio.o", 1 }, { "stdio/scanf.c", "stdio/scanf.o", 1 }, { "time/time.c", "time/time.o", 0 }, { "signal/signal.c", "signal/signal.o", 0 }, { "dirent/dirent.c", "dirent/dirent.o", 0 }, { "math/abs.c", "math/abs.o", 0 }, { "math/fabs.c", "math/fabs.o", 1 }, { "math/isinf.c", "math/isinf.o", 0 }, { "math/isnan.c", "math/isnan.o", 0 }, { "math/pow.c", "math/pow.o", 1 }, { "math/pow10.c", "math/pow10.o", 1 }, { NULL, NULL, 0 } }; static long get_mtime_safe(const char *p) { struct stat st; if (stat(p, &st) != 0) return 0; return (long)st.st_mtime; } static bool need_rebuild(const char *src, const char *obj) { if (!file_exists(obj)) return true; return get_mtime_safe(src) > get_mtime_safe(obj); } bool build_libcervus(void) { if (cmd_run(false, "command -v nasm >/dev/null 2>&1") != 0) { print_color(COLOR_RED, "[libcervus] 'nasm' is required to build crt0.o/setjmp.o. Install: " "apt install nasm | dnf install nasm | pacman -S nasm"); return false; } if (cmd_run(false, "command -v ar >/dev/null 2>&1") != 0) { print_color(COLOR_RED, "[libcervus] 'ar' (binutils) is required"); return false; } print_color(COLOR_CYAN, "[libcervus] Building libcervus.a and crt0.o (native)..."); char incdir[BIGPATH]; char libdir[BIGPATH]; char cwd_buf[PATH_MAX]; if (!getcwd(cwd_buf, sizeof(cwd_buf))) { print_color(COLOR_RED, "[libcervus] getcwd failed"); return false; } path_join2(incdir, sizeof(incdir), cwd_buf, SYSROOT_INC); path_join2(libdir, sizeof(libdir), cwd_buf, SYSROOT_LIB); int rc = chdir(LIBCERVUS_DIR); if (rc != 0) { print_color(COLOR_RED, "[libcervus] cannot chdir to %s", LIBCERVUS_DIR); return false; } bool any_changed = false; int built = 0; int skipped = 0; for (int i = 0; LCV_UNITS[i].src; i++) { const libcervus_unit_t *u = &LCV_UNITS[i]; if (!need_rebuild(u->src, u->obj)) { skipped++; continue; } const char *cflags = u->is_float ? LCV_CFLAGS_FLT : LCV_CFLAGS_INT; if (cmd_run(false, "gcc %s -nostdinc -isystem %s -c -o %s %s", cflags, incdir, u->obj, u->src) != 0) { print_color(COLOR_RED, "[libcervus] failed to compile %s", u->src); (void)!chdir(cwd_buf); return false; } any_changed = true; built++; } if (need_rebuild("setjmp.asm", "setjmp.o")) { if (cmd_run(false, "nasm -f elf64 -o setjmp.o setjmp.asm") != 0) { print_color(COLOR_RED, "[libcervus] nasm failed on setjmp.asm"); (void)!chdir(cwd_buf); return false; } any_changed = true; built++; } else skipped++; if (need_rebuild("crt0.asm", "crt0.o")) { if (cmd_run(false, "nasm -f elf64 -o crt0.o crt0.asm") != 0) { print_color(COLOR_RED, "[libcervus] nasm failed on crt0.asm"); (void)!chdir(cwd_buf); return false; } any_changed = true; built++; } else skipped++; bool need_ar = any_changed || !file_exists("libcervus.a"); if (need_ar) { char ar_cmd[8192]; size_t pos = 0; pos += snprintf(ar_cmd + pos, sizeof(ar_cmd) - pos, "ar rcs libcervus.a"); for (int i = 0; LCV_UNITS[i].src; i++) { pos += snprintf(ar_cmd + pos, sizeof(ar_cmd) - pos, " %s", LCV_UNITS[i].obj); } pos += snprintf(ar_cmd + pos, sizeof(ar_cmd) - pos, " setjmp.o"); if (cmd_run(false, "%s", ar_cmd) != 0) { print_color(COLOR_RED, "[libcervus] ar failed"); (void)!chdir(cwd_buf); return false; } } if (cmd_run(false, "mkdir -p %s", libdir) != 0 || cmd_run(false, "cp libcervus.a %s/", libdir) != 0 || cmd_run(false, "cp crt0.o %s/", libdir) != 0) { print_color(COLOR_RED, "[libcervus] install copy failed"); (void)!chdir(cwd_buf); return false; } (void)!chdir(cwd_buf); print_color(COLOR_GREEN, "[libcervus] OK (compiled %d, skipped %d)", built, skipped); return true; } #define TCC_VERSION_STR "0.9.27" #define TCC_TAR_FNAME "tcc-0.9.27.tar.bz2" #define TCC_SRC_DIR "tcc-0.9.27" #define TCC_DOWNLOAD_URL "https://download.savannah.gnu.org/releases/tinycc/tcc-0.9.27.tar.bz2" #define TCC_DIR "usr/tcc" typedef struct { char *data; size_t len; size_t cap; } dstr_t; static void dstr_init(dstr_t *s) { s->data = NULL; s->len = 0; s->cap = 0; } static void dstr_free(dstr_t *s) { free(s->data); s->data = NULL; s->len = s->cap = 0; } static bool dstr_reserve(dstr_t *s, size_t need) { if (s->cap >= need) return true; size_t nc = s->cap ? s->cap : 256; while (nc < need) nc *= 2; char *p = realloc(s->data, nc); if (!p) return false; s->data = p; s->cap = nc; return true; } static bool read_file_dstr(const char *path, dstr_t *out) { FILE *f = fopen(path, "rb"); if (!f) return false; fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET); if (sz < 0) { fclose(f); return false; } if (!dstr_reserve(out, (size_t)sz + 1)) { fclose(f); return false; } if (sz > 0 && fread(out->data, 1, (size_t)sz, f) != (size_t)sz) { fclose(f); return false; } out->len = (size_t)sz; out->data[out->len] = '\0'; fclose(f); return true; } static bool write_file_dstr(const char *path, const dstr_t *s) { FILE *f = fopen(path, "wb"); if (!f) return false; if (s->len > 0 && fwrite(s->data, 1, s->len, f) != s->len) { fclose(f); return false; } fclose(f); return true; } static bool dstr_replace_once(dstr_t *s, const char *old_s, const char *new_s) { size_t old_n = strlen(old_s); size_t new_n = strlen(new_s); if (old_n == 0 || s->len < old_n) return false; char *p = memmem(s->data, s->len, old_s, old_n); if (!p) return false; size_t off = (size_t)(p - s->data); if (new_n > old_n) { if (!dstr_reserve(s, s->len + (new_n - old_n) + 1)) return false; p = s->data + off; } memmove(p + new_n, p + old_n, s->len - off - old_n + 1 ); memcpy(p, new_s, new_n); s->len += (new_n - old_n); return true; } static bool dstr_guard_line_with(dstr_t *s, const char *marker, const char *guard_macro) { char *p = memmem(s->data, s->len, marker, strlen(marker)); if (!p) return false; char *line_start = p; while (line_start > s->data && line_start[-1] != '\n') line_start--; char *prev_line_start = line_start; if (prev_line_start > s->data) prev_line_start--; while (prev_line_start > s->data && prev_line_start[-1] != '\n') prev_line_start--; char needle[160]; snprintf(needle, sizeof(needle), "#ifndef %s", guard_macro); size_t pll = (size_t)(line_start - prev_line_start); if (pll >= strlen(needle) && memcmp(prev_line_start, needle, strlen(needle)) == 0) { return false; } char *line_end = p; while ((size_t)(line_end - s->data) < s->len && *line_end != '\n') line_end++; if ((size_t)(line_end - s->data) < s->len && *line_end == '\n') line_end++; char prefix[160], suffix[16]; snprintf(prefix, sizeof(prefix), "#ifndef %s\n", guard_macro); snprintf(suffix, sizeof(suffix), "#endif\n"); size_t ins_pre = strlen(prefix), ins_suf = strlen(suffix); size_t off_start = (size_t)(line_start - s->data); size_t off_end = (size_t)(line_end - s->data); if (!dstr_reserve(s, s->len + ins_pre + ins_suf + 1)) return false; line_start = s->data + off_start; line_end = s->data + off_end; memmove(line_end + ins_pre + ins_suf, line_end, s->len - off_end + 1); memcpy(line_end + ins_pre, suffix, ins_suf); memmove(line_start + ins_pre, line_start, off_end - off_start); memcpy(line_start, prefix, ins_pre); s->len += ins_pre + ins_suf; return true; } static int dstr_guard_func_impls(dstr_t *s, const char *name, const char *guard_macro) { int count = 0; size_t cursor = 0; size_t name_n = strlen(name); while (cursor + name_n < s->len) { char *p = memmem(s->data + cursor, s->len - cursor, name, name_n); if (!p) break; size_t pos = (size_t)(p - s->data); size_t i = pos + name_n; while (i < s->len && (s->data[i] == ' ' || s->data[i] == '\t')) i++; if (i >= s->len || s->data[i] != '(') { cursor = pos + 1; continue; } size_t j = i; while (j < s->len && s->data[j] != '{' && s->data[j] != ';') j++; if (j >= s->len) break; if (s->data[j] == ';') { cursor = pos + 1; continue; } int depth = 0; size_t end = j; for (; end < s->len; end++) { if (s->data[end] == '{') depth++; else if (s->data[end] == '}') { depth--; if (depth == 0) { end++; break; } } } if (depth != 0) break; if (end < s->len && s->data[end] == '\n') end++; size_t line_start = pos; while (line_start > 0 && s->data[line_start - 1] != '\n') line_start--; size_t look = line_start > 200 ? line_start - 200 : 0; if (memmem(s->data + look, line_start - look, guard_macro, strlen(guard_macro))) { cursor = end; continue; } char prefix[160], suffix[16]; snprintf(prefix, sizeof(prefix), "#ifndef %s\n", guard_macro); snprintf(suffix, sizeof(suffix), "#endif\n"); size_t pre_n = strlen(prefix), suf_n = strlen(suffix); if (!dstr_reserve(s, s->len + pre_n + suf_n + 1)) return count; memmove(s->data + end + pre_n + suf_n, s->data + end, s->len - end + 1); memcpy(s->data + end + pre_n, suffix, suf_n); memmove(s->data + line_start + pre_n, s->data + line_start, end - line_start); memcpy(s->data + line_start, prefix, pre_n); s->len += pre_n + suf_n; count++; cursor = end + pre_n + suf_n; } return count; } static bool patch_file(const char *src_dir, const char *rel, int (*editor)(dstr_t *)) { char path[SHORTPATH]; path_join2(path, sizeof(path), src_dir, rel); dstr_t s; dstr_init(&s); if (!read_file_dstr(path, &s)) { print_color(COLOR_RED, "[tcc-patch] cannot read %s", path); return false; } int changed = editor(&s); if (changed > 0) { if (!write_file_dstr(path, &s)) { print_color(COLOR_RED, "[tcc-patch] cannot write %s", path); dstr_free(&s); return false; } print_color(COLOR_GREEN, "[tcc-patch] %s: %d edit(s)", rel, changed); } else { print_color(COLOR_YELLOW, "[tcc-patch] %s: no changes (already patched?)", rel); } dstr_free(&s); return true; } static int edit_tcc_h(dstr_t *s) { int changed = 0; if (dstr_guard_line_with(s, "#include ", "TCC_NO_DLOPEN")) changed++; { const char *needle = "#include "; char *p = memmem(s->data, s->len, needle, strlen(needle)); if (p && !memmem(s->data, s->len, "#include ", strlen("#include "))) { const char *ins = "#include \n"; size_t off = (size_t)(p - s->data); size_t ins_n = strlen(ins); if (!dstr_reserve(s, s->len + ins_n + 1)) return changed; memmove(s->data + off + ins_n, s->data + off, s->len - off + 1); memcpy(s->data + off, ins, ins_n); s->len += ins_n; changed++; } } if (dstr_guard_line_with(s, "enable bound checking code", "CONFIG_TCC_BCHECK")) changed++; return changed; } static int edit_libtcc_c(dstr_t *s) { int changed = 0; if (dstr_replace_once(s, "return dlopen(filename, RTLD_GLOBAL | RTLD_LAZY);\n", "(void)filename;\n return NULL;\n")) changed++; if (dstr_replace_once(s, "return dlopen(filename, RTLD_LOCAL | RTLD_LAZY);\n", "(void)filename;\n return NULL;\n")) changed++; if (dstr_replace_once(s, "return dlsym(handle, sym);\n", "(void)handle; (void)sym;\n return NULL;\n")) changed++; if (dstr_replace_once(s, "dlclose(handle);\n", "(void)handle;\n")) changed++; if (!memmem(s->data, s->len, "Cervus: no ld.so", 16)) { if (dstr_replace_once(s, " s->alacarte_link = 1;", " s->alacarte_link = 1;\n s->static_link = 1; /* Cervus: no ld.so */")) changed++; } if (dstr_replace_once(s, " if (output_type != TCC_OUTPUT_DLL)\n" " tcc_add_crt(s, \"crt1.o\");\n" " tcc_add_crt(s, \"crti.o\");", " if (output_type != TCC_OUTPUT_DLL)\n" " tcc_add_crt(s, \"crt0.o\"); /* Cervus: only crt0 */")) changed++; return changed; } static int edit_tccrun_c(dstr_t *s) { int changed = 0; if (dstr_guard_line_with(s, "", "TCC_NO_BACKTRACE")) changed++; if (!memmem(s->data, s->len, "typedef void *ucontext_t;", strlen("typedef void *ucontext_t;"))) { size_t scan = s->len < 4096 ? s->len : 4096; size_t last_inc_end = 0; for (size_t i = 0; i < scan; ) { if (s->data[i] == '#' && i + 8 <= s->len && memcmp(s->data + i, "#include", 8) == 0) { size_t e = i; while (e < s->len && s->data[e] != '\n') e++; if (e < s->len) e++; last_inc_end = e; i = e; continue; } i++; } if (last_inc_end > 0) { const char *stub = "\n#ifdef TCC_NO_BACKTRACE\n" "typedef void *ucontext_t;\n" "#endif\n"; size_t stub_n = strlen(stub); if (dstr_reserve(s, s->len + stub_n + 1)) { memmove(s->data + last_inc_end + stub_n, s->data + last_inc_end, s->len - last_inc_end + 1); memcpy(s->data + last_inc_end, stub, stub_n); s->len += stub_n; changed++; } } } if (dstr_guard_line_with(s, "set_exception_handler();", "TCC_NO_BACKTRACE")) changed++; const char *names[] = { "rt_error", "sig_error", "set_exception_handler", "rt_get_caller_pc", NULL }; for (int i = 0; names[i]; i++) { int n = dstr_guard_func_impls(s, names[i], "TCC_NO_BACKTRACE"); if (n > 0) changed++; } return changed; } static int edit_tccelf_c(dstr_t *s) { int changed = 0; if (!memmem(s->data, s->len, "Cervus: -lcervus", 16)) { if (dstr_replace_once(s, " if (!s1->nostdlib) {\n tcc_add_library_err(s1, \"c\");", " if (!s1->nostdlib) {\n tcc_add_library_err(s1, \"cervus\"); /* Cervus: -lcervus */")) changed++; } if (dstr_replace_once(s, " if (s1->output_type != TCC_OUTPUT_MEMORY)\n" " tcc_add_crt(s1, \"crtn.o\");\n" " }\n", " /* Cervus: no crtn.o */\n }\n")) changed++; return changed; } static bool write_tcc_config_h(const char *src_dir) { char path[SHORTPATH]; path_join2(path, sizeof(path), src_dir, "config.h"); FILE *f = fopen(path, "w"); if (!f) return false; fputs( "#ifndef _CONFIG_H\n" "#define _CONFIG_H\n\n" "#define TCC_VERSION \"" TCC_VERSION_STR "\"\n\n" "#define CONFIG_TCC_SYSROOT \"\"\n" "#define CONFIG_TCC_LIBPATHS \"/usr/lib\"\n" "#define CONFIG_TCC_CRTPREFIX \"/usr/lib\"\n" "#define CONFIG_TCC_ELFINTERP \"\"\n" "#define CONFIG_TCCDIR \"/usr/lib/tcc\"\n\n" "#define HOST_OS \"Cervus\"\n" "#define HOST_ARCH \"x86_64\"\n\n" "#define TCC_TARGET_X86_64 1\n\n" "#define CONFIG_TCC_PREDEFS 1\n" "#define CONFIG_TCC_STATIC 1\n\n" "#define TCC_NO_DLOPEN 1\n" "#define TCC_NO_BACKTRACE 1\n\n" "#define CONFIG_TCC_BCHECK 0\n\n" "#undef CONFIG_WIN32\n" "#undef CONFIG_WIN64\n" "#undef TCC_TARGET_PE\n\n" "#endif /* _CONFIG_H */\n", f); fclose(f); return true; } static bool tcc_download(void) { char tar_path[SHORTPATH]; path_join2(tar_path, sizeof(tar_path), TCC_DIR, TCC_TAR_FNAME); if (file_exists(tar_path)) return true; print_color(COLOR_CYAN, "[tcc] Downloading %s...", TCC_TAR_FNAME); if (cmd_run(false, "command -v wget >/dev/null 2>&1") == 0) { if (cmd_run(true, "wget -q -O %s %s", tar_path, TCC_DOWNLOAD_URL) == 0 && file_exists(tar_path)) return true; } if (cmd_run(false, "command -v curl >/dev/null 2>&1") == 0) { if (cmd_run(true, "curl -fL -o %s %s", tar_path, TCC_DOWNLOAD_URL) == 0 && file_exists(tar_path)) return true; } print_color(COLOR_RED, "[tcc] cannot download (need wget or curl)"); return false; } static bool tcc_extract_and_patch(void) { char src_dir_full[SHORTPATH]; path_join2(src_dir_full, sizeof(src_dir_full), TCC_DIR, TCC_SRC_DIR); if (!file_exists(src_dir_full)) { print_color(COLOR_CYAN, "[tcc] Extracting %s...", TCC_TAR_FNAME); if (cmd_run(true, "tar -xjf %s/%s -C %s", TCC_DIR, TCC_TAR_FNAME, TCC_DIR) != 0) { print_color(COLOR_RED, "[tcc] tar -xjf failed"); return false; } } if (!write_tcc_config_h(src_dir_full)) { print_color(COLOR_RED, "[tcc] cannot write config.h"); return false; } print_color(COLOR_CYAN, "[tcc] Applying Cervus patches (idempotent)..."); if (!patch_file(src_dir_full, "tcc.h", edit_tcc_h)) return false; if (!patch_file(src_dir_full, "libtcc.c", edit_libtcc_c)) return false; if (!patch_file(src_dir_full, "tccrun.c", edit_tccrun_c)) return false; if (!patch_file(src_dir_full, "tccelf.c", edit_tccelf_c)) return false; return true; } static bool tcc_build_and_install(void) { char src_dir_full[SHORTPATH]; path_join2(src_dir_full, sizeof(src_dir_full), TCC_DIR, TCC_SRC_DIR); char cwd_buf[PATH_MAX]; if (!getcwd(cwd_buf, sizeof(cwd_buf))) { print_color(COLOR_RED, "[tcc] getcwd failed"); return false; } char incdir[BIGPATH], libdir[BIGPATH]; path_join2(incdir, sizeof(incdir), cwd_buf, SYSROOT_INC); path_join2(libdir, sizeof(libdir), cwd_buf, SYSROOT_LIB); char tcc_elf[SHORTPATH], libtcc1_a[SHORTPATH]; path_join2(tcc_elf, sizeof(tcc_elf), TCC_DIR, "tcc.elf"); path_join2(libtcc1_a, sizeof(libtcc1_a), TCC_DIR, "libtcc1.a"); char one_src[SHORTPATH]; path_join2(one_src, sizeof(one_src), src_dir_full, "tcc.c"); char one_hdr[SHORTPATH], one_libtcc[SHORTPATH]; path_join2(one_hdr, sizeof(one_hdr), src_dir_full, "tcc.h"); path_join2(one_libtcc, sizeof(one_libtcc), src_dir_full, "libtcc.c"); long elf_t = get_mtime_safe(tcc_elf); bool need_tcc = !file_exists(tcc_elf) || get_mtime_safe(one_src) > elf_t || get_mtime_safe(one_hdr) > elf_t || get_mtime_safe(one_libtcc) > elf_t; if (need_tcc) { print_color(COLOR_CYAN, "[tcc] Building tcc.elf for Cervus (x86_64)..."); char crt0[BIGPATH]; path_join2(crt0, sizeof(crt0), libdir, "crt0.o"); if (!file_exists(crt0)) { print_color(COLOR_RED, "[tcc] %s missing (libcervus build failed?)", crt0); return false; } int rc = cmd_run(true, "gcc -ffreestanding -nostdlib -static -fno-stack-protector " "-mno-red-zone -fno-pie -fno-pic -O2 -D__CERVUS__ " "-nostdinc -isystem %s " "-DTCC_TARGET_X86_64 -DONE_SOURCE=1 " "-DCONFIG_TCC_PREDEFS=1 -DTCC_NO_DLOPEN=1 -DTCC_NO_BACKTRACE=1 " "-DCONFIG_TCC_STATIC=1 " "-I%s " "-o %s %s/tcc.c %s " "-nostdlib -static -L%s -lcervus", incdir, src_dir_full, tcc_elf, src_dir_full, crt0, libdir); if (rc != 0) { print_color(COLOR_RED, "[tcc] tcc.elf build failed"); return false; } } else { print_color(COLOR_GREEN, "[tcc] tcc.elf up to date"); } char l1_o[SHORTPATH], al_o[SHORTPATH], va_o[SHORTPATH]; path_join2(l1_o, sizeof(l1_o), TCC_DIR, "libtcc1.o"); path_join2(al_o, sizeof(al_o), TCC_DIR, "alloca86_64.o"); path_join2(va_o, sizeof(va_o), TCC_DIR, "va_list.o"); bool need_libtcc1 = !file_exists(libtcc1_a); if (need_libtcc1) { print_color(COLOR_CYAN, "[tcc] Building libtcc1.a..."); const char *l1_flags = "-ffreestanding -nostdlib -fno-stack-protector " "-mno-red-zone -fno-pie -fno-pic -O2 -D__CERVUS__ " "-DTCC_TARGET_X86_64"; char src_path[SHORTPATH]; char ar_cmd[2048]; size_t arpos = (size_t)snprintf(ar_cmd, sizeof(ar_cmd), "ar rcs %s", libtcc1_a); path_join2(src_path, sizeof(src_path), src_dir_full, "lib/libtcc1.c"); if (!file_exists(src_path)) { print_color(COLOR_RED, "[tcc] missing %s", src_path); return false; } if (cmd_run(true, "gcc %s -c %s -o %s", l1_flags, src_path, l1_o) != 0) { print_color(COLOR_RED, "[tcc] libtcc1.c compile failed"); return false; } arpos += (size_t)snprintf(ar_cmd + arpos, sizeof(ar_cmd) - arpos, " %s", l1_o); path_join2(src_path, sizeof(src_path), src_dir_full, "lib/alloca86_64.S"); if (file_exists(src_path)) { if (cmd_run(true, "gcc %s -c %s -o %s", l1_flags, src_path, al_o) != 0) { print_color(COLOR_RED, "[tcc] alloca86_64.S compile failed"); return false; } arpos += (size_t)snprintf(ar_cmd + arpos, sizeof(ar_cmd) - arpos, " %s", al_o); } path_join2(src_path, sizeof(src_path), src_dir_full, "lib/va_list.c"); if (file_exists(src_path)) { if (cmd_run(true, "gcc %s -c %s -o %s", l1_flags, src_path, va_o) != 0) { print_color(COLOR_RED, "[tcc] va_list.c compile failed"); return false; } arpos += (size_t)snprintf(ar_cmd + arpos, sizeof(ar_cmd) - arpos, " %s", va_o); } if (cmd_run(true, "%s", ar_cmd) != 0) { print_color(COLOR_RED, "[tcc] ar failed"); return false; } } else { print_color(COLOR_GREEN, "[tcc] libtcc1.a up to date"); } print_color(COLOR_CYAN, "[tcc] Installing to sysroot..."); if (cmd_run(false, "mkdir -p %s/usr/bin %s/usr/lib/tcc/include", SYSROOT_DIR, SYSROOT_DIR) != 0) { print_color(COLOR_RED, "[tcc] mkdir failed"); return false; } if (cmd_run(false, "cp %s %s/usr/bin/tcc", tcc_elf, SYSROOT_DIR) != 0 || cmd_run(false, "cp %s %s/usr/lib/tcc/libtcc1.a", libtcc1_a, SYSROOT_DIR) != 0 || cmd_run(false, "cp %s/include/*.h %s/usr/lib/tcc/include/", src_dir_full, SYSROOT_DIR) != 0) { print_color(COLOR_RED, "[tcc] install copy failed"); return false; } print_color(COLOR_GREEN, "[tcc] Installed to %s/usr/bin/tcc", SYSROOT_DIR); return true; } bool build_tcc(void) { if (!tcc_download()) return false; if (!tcc_extract_and_patch()) return false; if (!tcc_build_and_install()) return false; return true; } void clean_tcc_build(bool deep) { char tcc_elf[PATH_MAX], libtcc1_a[PATH_MAX]; path_join2(tcc_elf, sizeof(tcc_elf), TCC_DIR, "tcc.elf"); path_join2(libtcc1_a, sizeof(libtcc1_a), TCC_DIR, "libtcc1.a"); cmd_run(false, "rm -f %s/libtcc1.o %s/alloca86_64.o %s/va_list.o", TCC_DIR, TCC_DIR, TCC_DIR); if (file_exists(tcc_elf)) { remove(tcc_elf); print_color(COLOR_YELLOW, "[clean] removed %s", tcc_elf); } if (file_exists(libtcc1_a)) { remove(libtcc1_a); print_color(COLOR_YELLOW, "[clean] removed %s", libtcc1_a); } cmd_run(false, "rm -f %s/usr/bin/tcc", SYSROOT_DIR); cmd_run(false, "rm -rf %s/usr/lib/tcc", SYSROOT_DIR); print_color(COLOR_YELLOW, "[clean] removed %s/usr/bin/tcc and %s/usr/lib/tcc/", SYSROOT_DIR, SYSROOT_DIR); if (deep) { char src_dir[SHORTPATH], tar_path[SHORTPATH]; path_join2(src_dir, sizeof(src_dir), TCC_DIR, TCC_SRC_DIR); path_join2(tar_path, sizeof(tar_path), TCC_DIR, TCC_TAR_FNAME); if (file_exists(src_dir)) { rm_rf(src_dir); print_color(COLOR_YELLOW, "[clean] removed %s", src_dir); } if (file_exists(tar_path)) { remove(tar_path); print_color(COLOR_YELLOW, "[clean] removed %s", tar_path); } } } void clean_libcervus_build(bool deep) { char buf[PATH_MAX]; for (int i = 0; LCV_UNITS[i].src; i++) { path_join2(buf, sizeof(buf), LIBCERVUS_DIR, LCV_UNITS[i].obj); if (file_exists(buf)) remove(buf); } path_join2(buf, sizeof(buf), LIBCERVUS_DIR, "setjmp.o"); if (file_exists(buf)) remove(buf); path_join2(buf, sizeof(buf), LIBCERVUS_DIR, "crt0.o"); if (file_exists(buf)) remove(buf); path_join2(buf, sizeof(buf), LIBCERVUS_DIR, "libcervus.a"); if (file_exists(buf)) remove(buf); cmd_run(false, "rm -f %s/libcervus.a %s/crt0.o", SYSROOT_LIB, SYSROOT_LIB); (void)deep; } bool build_installer(void) { DIR *d = opendir(INSTALLER_DIR); if (!d) { print_color(COLOR_YELLOW, "[installer] directory '%s' not found, skipping", INSTALLER_DIR); return true; } struct dirent *de; int built = 0; while ((de = readdir(d)) != NULL) { const char *nm = de->d_name; size_t nlen = strlen(nm); if (nlen < 3 || nm[0] == '.') continue; if (strcmp(nm + nlen - 2, ".c") != 0) continue; app_entry_t e; snprintf(e.src, sizeof(e.src), "%s/%s", INSTALLER_DIR, nm); snprintf(e.elf, sizeof(e.elf), "%s/%.*s.elf", INSTALLER_DIR, (int)(nlen-2), nm); snprintf(e.name, sizeof(e.name), "%.*s", (int)(nlen-2), nm); if (!build_one_app(&e)) { closedir(d); return false; } built++; } closedir(d); if (built == 0) print_color(COLOR_YELLOW, "[installer] no .c sources in '%s'", INSTALLER_DIR); return true; } bool build_initramfs(void) { if (ARG_NO_INITRAMFS) { print_color(COLOR_YELLOW, "[initramfs] skipped (--no-initramfs)"); return true; } if (!build_limine()) { print_color(COLOR_YELLOW, "[initramfs] warning: limine not available yet, boot files will be incomplete"); } bool tar_exists = file_exists(INITRAMFS_TAR); bool any_newer = false; if (!tar_exists) { any_newer = true; } else { scan_apps(); for (int i = 0; i < g_naps; i++) { if (file_exists(g_apps[i].elf) && get_mtime(g_apps[i].elf) > get_mtime(INITRAMFS_TAR)) { any_newer = true; break; } } if (!any_newer) { scan_bin_apps(); for (int i = 0; i < g_nbin; i++) { if (file_exists(g_bin_apps[i].elf) && get_mtime(g_bin_apps[i].elf) > get_mtime(INITRAMFS_TAR)) { any_newer = true; break; } } } if (!any_newer && file_exists(SHELL_ELF) && get_mtime(SHELL_ELF) > get_mtime(INITRAMFS_TAR)) { any_newer = true; } if (!any_newer && file_exists(INSTALLER_ELF) && get_mtime(INSTALLER_ELF) > get_mtime(INITRAMFS_TAR)) { any_newer = true; } if (!any_newer && file_exists(SYSROOT_LIB "/libcervus.a") && get_mtime(SYSROOT_LIB "/libcervus.a") > get_mtime(INITRAMFS_TAR)) { any_newer = true; } } if (tar_exists && !any_newer) { print_color(COLOR_GREEN, "[initramfs] %s is up to date", INITRAMFS_TAR); return true; } print_color(COLOR_CYAN, "[initramfs] Building rootfs..."); const char *dirs[] = { INITRAMFS_ROOTFS "/bin", INITRAMFS_ROOTFS "/dev", INITRAMFS_ROOTFS "/etc", INITRAMFS_ROOTFS "/home", INITRAMFS_ROOTFS "/tmp", INITRAMFS_ROOTFS "/proc", NULL }; for (int i = 0; dirs[i]; i++) ensure_dir(dirs[i]); FILE *passwd = fopen(INITRAMFS_ROOTFS "/etc/passwd", "w"); if (passwd) { fprintf(passwd, "root:x:0:0:root:/root:/bin/sh\n"); fclose(passwd); } FILE *hostname = fopen(INITRAMFS_ROOTFS "/etc/hostname", "w"); if (hostname) { fprintf(hostname, "cervus"); fclose(hostname); } FILE *motd = fopen(INITRAMFS_ROOTFS "/etc/motd", "w"); if (motd) { fprintf(motd, "\n" " $$$$$$\\ \n" " $$ __$$\\ \n" " $$ / \\__| $$$$$$\\ $$$$$$\\ $$\\ $$\\ $$\\ $$\\ $$$$$$$\\ \n" " $$ | $$ __$$\\ $$ __$$\\\\$$\\ $$ |$$ | $$ |$$ _____|\n" " $$ | $$$$$$$$ |$$ | \\__|\\$$\\$$ / $$ | $$ |\\$$$$$$\\ \n" " $$ | $$\\ $$ ____|$$ | \\$$$ / $$ | $$ | \\____$$\\ \n" " \\$$$$$$ |\\$$$$$$$\\ $$ | \\$ / \\$$$$$$ |$$$$$$$ |\n" " \\______/ \\______||\\__| \\_/ \\______/ \\_______/\n" "\n" " Cervus OS " VERSION " (Alpha release)\n" "\n" " Type 'help' to see available commands.\n" "\n" " Your home directory is /mnt/home (persistent ext2 disk).\n" " Files created in /mnt/ will survive reboots.\n" " Files outside /mnt/ are in RAM and will be lost on reboot.\n" "\n"); fclose(motd); } if (file_exists(SHELL_ELF)) { if (cmd_run(false, "cp %s %s/bin/init", SHELL_ELF, INITRAMFS_ROOTFS) != 0) { print_color(COLOR_RED, "[initramfs] Failed to copy shell.elf -> bin/init"); return false; } cmd_run(false, "cp %s %s/bin/shell", SHELL_ELF, INITRAMFS_ROOTFS); print_color(COLOR_GREEN, "[initramfs] shell.elf -> /bin/init + /bin/shell"); } else { print_color(COLOR_RED, "[initramfs] shell.elf not found - boot will drop to nothing!"); FILE *stub = fopen(INITRAMFS_ROOTFS "/bin/.keep", "w"); if (stub) fclose(stub); } scan_bin_apps(); print_color(COLOR_CYAN, "[initramfs] Copying %d bin program(s) -> rootfs/bin/", g_nbin); for (int i = 0; i < g_nbin; i++) { if (!file_exists(g_bin_apps[i].elf)) { print_color(COLOR_YELLOW, "[initramfs] %s not built, skipping", g_bin_apps[i].src); continue; } char dst[512]; snprintf(dst, sizeof(dst), "%s/bin/%s", INITRAMFS_ROOTFS, g_bin_apps[i].name); if (cmd_run(false, "cp %s %s", g_bin_apps[i].elf, dst) != 0) { print_color(COLOR_RED, "[initramfs] Failed to copy %s", g_bin_apps[i].elf); } else { print_color(COLOR_GREEN, "[initramfs] %s -> rootfs/bin/%s", g_bin_apps[i].elf, g_bin_apps[i].name); } } ensure_dir(INITRAMFS_ROOTFS "/apps"); scan_apps(); for (int i = 0; i < g_naps; i++) { if (strcmp(g_apps[i].name, "shell") == 0) continue; if (!file_exists(g_apps[i].elf)) continue; char dst[512]; snprintf(dst, sizeof(dst), "%s/apps/%s", INITRAMFS_ROOTFS, g_apps[i].name); if (cmd_run(false, "cp %s %s", g_apps[i].elf, dst) != 0) { print_color(COLOR_RED, "[initramfs] Failed to copy %s", g_apps[i].elf); } else { print_color(COLOR_GREEN, "[initramfs] %s -> rootfs/apps/%s", g_apps[i].elf, g_apps[i].name); } } if (file_exists(INSTALLER_ELF)) { char dst[512]; snprintf(dst, sizeof(dst), "%s/bin/install-on-disk", INITRAMFS_ROOTFS); if (cmd_run(false, "cp %s %s", INSTALLER_ELF, dst) == 0) print_color(COLOR_GREEN, "[initramfs] %s -> rootfs/bin/install-on-disk", INSTALLER_ELF); else print_color(COLOR_RED, "[initramfs] failed to copy installer"); } if (file_exists(SYSROOT_DIR "/usr")) { print_color(COLOR_CYAN, "[initramfs] Copying sysroot -> rootfs/usr..."); ensure_dir(INITRAMFS_ROOTFS "/usr"); if (cmd_run(false, "cp -r " SYSROOT_DIR "/usr/. %s/usr/", INITRAMFS_ROOTFS) != 0) { print_color(COLOR_RED, "[initramfs] Failed to copy sysroot"); return false; } print_color(COLOR_GREEN, "[initramfs] sysroot installed into rootfs/usr/"); } else { print_color(COLOR_YELLOW, "[initramfs] " SYSROOT_DIR "/usr not found - skipping sysroot " "(did libcervus.a build fail?)"); } FILE *readme = fopen(INITRAMFS_ROOTFS "/home/readme.txt", "w"); if (readme) { fprintf(readme, "Cervus OS v0.0.2\n" "================\n" "\n" "This is Cervus - an x86_64 OS written in C.\n" "Bootloader: Limine | Filesystem: ext2\n" "\n" "Built-in shell commands:\n" " help, cd, exit\n" "\n" "Programs in /bin (always available):\n" " ls, cat, echo, pwd, clear, uname, meminfo, cpuinfo\n" "\n" "Programs in /apps (run from /apps dir):\n" " hello, calc, cal, date, ps, uptime, find, stat,\n" " hexdump, kill, wc, yes, sleep, ...\n" "\n" "Persistent storage:\n" " /mnt/home - user home directory (ext2)\n" " /mnt/usr - sysroot for libraries and headers\n" " /mnt/tmp - temporary files (persistent)\n" "\n" "Source: https://github.com/VeoQeo/Cervus\n" ); fclose(readme); print_color(COLOR_GREEN, "[initramfs] created /home/readme.txt"); } FILE *welcome = fopen(INITRAMFS_ROOTFS "/home/welcome.txt", "w"); if (welcome) { fprintf(welcome, "Welcome to Cervus Shell!\n" "\n" "Tips:\n" " - Use arrow keys to move cursor within a command\n" " - Use Up/Down to browse command history (max 20)\n" " - Type 'ls' to list files, 'cat ' to read .txt files\n" " - Binaries are in /bin and /apps\n" ); fclose(welcome); print_color(COLOR_GREEN, "[initramfs] created /home/welcome.txt"); } ensure_dir(INITRAMFS_ROOTFS "/boot"); struct { const char *src; const char *dst; bool required; } boot_items[] = { { "bin/kernel", INITRAMFS_ROOTFS "/boot/kernel", true }, { SHELL_ELF, INITRAMFS_ROOTFS "/boot/shell.elf", true }, { "limine/limine-bios.sys", INITRAMFS_ROOTFS "/boot/limine-bios.sys", false }, { "limine/limine-bios-hdd.bin", INITRAMFS_ROOTFS "/boot/limine-bios-hdd.bin", false }, { "limine/BOOTX64.EFI", INITRAMFS_ROOTFS "/boot/BOOTX64.EFI", false }, { "limine/BOOTIA32.EFI", INITRAMFS_ROOTFS "/boot/BOOTIA32.EFI", false }, { WALLPAPER_SRC, INITRAMFS_ROOTFS "/boot/wallpaper.png", false }, { NULL, NULL, false } }; for (int i = 0; boot_items[i].src; i++) { if (!file_exists(boot_items[i].src)) { if (boot_items[i].required) { print_color(COLOR_RED, "[initramfs] missing required boot file: %s", boot_items[i].src); return false; } print_color(COLOR_YELLOW, "[initramfs] skip (not built yet): %s", boot_items[i].src); continue; } if (cmd_run(false, "cp %s %s", boot_items[i].src, boot_items[i].dst) != 0) { print_color(COLOR_RED, "[initramfs] failed to copy %s", boot_items[i].src); if (boot_items[i].required) return false; } else { print_color(COLOR_GREEN, "[initramfs] %s -> %s", boot_items[i].src, boot_items[i].dst); } } print_color(COLOR_CYAN, "[initramfs] Packing %s...", INITRAMFS_TAR); if (cmd_run(false, "tar --format=ustar -cf %s -C %s .", INITRAMFS_TAR, INITRAMFS_ROOTFS) != 0) { print_color(COLOR_RED, "[initramfs] tar failed"); return false; } print_color(COLOR_GREEN, "[initramfs] Contents of %s:", INITRAMFS_TAR); cmd_run(false, "tar -tvf %s", INITRAMFS_TAR); print_color(COLOR_GREEN, "[initramfs] %s built successfully", INITRAMFS_TAR); return true; } typedef struct { char **paths; int count; int capacity; } FileList; void file_list_add(FileList *list, const char *path) { if (list->count >= list->capacity) { list->capacity = list->capacity == 0 ? 16 : list->capacity * 2; list->paths = realloc(list->paths, list->capacity * sizeof(char *)); } list->paths[list->count++] = strdup(path); } void find_src_files(const char *root_dir, FileList *list) { DIR *dir; struct dirent *entry; if (!(dir = opendir(root_dir))) return; while ((entry = readdir(dir)) != NULL) { if (entry->d_type == DT_DIR) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, ".git") == 0) continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", root_dir, entry->d_name); find_src_files(path, list); } else { const char *ext = strrchr(entry->d_name, '.'); if (ext && (strcmp(ext, ".c") == 0 || strcmp(ext, ".asm") == 0 || strcmp(ext, ".S") == 0 || strcmp(ext, ".psf") == 0)) { char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", root_dir, entry->d_name); file_list_add(list, path); } } } closedir(dir); } bool is_sse_file(const char *filename) { for (int i = 0; SSE_FILES[i] != NULL; i++) if (strstr(filename, SSE_FILES[i]) != NULL) return true; return false; } bool compile_kernel(void) { print_color(COLOR_GREEN, "Compiling kernel..."); ensure_linker_script(); if (!setup_dependencies()) return false; if (!build_libcervus()) return false; if (!build_tcc()) return false; if (!build_all_apps()) return false; if (!build_all_bin_apps()) return false; if (!build_installer()) return false; ensure_dir("bin"); ensure_dir("obj/kernel"); ensure_dir("obj/libc"); const char *base_cflags = "-g -O2 -pipe -Wall -Wextra -std=gnu11 -nostdinc -ffreestanding " "-fno-stack-protector -fno-stack-check -fno-lto -fno-PIC " "-ffunction-sections -fdata-sections " "-m64 -march=x86-64 -mabi=sysv -mcmodel=kernel " "-mno-red-zone -mgeneral-regs-only -fcf-protection=none"; const char *sse_base_cflags = "-g -O2 -pipe -Wall -Wextra -std=gnu11 -nostdinc -ffreestanding " "-fno-stack-protector -fno-stack-check -fno-lto -fno-PIC " "-ffunction-sections -fdata-sections " "-m64 -march=x86-64 -mabi=sysv -mcmodel=kernel " "-mno-red-zone -fcf-protection=none"; const char *core_cflags = "-mno-sse -mno-sse2 -mno-mmx -mno-3dnow"; const char *sse_cflags = "-msse -msse2 -mfpmath=sse -mno-mmx -mno-3dnow"; char cppflags[2048]; snprintf(cppflags, sizeof(cppflags), "-I kernel/src" " -I libc/include" " -I limine-tools/limine-protocol/include" " -isystem limine-tools/freestnd-c-hdrs/include" " -MMD -MP"); FileList sources = {0}; if (file_exists("kernel/src")) find_src_files("kernel/src", &sources); if (file_exists("libc/src")) find_src_files("libc/src", &sources); FileList objects = {0}; bool failed = false; for (int i = 0; i < sources.count; i++) { char *src = sources.paths[i]; char category[16] = "other"; if (strncmp(src, "kernel/", 7) == 0) strcpy(category, "kernel"); else if (strncmp(src, "libc/", 5) == 0) strcpy(category, "libc"); char obj_path[PATH_MAX]; char flat[PATH_MAX]; strcpy(flat, src); for (int j = 0; flat[j]; j++) if (flat[j] == '/' || flat[j] == '.') flat[j] = '_'; snprintf(obj_path, sizeof(obj_path), "obj/%s/%s.o", category, flat); file_list_add(&objects, obj_path); if (file_exists(obj_path) && get_mtime(src) <= get_mtime(obj_path) && get_mtime("builder/build.c") <= get_mtime(obj_path)) continue; const char *ext = strrchr(src, '.'); if (strcmp(ext, ".psf") == 0) { print_color(COLOR_BLUE, "Converting binary: %s", src); char tmp[PATH_MAX]; snprintf(tmp, sizeof(tmp), "temp_%s", strrchr(src, '/') + 1); cmd_run(false, "cp %s %s", src, tmp); cmd_run(false, "objcopy -I binary -O elf64-x86-64 -B i386:x86-64 " "--rename-section .data=.rodata,alloc,load,readonly,data,contents " "%s %s", tmp, obj_path); remove(tmp); char stem[256]; strcpy(stem, strrchr(src, '/') + 1); *strrchr(stem, '.') = '\0'; char rsym[1024]; snprintf(rsym, sizeof(rsym), "--redefine-sym _binary_temp_%s_psf_start=_binary_%s_psf_start " "--redefine-sym _binary_temp_%s_psf_end=_binary_%s_psf_end " "--redefine-sym _binary_temp_%s_psf_size=_binary_%s_psf_size", stem, stem, stem, stem, stem, stem); cmd_run(false, "objcopy %s %s", rsym, obj_path); } else if (strcmp(ext, ".asm") == 0) { print_color(COLOR_CYAN, "[asm] %s", src); if (cmd_run(false, "nasm -g -F dwarf -f elf64 %s -o %s", src, obj_path) != 0) failed = true; } else { bool sse = is_sse_file(src); char flags[1024]; snprintf(flags, sizeof(flags), "%s %s", sse ? sse_base_cflags : base_cflags, sse ? sse_cflags : core_cflags); print_color(sse ? COLOR_MAGENTA : COLOR_CYAN, "[%s] %s", category, src); if (cmd_run(false, "gcc %s %s -c %s -o %s", flags, cppflags, src, obj_path) != 0) failed = true; } } if (failed) return false; print_color(COLOR_BLUE, "Linking kernel..."); char ld_cmd[65536]; snprintf(ld_cmd, sizeof(ld_cmd), "ld -m elf_x86_64 -nostdlib -static -z max-page-size=0x1000 " "--gc-sections -T kernel/linker-scripts/x86_64.lds -o bin/kernel"); for (int i = 0; i < objects.count; i++) { strcat(ld_cmd, " "); strcat(ld_cmd, objects.paths[i]); } if (system(ld_cmd) != 0) return false; print_color(COLOR_GREEN, "Kernel linked: bin/kernel"); return true; } bool create_iso(void) { print_color(COLOR_GREEN, "Creating ISO..."); if (!build_limine()) return false; if (!file_exists("bin/kernel")) { print_color(COLOR_RED, "bin/kernel not found!"); return false; } rm_rf("iso_root"); ensure_dir("iso_root/boot/limine"); ensure_dir("iso_root/boot/wallpapers"); ensure_dir("iso_root/EFI/BOOT"); ensure_dir("demo_iso"); if (file_exists(WALLPAPER_SRC)) { cmd_run(false, "cp %s iso_root/boot/wallpapers/cervus.png", WALLPAPER_SRC); print_color(COLOR_GREEN, "Wallpaper copied"); } else { print_color(COLOR_YELLOW, "Warning: wallpaper not found at %s", WALLPAPER_SRC); } cmd_run(false, "cp bin/kernel iso_root/boot/kernel"); bool has_elf = file_exists(SHELL_ELF); if (has_elf) { cmd_run(false, "cp %s iso_root/boot/shell.elf", SHELL_ELF); print_color(COLOR_GREEN, "[module 0] shell.elf -> iso_root/boot/shell.elf"); } else { print_color(COLOR_RED, "[module 0] shell.elf not found - boot will fail!"); } bool has_initramfs = !ARG_NO_INITRAMFS && file_exists(INITRAMFS_TAR); if (has_initramfs) { cmd_run(false, "cp %s iso_root/boot/initramfs.tar", INITRAMFS_TAR); print_color(COLOR_GREEN, "[module 1] initramfs.tar -> iso_root/boot/initramfs.tar"); } else { print_color(COLOR_YELLOW, "[module 1] initramfs.tar not found - booting without rootfs"); } FILE *f = fopen("limine.conf", "w"); if (!f) { print_color(COLOR_RED, "Cannot create limine.conf"); return false; } if (file_exists(WALLPAPER_SRC)) fprintf(f, "wallpaper: %s\n", WALLPAPER_DST); fprintf(f, "timeout: 5\n\n"); fprintf(f, "/%s %s (Install / Live)\n" " protocol: limine\n" " path: boot():/boot/kernel\n", IMAGE_NAME, VERSION); if (has_elf) { fprintf(f, " module_path: boot():/boot/shell.elf\n" " module_cmdline: init\n"); } if (has_initramfs) { fprintf(f, " module_path: boot():/boot/initramfs.tar\n" " module_cmdline: initramfs\n"); } fclose(f); print_color(COLOR_CYAN, "--- limine.conf ---"); cmd_run(false, "cat limine.conf"); print_color(COLOR_CYAN, "-------------------"); cmd_run(false, "cp limine.conf iso_root/boot/limine/"); cmd_run(false, "cp limine/limine-bios.sys limine/limine-bios-cd.bin " "limine/limine-uefi-cd.bin iso_root/boot/limine/"); cmd_run(false, "cp limine/BOOTX64.EFI limine/BOOTIA32.EFI iso_root/EFI/BOOT/"); char timestamp[64]; time_t t = time(NULL); strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", localtime(&t)); char iso_name[PATH_MAX]; snprintf(iso_name, sizeof(iso_name), "demo_iso/%s.%s.%s.iso", IMAGE_NAME, VERSION, timestamp); char xorriso[PATH_MAX + 1024]; snprintf(xorriso, sizeof(xorriso), "xorriso -as mkisofs -R -r -J" " -b boot/limine/limine-bios-cd.bin" " -no-emul-boot -boot-load-size 4 -boot-info-table" " -hfsplus -apm-block-size 2048" " --efi-boot boot/limine/limine-uefi-cd.bin" " -efi-boot-part --efi-boot-image --protective-msdos-label" " iso_root -o %s", iso_name); if (cmd_run(true, xorriso) != 0) return false; cmd_run(true, "./limine/limine bios-install %s", iso_name); char link[PATH_MAX]; snprintf(link, sizeof(link), "demo_iso/%s.latest.iso", IMAGE_NAME); unlink(link); (void)!symlink(strrchr(iso_name, '/') + 1, link); rm_rf("iso_root"); print_color(COLOR_GREEN, "ISO ready: %s", iso_name); return true; } void check_sudo(void) { if (geteuid() != 0) { print_color(COLOR_RED, "This command requires root. Run with sudo."); exit(1); } } void list_iso_files(FileList *list) { DIR *dir = opendir("demo_iso"); if (!dir) return; struct dirent *e; while ((e = readdir(dir)) != NULL) { if (strstr(e->d_name, ".iso")) { char path[PATH_MAX]; snprintf(path, sizeof(path), "demo_iso/%s", e->d_name); file_list_add(list, path); } } closedir(dir); } void list_usb_devices(FileList *list) { DIR *dir = opendir("/sys/block"); if (!dir) return; struct dirent *e; while ((e = readdir(dir)) != NULL) { if (e->d_name[0] == '.') continue; char path[PATH_MAX]; snprintf(path, sizeof(path), "/sys/block/%s/removable", e->d_name); FILE *f = fopen(path, "r"); if (f) { int removable = 0; if (fscanf(f, "%d", &removable) == 1 && removable) { char dev[PATH_MAX]; snprintf(dev, sizeof(dev), "/dev/%s", e->d_name); file_list_add(list, dev); } fclose(f); } } closedir(dir); } void flash_iso(void) { check_sudo(); FileList isos = {0}, devs = {0}; list_iso_files(&isos); if (isos.count == 0) { print_color(COLOR_RED, "No ISO found in demo_iso/"); return; } printf("\n%s--- SELECT ISO ---%s\n", COLOR_CYAN, COLOR_RESET); for (int i = 0; i < isos.count; i++) printf("[%d] %s\n", i + 1, isos.paths[i]); int ic; printf("Choice: "); if (scanf("%d", &ic) < 1 || ic < 1 || ic > isos.count) return; list_usb_devices(&devs); if (devs.count == 0) { print_color(COLOR_RED, "No removable USB devices found!"); return; } printf("\n%s--- SELECT DEVICE ---%s\n", COLOR_RED, COLOR_RESET); for (int i = 0; i < devs.count; i++) { char mpath[PATH_MAX], model[256] = "Unknown"; snprintf(mpath, sizeof(mpath), "/sys/block/%s/device/model", devs.paths[i] + 5); FILE *mf = fopen(mpath, "r"); if (mf) { if (fgets(model, sizeof(model), mf)) model[strcspn(model, "\n")] = 0; fclose(mf); } printf("[%d] %s (%s)\n", i + 1, devs.paths[i], model); } int dc; printf("Choice: "); if (scanf("%d", &dc) < 1 || dc < 1 || dc > devs.count) return; printf("\n%sWARNING: ALL DATA ON %s WILL BE ERASED!%s\n", COLOR_BOLD, devs.paths[dc - 1], COLOR_RESET); printf("Type 'YES' to confirm: "); char confirm[10]; if (scanf("%9s", confirm) != 1) { printf("Aborted.\n"); return; } if (strcmp(confirm, "YES") != 0) { printf("Aborted.\n"); return; } print_color(COLOR_YELLOW, "Flashing %s -> %s ...", isos.paths[ic - 1], devs.paths[dc - 1]); cmd_run(true, "dd if=%s of=%s bs=4M status=progress oflag=sync", isos.paths[ic - 1], devs.paths[dc - 1]); print_color(COLOR_GREEN, "Done!"); } bool is_unreadable_file(const char *filename) { const char *skip_names[] = { "TODO", "LICENSE", "build", NULL }; for (int i = 0; skip_names[i]; i++) if (strcmp(filename, skip_names[i]) == 0) return true; const char *ext = strrchr(filename, '.'); if (!ext) return false; const char *bin_exts[] = { ".psf", ".jpg", ".png", ".jpeg", ".iso", ".hdd", ".img", ".bin", ".elf", ".o", ".txt", ".md", ".gitignore", ".json", ".tar", NULL }; for (int i = 0; bin_exts[i]; i++) if (strcasecmp(ext, bin_exts[i]) == 0) return true; return false; } void generate_tree_recursive(const char *base_dir, FILE *out, int level) { struct dirent **namelist; int n = scandir(base_dir, &namelist, NULL, alphasort); if (n < 0) return; const char *skip_dirs[] = { "wallpapers", "demo_iso", ".vscode", "builder", INITRAMFS_ROOTFS, NULL }; for (int i = 0; i < n; i++) { const char *name = namelist[i]->d_name; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0 || strcmp(name, ".git") == 0 || strcmp(name, "obj") == 0 || strcmp(name, "bin") == 0 || strcmp(name, "limine") == 0) { free(namelist[i]); continue; } char path[PATH_MAX]; snprintf(path, sizeof(path), "%s/%s", base_dir, name); struct stat st; stat(path, &st); if (S_ISDIR(st.st_mode)) { bool skip = false; for (int j = 0; skip_dirs[j]; j++) if (strcmp(name, skip_dirs[j]) == 0) { skip = true; break; } if (skip) { free(namelist[i]); continue; } } if (!S_ISDIR(st.st_mode) && is_unreadable_file(name)) { free(namelist[i]); continue; } for (int j = 0; j < level; j++) fprintf(out, "| "); if (S_ISDIR(st.st_mode)) { fprintf(out, "+-- %s/\n", name); generate_tree_recursive(path, out, level + 1); } else { fprintf(out, "+-- %s (%ld bytes)\n", name, st.st_size); if (!ARG_STRUCTURE_ONLY && should_print_content(name)) { FILE *src = fopen(path, "r"); if (src) { char line[4096]; int ln = 1; while (fgets(line, sizeof(line), src) && ln <= 5000) { for (int j = 0; j <= level; j++) fprintf(out, "| "); fprintf(out, "| %4d: %s", ln++, line); } fclose(src); } } } free(namelist[i]); } free(namelist); } void do_generate_tree(void) { FILE *f = fopen("OS-TREE.txt", "w"); if (!f) return; time_t t = time(NULL); fprintf(f, "OS Tree - %s %s | %s", IMAGE_NAME, VERSION, ctime(&t)); generate_tree_recursive(".", f, 0); fclose(f); print_color(COLOR_GREEN, "Tree generated to OS-TREE.txt"); } static bool limine_conf_has_cervus(const char *path) { FILE *f = fopen(path, "r"); if (!f) return false; char line[1024]; while (fgets(line, sizeof(line), f)) { if (strstr(line, CERVUS_MARKER)) { fclose(f); return true; } } fclose(f); return false; } static bool __attribute__((unused)) copy_file_raw(const char *src, const char *dst) { FILE *in = fopen(src, "r"); if (!in) return false; FILE *out = fopen(dst, "w"); if (!out) { fclose(in); return false; } char buf[4096]; size_t n; while ((n = fread(buf, 1, sizeof(buf), in)) > 0) fwrite(buf, 1, n, out); fclose(in); fclose(out); return true; } static bool ask_yes_no(const char *question) { printf("%s%s [y/n]: %s", COLOR_YELLOW, question, COLOR_RESET); fflush(stdout); char buf[16]; if (!fgets(buf, sizeof(buf), stdin)) return false; return (buf[0] == 'y' || buf[0] == 'Y'); } void reset_hardware_conf(void) { check_sudo(); if (!file_exists(LIMINE_CONF_BACKUP)) { print_color(COLOR_RED, "No backup found at %s", LIMINE_CONF_BACKUP); print_color(COLOR_RED, "Nothing to restore. Was hardwaretest ever run?"); return; } print_color(COLOR_CYAN, "Restoring original Limine configuration..."); if (cmd_run(true, "cp %s %s", LIMINE_CONF_BACKUP, LIMINE_CONF_PATH) != 0) { print_color(COLOR_RED, "Failed to restore %s", LIMINE_CONF_PATH); return; } print_color(COLOR_GREEN, "Original limine.conf restored from backup"); if (file_exists(CERVUS_BOOT_DIR)) { print_color(COLOR_CYAN, "Removing %s ...", CERVUS_BOOT_DIR); cmd_run(true, "rm -rf %s", CERVUS_BOOT_DIR); } print_color(COLOR_GREEN, "Hardware configuration reset complete"); print_color(COLOR_CYAN, "--- Current limine.conf ---"); cmd_run(false, "cat %s", LIMINE_CONF_PATH); print_color(COLOR_CYAN, "---------------------------"); if (ask_yes_no("Reboot now?")) { print_color(COLOR_YELLOW, "Rebooting..."); cmd_run(false, "reboot"); } } static void write_cervus_entries(FILE *f, bool has_elf, bool has_initramfs) { if (file_exists(WALLPAPER_SRC)) fprintf(f, "wallpaper: boot():/cervus/wallpaper.png\n"); fprintf(f, "/%s %s (Install / Live)\n" " protocol: limine\n" " path: boot():/cervus/kernel\n", IMAGE_NAME, VERSION); if (has_elf) { fprintf(f, " module_path: boot():/cervus/shell.elf\n" " module_cmdline: init\n"); } if (has_initramfs) { fprintf(f, " module_path: boot():/cervus/initramfs.tar\n" " module_cmdline: initramfs\n"); } fprintf(f, "/%s %s (Installed - boot from disk)\n" " protocol: limine\n" " path: boot():/cervus/kernel\n", IMAGE_NAME, VERSION); if (has_elf) { fprintf(f, " module_path: boot():/cervus/shell.elf\n" " module_cmdline: init\n"); } } void hardware_test(void) { check_sudo(); if (!file_exists(LIMINE_CONF_PATH)) { print_color(COLOR_RED, "Limine config not found at %s", LIMINE_CONF_PATH); print_color(COLOR_RED, "Is Limine installed as your bootloader?"); print_color(COLOR_YELLOW, "Expected path: %s", LIMINE_CONF_PATH); print_color(COLOR_YELLOW, "Install Limine first: https://github.com/limine-bootloader/limine"); return; } print_color(COLOR_GREEN, "=== Cervus Hardware Test Setup ==="); print_color(COLOR_CYAN, "Limine config found: %s", LIMINE_CONF_PATH); print_color(COLOR_CYAN, "Step 1: Building Cervus..."); if (!compile_kernel()) { print_color(COLOR_RED, "Kernel compilation failed"); return; } if (!build_initramfs()) { print_color(COLOR_RED, "initramfs build failed"); return; } if (!file_exists("bin/kernel")) { print_color(COLOR_RED, "bin/kernel not found after build!"); return; } print_color(COLOR_CYAN, "Step 2: Checking Limine configuration..."); bool already_has_cervus = limine_conf_has_cervus(LIMINE_CONF_PATH); if (already_has_cervus) { print_color(COLOR_GREEN, "Cervus entry already present in limine.conf"); print_color(COLOR_CYAN, "Updating boot files only..."); } else { print_color(COLOR_CYAN, "No Cervus entry found, will add one"); if (!file_exists(LIMINE_CONF_BACKUP)) { print_color(COLOR_YELLOW, "Step 2a: Backing up original limine.conf..."); if (cmd_run(true, "cp %s %s", LIMINE_CONF_PATH, LIMINE_CONF_BACKUP) != 0) { print_color(COLOR_RED, "Failed to backup limine.conf!"); return; } print_color(COLOR_GREEN, "Backup saved: %s", LIMINE_CONF_BACKUP); print_color(COLOR_YELLOW, "To restore later: ./build --resethardwareconf"); } else { print_color(COLOR_GREEN, "Backup already exists: %s", LIMINE_CONF_BACKUP); } } print_color(COLOR_CYAN, "Step 3: Copying Cervus files to /boot..."); cmd_run(true, "mkdir -p %s", CERVUS_BOOT_DIR); if (cmd_run(true, "cp bin/kernel %s/kernel", CERVUS_BOOT_DIR) != 0) { print_color(COLOR_RED, "Failed to copy kernel"); return; } print_color(COLOR_GREEN, " kernel -> %s/kernel", CERVUS_BOOT_DIR); bool has_elf = file_exists(SHELL_ELF); if (has_elf) { cmd_run(true, "cp %s %s/shell.elf", SHELL_ELF, CERVUS_BOOT_DIR); print_color(COLOR_GREEN, " shell.elf -> %s/shell.elf", CERVUS_BOOT_DIR); } bool has_initramfs = file_exists(INITRAMFS_TAR); if (has_initramfs) { cmd_run(true, "cp %s %s/initramfs.tar", INITRAMFS_TAR, CERVUS_BOOT_DIR); print_color(COLOR_GREEN, " initramfs.tar -> %s/initramfs.tar", CERVUS_BOOT_DIR); } if (file_exists(WALLPAPER_SRC)) { cmd_run(true, "cp %s %s/wallpaper.png", WALLPAPER_SRC, CERVUS_BOOT_DIR); print_color(COLOR_GREEN, " wallpaper -> %s/wallpaper.png", CERVUS_BOOT_DIR); } if (!already_has_cervus) { print_color(COLOR_CYAN, "Step 4: Adding Cervus entries to limine.conf..."); FILE *f = fopen(LIMINE_CONF_PATH, "a"); if (!f) { print_color(COLOR_RED, "Cannot open %s for writing", LIMINE_CONF_PATH); return; } fprintf(f, "\n%s\n", CERVUS_MARKER); write_cervus_entries(f, has_elf, has_initramfs); fprintf(f, "%s\n", "# --- END CERVUS ---"); fclose(f); print_color(COLOR_GREEN, "Cervus boot entries added to limine.conf"); } else { print_color(COLOR_CYAN, "Step 4: Updating Cervus entries in limine.conf..."); FILE *orig = fopen(LIMINE_CONF_PATH, "r"); if (!orig) { print_color(COLOR_RED, "Cannot read %s", LIMINE_CONF_PATH); return; } char tmppath[PATH_MAX]; snprintf(tmppath, sizeof(tmppath), "%s.tmp", LIMINE_CONF_PATH); FILE *tmp = fopen(tmppath, "w"); if (!tmp) { fclose(orig); print_color(COLOR_RED, "Cannot create temp file"); return; } char line[1024]; bool in_cervus = false; while (fgets(line, sizeof(line), orig)) { if (strstr(line, CERVUS_MARKER)) { in_cervus = true; continue; } if (in_cervus && strstr(line, "# --- END CERVUS ---")) { in_cervus = false; continue; } if (!in_cervus) { fputs(line, tmp); } } fclose(orig); fprintf(tmp, "\n%s\n", CERVUS_MARKER); write_cervus_entries(tmp, has_elf, has_initramfs); fprintf(tmp, "%s\n", "# --- END CERVUS ---"); fclose(tmp); cmd_run(false, "mv %s %s", tmppath, LIMINE_CONF_PATH); print_color(COLOR_GREEN, "Cervus entries updated in limine.conf"); } print_color(COLOR_GREEN, "\n=== Setup Complete ==="); print_color(COLOR_CYAN, "--- Current limine.conf ---"); cmd_run(false, "cat %s", LIMINE_CONF_PATH); print_color(COLOR_CYAN, "---------------------------"); print_color(COLOR_GREEN, "\nCervus will appear in your Limine boot menu."); print_color(COLOR_YELLOW, "First boot: select '%s %s (Install / Live)'", IMAGE_NAME, VERSION); print_color(COLOR_YELLOW, "After install: select '%s %s (Installed - boot from disk)'", IMAGE_NAME, VERSION); print_color(COLOR_YELLOW, "To restore original config: sudo ./build --resethardwareconf"); if (ask_yes_no("\nReboot now to test on real hardware?")) { print_color(COLOR_YELLOW, "Rebooting..."); cmd_run(false, "reboot"); } else { print_color(COLOR_GREEN, "OK. Reboot manually when ready."); } } static const char* find_ovmf(void) { const char *ovmf_paths[] = { "/usr/share/edk2/x64/OVMF.4m.fd", "/usr/share/edk2/x64/OVMF_CODE.4m.fd", "/usr/share/edk2/ovmf/OVMF.fd", "/usr/share/edk2/ovmf/OVMF_CODE.fd", "/usr/share/ovmf/x64/OVMF.fd", "/usr/share/ovmf/x64/OVMF_CODE.fd", "/usr/share/ovmf/OVMF.fd", "/usr/share/OVMF/OVMF.fd", "/usr/share/OVMF/OVMF_CODE.fd", "/usr/share/qemu/OVMF.fd", NULL }; for (int i = 0; ovmf_paths[i]; i++) { if (file_exists(ovmf_paths[i])) return ovmf_paths[i]; } return NULL; } static void print_help(void) { printf("%sCervus OS build system%s\n\n", COLOR_BOLD, COLOR_RESET); printf("Usage: ./build [command] [options]\n\n"); printf("Commands:\n"); printf(" run Build kernel + initramfs + ISO, then launch QEMU (BIOS, with disk)\n"); printf(" run-uefi Build kernel + initramfs + ISO, then launch QEMU (UEFI, with disk)\n"); printf(" run-installed Launch QEMU from cervus_disk.img only (BIOS, no ISO, real hardware sim)\n"); printf(" run-installed-uefi Launch QEMU from cervus_disk.img only (UEFI, no ISO)\n"); printf(" run-fresh Wipe disk and run installer from ISO again (BIOS)\n"); printf(" run-fresh-uefi Wipe disk and run installer from ISO again (UEFI)\n"); printf(" hardwaretest Build & install Cervus into Limine boot menu (requires sudo)\n"); printf(" flash Flash latest ISO to USB device (requires sudo)\n"); printf(" clean Remove all build artifacts\n"); printf(" cleaniso Remove only ISO images in demo_iso/\n"); printf(" gitclean Same as clean (for git commit prep)\n"); printf(" help Show this message\n\n"); printf("Subcomponent build (debug):\n"); printf(" _libcervus Build only libcervus.a + crt0.o into sysroot\n"); printf(" _tcc Build only TCC (and libcervus first) into sysroot\n\n"); printf("Options:\n"); printf(" --tree [files] Generate OS-TREE.txt (optional: only list files)\n"); printf(" --structure-only Generate tree without file contents\n"); printf(" --no-clean Keep obj/ and bin/ after run\n"); printf(" --no-initramfs Skip initramfs.tar creation\n"); printf(" --reset-disk Re-create empty disk image\n"); printf(" --live Run without disk (Live Mode, BIOS only)\n"); printf(" --resethardwareconf Restore original Limine config (requires sudo)\n\n"); printf("Boot entries in Limine menu:\n"); printf(" Install / Live - first boot, runs installer, uses initramfs\n"); printf(" Installed - boot from disk - after install, no initramfs, uses ext2 disk\n\n"); printf("Hardware test workflow:\n"); printf(" sudo ./build hardwaretest # build, install to /boot, add boot entries\n"); printf(" sudo ./build --resethardwareconf # restore original bootloader config\n\n"); printf("UEFI Requirements:\n"); printf(" Install OVMF: sudo apt install ovmf (Debian/Ubuntu) or equivalent\n\n"); printf("Examples:\n"); printf(" ./build run # normal boot with disk (BIOS)\n"); printf(" ./build run-uefi # normal boot with disk (UEFI)\n"); printf(" ./build run --live # boot without disk (Live Mode, BIOS)\n"); printf(" ./build run-fresh-uefi # fresh disk, boot installer (UEFI)\n"); printf(" ./build run --reset-disk # fresh disk (BIOS)\n"); printf(" ./build run --tree\n"); printf(" ./build --tree kernel.c vfs.c\n"); } int main(int argc, char **argv) { char *command = NULL; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--tree") == 0) { ARG_TREE = true; int j = i + 1; while (j < argc && argv[j][0] != '-') add_tree_file(argv[j++]); i = j - 1; } else if (strcmp(argv[i], "--structure-only") == 0) { ARG_STRUCTURE_ONLY = true; } else if (strcmp(argv[i], "--no-clean") == 0) { ARG_NO_CLEAN = true; } else if (strcmp(argv[i], "--no-initramfs") == 0) { ARG_NO_INITRAMFS = true; } else if (strcmp(argv[i], "--resethardwareconf") == 0) { ARG_RESET_HW_CONF = true; } else if (strcmp(argv[i], "--reset-disk") == 0) { ARG_RESET_DISK = true; } else if (strcmp(argv[i], "--live") == 0) { ARG_LIVE = true; } else if (argv[i][0] != '-') { command = argv[i]; } } if (ARG_TREE && !command) { do_generate_tree(); return 0; } if (ARG_RESET_HW_CONF) { reset_hardware_conf(); return 0; } if (!command || strcmp(command, "help") == 0) { print_help(); return 0; } if (strcmp(command, "clean") == 0 || strcmp(command, "gitclean") == 0) { for (int i = 0; DIRS_TO_CLEAN[i]; i++) rm_rf(DIRS_TO_CLEAN[i]); clean_apps_elfs(); clean_bin_elfs(); for (int i = 0; FILES_TO_CLEAN[i]; i++) if (file_exists(FILES_TO_CLEAN[i])) remove(FILES_TO_CLEAN[i]); clean_libcervus_build(true); clean_tcc_build(true); cmd_run(false, "rm -f " INSTALLER_DIR "/*.elf 2>/dev/null"); cmd_run(false, "rm -f temp_* 2>/dev/null"); print_color(COLOR_GREEN, "Cleanup complete"); return 0; } if (strcmp(command, "cleaniso") == 0) { rm_rf("demo_iso"); ensure_dir("demo_iso"); return 0; } if (strcmp(command, "flash") == 0) { flash_iso(); return 0; } if (strcmp(command, "hardwaretest") == 0) { hardware_test(); return 0; } if (strcmp(command, "_libcervus") == 0) { return build_libcervus() ? 0 : 1; } if (strcmp(command, "_tcc") == 0) { if (!build_libcervus()) return 1; return build_tcc() ? 0 : 1; } if (strcmp(command, "run-installed-uefi") == 0) { if (!file_exists("cervus_disk.img")) { print_color(COLOR_RED, "cervus_disk.img not found. Run './build run' first."); return 1; } const char *ovmf = find_ovmf(); if (!ovmf) { print_color(COLOR_RED, "OVMF not found. Install: sudo apt install ovmf"); return 1; } print_color(COLOR_GREEN, "Starting QEMU with UEFI firmware from disk only..."); print_color(COLOR_GREEN, "Using OVMF: %s", ovmf); cmd_run(false, "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" " -bios %s" " -drive file=cervus_disk.img,format=raw,if=ide" " -serial stdio" " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" " 2>&1 | tee log.txt", ovmf); return 0; } if (strcmp(command, "run-installed") == 0) { if (!file_exists("cervus_disk.img")) { print_color(COLOR_RED, "cervus_disk.img not found. " "Run './build run' first to build and install Cervus."); return 1; } print_color(COLOR_CYAN, "Starting QEMU from disk only (no ISO, simulates real hardware)..."); cmd_run(false, "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" " -drive file=cervus_disk.img,format=raw,if=ide" " -serial stdio" " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" " 2>&1 | tee log.txt"); return 0; } if (strcmp(command, "run-fresh") == 0) { ARG_RESET_DISK = true; command = "run"; } if (strcmp(command, "run-fresh-uefi") == 0) { ARG_RESET_DISK = true; command = "run-uefi"; } if (strcmp(command, "run") == 0 || strcmp(command, "run-uefi") == 0) { bool use_uefi = (strcmp(command, "run-uefi") == 0); if (!compile_kernel()) { print_color(COLOR_RED, "Kernel compilation failed"); return 1; } if (!build_initramfs()) { print_color(COLOR_RED, "initramfs build failed"); return 1; } if (!create_iso()) { print_color(COLOR_RED, "ISO creation failed"); return 1; } if (ARG_TREE) do_generate_tree(); char iso_path[PATH_MAX]; snprintf(iso_path, sizeof(iso_path), "demo_iso/%s.latest.iso", IMAGE_NAME); if (use_uefi) { const char *ovmf = find_ovmf(); if (!ovmf) { print_color(COLOR_RED, "OVMF not found. Install: sudo apt install ovmf"); return 1; } print_color(COLOR_GREEN, "Using OVMF: %s", ovmf); } if (ARG_LIVE && !use_uefi) { print_color(COLOR_CYAN, "Starting QEMU in Live Mode (no disk, BIOS)..."); cmd_run(false, "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" " -cdrom %s -boot d" " -serial stdio" " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" " 2>&1 | tee log.txt", iso_path); } else if (ARG_LIVE && use_uefi) { print_color(COLOR_RED, "Live mode (--live) is not supported in UEFI mode yet"); return 1; } else { if (!file_exists("cervus_disk.img") || ARG_RESET_DISK) { if (ARG_RESET_DISK && file_exists("cervus_disk.img")) { print_color(COLOR_YELLOW, "[disk] --reset-disk: removing old disk image"); remove("cervus_disk.img"); } print_color(COLOR_CYAN, "Creating empty disk image (256MB)..."); cmd_run(false, "dd if=/dev/zero of=cervus_disk.img bs=1M count=256 2>/dev/null"); print_color(COLOR_GREEN, "Disk created (OS installer will format on first boot)"); } else { print_color(COLOR_GREEN, "Using existing cervus_disk.img (persistent data)"); } if (use_uefi) { const char *ovmf = find_ovmf(); print_color(COLOR_GREEN, "Starting QEMU with UEFI firmware (IDE mode)..."); cmd_run(false, "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" " -bios %s" " -cdrom %s -boot d" " -serial stdio" " -m 8G -smp 8 -cpu qemu64,+fsgsbase -display gtk,grab-on-hover=on" " -drive file=cervus_disk.img,format=raw,if=ide,index=0,media=disk" " 2>&1 | tee log.txt", ovmf, iso_path); } else { print_color(COLOR_GREEN, "Starting QEMU with BIOS..."); cmd_run(false, "GDK_BACKEND=x11 qemu-system-x86_64 -machine pc" " -cdrom %s -boot d" " -serial stdio" " %s" " 2>&1 | tee log.txt", iso_path, QEMUFLAGS); } } if (!ARG_NO_CLEAN) { print_color(COLOR_CYAN, "[post-run] Cleaning build artifacts..."); rm_rf("obj"); rm_rf("bin"); rm_rf(INITRAMFS_ROOTFS); if (file_exists(INITRAMFS_TAR)) { remove(INITRAMFS_TAR); print_color(COLOR_GREEN, "[post-run] removed %s", INITRAMFS_TAR); } clean_apps_elfs(); clean_bin_elfs(); clean_libcervus_build(false); clean_tcc_build(false); print_color(COLOR_GREEN, "[post-run] Done."); } return 0; } print_color(COLOR_RED, "Unknown command: %s (try: ./build help)", command); return 1; }