Files
alexvoste 1a9fd27a31 push
2026-05-07 02:22:25 +03:00

592 lines
16 KiB
C

#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>
#define MAX_ENTRIES 128
#define MBR_TYPE_FAT32_LBA 0x0C
#define MBR_TYPE_LINUX 0x83
#define MBR_TYPE_LINUX_SWAP 0x82
typedef struct {
char name[32];
char model[41];
uint64_t sectors;
uint64_t size_bytes;
} disk_summary_t;
typedef struct {
char name[64];
uint8_t type;
} dir_entry_t;
static void safe_strcpy(char *dst, size_t dsz, const char *src)
{
if (!dst || dsz == 0) return;
if (!src) { dst[0] = '\0'; return; }
size_t i = 0;
while (i + 1 < dsz && src[i]) { dst[i] = src[i]; i++; }
dst[i] = '\0';
}
static void clear_screen(void)
{
fputs("\x1b[2J\x1b[H", stdout);
}
static int ensure_dir(const char *path)
{
struct stat st;
if (stat(path, &st) == 0) return 0;
return mkdir(path, 0755);
}
static int ensure_parent_dir(const char *path)
{
char tmp[512];
size_t len = strlen(path);
if (len >= sizeof(tmp)) return -1;
int last_slash = -1;
for (int i = (int)len - 1; i >= 0; i--) {
if (path[i] == '/') { last_slash = i; break; }
}
if (last_slash <= 0) return 0;
for (int i = 0; i < last_slash; i++) tmp[i] = path[i];
tmp[last_slash] = '\0';
int depth = 0;
int starts[32];
starts[depth++] = 0;
for (int i = 1; i < last_slash && depth < 32; i++) {
if (tmp[i] == '/') starts[depth++] = i;
}
for (int d = 0; d < depth; d++) {
int end = (d + 1 < depth) ? starts[d + 1] : last_slash;
char part[512];
for (int i = 0; i < end; i++) part[i] = tmp[i];
part[end] = '\0';
if (part[0] == '\0') continue;
struct stat st;
if (stat(part, &st) != 0) syscall2(SYS_MKDIR, part, 0755);
}
return 0;
}
static int copy_one_file_progress(const char *src, const char *dst, const char *display_name)
{
int sfd = open(src, O_RDONLY, 0);
if (sfd < 0) return sfd;
ensure_parent_dir(dst);
int dfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC, 0755);
if (dfd < 0) { close(sfd); return dfd; }
struct stat st;
uint64_t total = 0;
if (stat(src, &st) == 0) total = st.st_size;
static char fbuf[4096];
ssize_t n;
int rc = 0;
uint64_t written = 0;
int last_pct = -1;
int spinner = 0;
while ((n = read(sfd, fbuf, sizeof(fbuf))) > 0) {
ssize_t w = write(dfd, fbuf, (size_t)n);
if (w < 0) { rc = (int)w; break; }
written += (uint64_t)w;
if (total > 0 && display_name) {
int pct = (int)((written * 100) / total);
if (pct != last_pct) {
static const char glyphs[4] = { '|', '/', '-', '\\' };
fputs("\r\033[K ", stdout);
putchar(glyphs[spinner & 3]);
fputs(" ", stdout);
fputs(display_name, stdout);
fputs(" ", stdout);
char pb[8];
int bi = 0;
if (pct >= 100) { pb[bi++]='1'; pb[bi++]='0'; pb[bi++]='0'; }
else if (pct >= 10) { pb[bi++]=(char)('0'+pct/10); pb[bi++]=(char)('0'+pct%10); }
else { pb[bi++]=(char)('0'+pct); }
pb[bi++]='%';
pb[bi]='\0';
fputs(pb, stdout);
spinner++;
last_pct = pct;
}
}
}
close(sfd);
close(dfd);
if (display_name) {
fputs("\r\033[K ", stdout);
fputs(display_name, stdout);
fputs(rc < 0 ? " FAILED\n" : " done\n", stdout);
}
return rc;
}
static int copy_one_file(const char *src, const char *dst)
{
return copy_one_file_progress(src, dst, NULL);
}
static int read_dir_entries(const char *path, dir_entry_t *out, int max)
{
DIR *d = opendir(path);
if (!d) return 0;
int count = 0;
struct dirent *de;
while (count < max && (de = readdir(d)) != NULL) {
if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
(de->d_name[1] == '.' && de->d_name[2] == '\0'))) continue;
safe_strcpy(out[count].name, sizeof(out[count].name), de->d_name);
out[count].type = de->d_type;
count++;
}
closedir(d);
return count;
}
static int should_exclude_from_copy(const char *name)
{
if (strcmp(name, "install-on-disk") == 0) return 1;
return 0;
}
static void copy_tree(const char *src_dir, const char *dst_dir)
{
dir_entry_t entries[MAX_ENTRIES];
int n = read_dir_entries(src_dir, entries, MAX_ENTRIES);
if (n == 0) return;
ensure_dir(dst_dir);
for (int i = 0; i < n; i++) {
if (should_exclude_from_copy(entries[i].name)) {
fputs(C_GRAY " skip (installer): " C_RESET, stdout);
fputs(entries[i].name, stdout);
putchar(10);
continue;
}
char sp[256], dp[256];
path_join(src_dir, entries[i].name, sp, sizeof(sp));
path_join(dst_dir, entries[i].name, dp, sizeof(dp));
if (entries[i].type == 1) {
ensure_dir(dp);
copy_tree(sp, dp);
} else {
copy_one_file_progress(sp, dp, sp);
}
}
}
static int list_disks(disk_summary_t out[4])
{
int found = 0;
for (int i = 0; i < 4; i++) {
struct {
char name[32];
uint64_t sectors;
uint64_t size_bytes;
char model[41];
uint8_t present;
uint8_t _pad[6];
} info;
memset(&info, 0, sizeof(info));
int r = (int)syscall2(SYS_DISK_INFO, (uint64_t)i, (uint64_t)&info);
if (r < 0 || !info.present) continue;
size_t nlen = strlen(info.name);
int is_part = 0;
for (size_t k = 0; k < nlen; k++) {
if (info.name[k] >= '0' && info.name[k] <= '9') { is_part = 1; break; }
}
if (is_part) continue;
safe_strcpy(out[found].name, sizeof(out[found].name), info.name);
safe_strcpy(out[found].model, sizeof(out[found].model), info.model);
out[found].sectors = info.sectors;
out[found].size_bytes = info.size_bytes;
found++;
if (found >= 4) break;
}
return found;
}
static int ask_choose_disk(char *out_name, size_t out_cap)
{
disk_summary_t disks[4];
int n = list_disks(disks);
if (n == 0) {
fputs(C_RED " No disks detected!" C_RESET "\n", stdout);
return -1;
}
fputs("\n", stdout);
fputs(C_CYAN " Available disks:" C_RESET "\n", stdout);
for (int i = 0; i < n; i++) {
fputs(" ", stdout);
putchar((char)('1' + i));
fputs(") ", stdout);
fputs(C_BOLD, stdout);
fputs(disks[i].name, stdout);
fputs(C_RESET, stdout);
fputs(" ", stdout);
uint64_t mb = disks[i].size_bytes / (1024 * 1024);
char buf[32];
int bi = 0;
if (mb == 0) {
buf[bi++] = '0';
} else {
char rev[32];
int ri = 0;
uint64_t v = mb;
while (v) { rev[ri++] = (char)('0' + (v % 10)); v /= 10; }
while (ri) buf[bi++] = rev[--ri];
}
buf[bi] = '\0';
fputs(buf, stdout);
fputs(" MB ", stdout);
fputs(disks[i].model, stdout);
fputs("\n", stdout);
}
fputs("\n Select disk [1-", stdout);
putchar((char)('0' + n));
fputs("] (q to cancel): ", stdout);
char c = 0;
while (1) {
if (read(0, &c, 1) <= 0) continue;
if (c >= '1' && c <= (char)('0' + n)) { putchar(c); putchar(10); break; }
if (c == 'q' || c == 'Q') { putchar(c); putchar(10); return -1; }
}
int idx = c - '1';
safe_strcpy(out_name, out_cap, disks[idx].name);
return 0;
}
static void progress_done(const char *msg)
{
putchar('\r');
fputs(C_GREEN " ", stdout);
fputs(msg, stdout);
fputs(C_RESET " \n", stdout);
}
static void progress_fail(const char *msg)
{
putchar('\r');
fputs(C_RED " ", stdout);
fputs(msg, stdout);
fputs(C_RESET " \n", stdout);
}
static int write_limine_conf(const char *path)
{
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) return fd;
const char *conf =
"timeout: 5\n"
"default_entry: 1\n"
"interface_branding: Cervus\n"
"wallpaper: boot():/boot/wallpaper.png\n"
"\n"
"/Cervus v0.0.2 Alpha\n"
" protocol: limine\n"
" path: boot():/kernel\n"
" module_path: boot():/shell.elf\n"
" module_cmdline: init\n";
write(fd, conf, strlen(conf));
close(fd);
return 0;
}
static int do_install(void)
{
clear_screen();
fputs(C_CYAN " Cervus OS Installer" C_RESET "\n", stdout);
fputs(C_GRAY " -----------------------------------" C_RESET "\n", stdout);
char chosen_disk_name[32];
if (ask_choose_disk(chosen_disk_name, sizeof(chosen_disk_name)) < 0) {
fputs("\n Cancelled.\n\n", stdout);
return 1;
}
fputs("\n Target disk: " C_BOLD, stdout);
fputs(chosen_disk_name, stdout);
fputs(C_RESET "\n", stdout);
fputs(" Layout: ESP (FAT32, 64 MB) + root (ext2) + swap (16 MB)\n", stdout);
fputs("\n", stdout);
fputs(C_RED " WARNING: This will erase ALL data on " C_RESET, stdout);
fputs(C_BOLD, stdout);
fputs(chosen_disk_name, stdout);
fputs(C_RESET C_RED "!" C_RESET "\n\n", stdout);
fputs(" Continue? [y/n]: ", stdout);
char c = 0;
while (1) {
if (read(0, &c, 1) <= 0) continue;
if (c == 'y' || c == 'Y' || c == 'n' || c == 'N') { putchar(c); putchar(10); break; }
}
if (c == 'n' || c == 'N') {
fputs("\n Cancelled.\n\n", stdout);
return 1;
}
disk_summary_t disks[4];
int n_disks = list_disks(disks);
uint64_t total_sectors = 0;
for (int i = 0; i < n_disks; i++) {
if (strcmp(disks[i].name, chosen_disk_name) == 0) {
total_sectors = disks[i].sectors;
break;
}
}
if (total_sectors < 300000) {
fputs(C_RED " Disk too small (need at least 150 MB)" C_RESET "\n\n", stdout);
return 1;
}
uint32_t esp_start = 2048;
uint32_t esp_size = 131072;
uint32_t swap_size = 32768;
uint32_t root_start = esp_start + esp_size;
uint32_t avail = (uint32_t)total_sectors - root_start - swap_size;
uint32_t root_size = avail;
uint32_t swap_start = root_start + root_size;
fputs("\n [1/8] Writing partition table...\n", stdout);
cervus_mbr_part_t specs[4];
memset(specs, 0, sizeof(specs));
specs[0].boot_flag = 1;
specs[0].type = MBR_TYPE_FAT32_LBA;
specs[0].lba_start = esp_start;
specs[0].sector_count = esp_size;
specs[1].boot_flag = 0;
specs[1].type = MBR_TYPE_LINUX;
specs[1].lba_start = root_start;
specs[1].sector_count = root_size;
specs[2].boot_flag = 0;
specs[2].type = MBR_TYPE_LINUX_SWAP;
specs[2].lba_start = swap_start;
specs[2].sector_count = swap_size;
if (cervus_disk_partition(chosen_disk_name, specs, 3) < 0) {
progress_fail("Failed to write partition table!");
return 1;
}
progress_done("partition table written");
char part1[32], part2[32];
snprintf(part1, sizeof(part1), "%s1", chosen_disk_name);
snprintf(part2, sizeof(part2), "%s2", chosen_disk_name);
fputs(" [2/8] Formatting ", stdout);
fputs(part1, stdout);
fputs(" as FAT32 (ESP)...\n", stdout);
if (cervus_disk_mkfs_fat32(part1, "CERVUS-ESP") < 0) {
progress_fail("mkfs.fat32 failed!");
return 1;
}
progress_done("FAT32 ESP created");
fputs(" [3/8] Formatting ", stdout);
fputs(part2, stdout);
fputs(" as ext2 (root)...\n", stdout);
if (cervus_disk_format(part2, "cervus-root") < 0) {
progress_fail("mkfs.ext2 failed!");
return 1;
}
progress_done("ext2 root created");
fputs(" [4/8] Mounting partitions...\n", stdout);
ensure_dir("/mnt/esp");
ensure_dir("/mnt/root");
if (cervus_disk_mount(part1, "/mnt/esp") < 0) {
progress_fail("mount ESP failed");
return 1;
}
if (cervus_disk_mount(part2, "/mnt/root") < 0) {
progress_fail("mount root failed");
cervus_disk_umount("/mnt/esp");
return 1;
}
progress_done("mounted");
fputs(" [5/8] Copying boot files to ESP...\n", stdout);
ensure_dir("/mnt/esp/boot");
ensure_dir("/mnt/esp/boot/limine");
ensure_dir("/mnt/esp/EFI");
ensure_dir("/mnt/esp/EFI/BOOT");
struct { const char *src; const char *dst; int required; } boot_files[] = {
{ "/boot/kernel", "/mnt/esp/boot/kernel", 1 },
{ "/boot/kernel", "/mnt/esp/kernel", 0 },
{ "/boot/shell.elf", "/mnt/esp/boot/shell.elf", 1 },
{ "/boot/shell.elf", "/mnt/esp/shell.elf", 0 },
{ "/boot/limine-bios.sys", "/mnt/esp/boot/limine/limine-bios.sys", 0 },
{ "/boot/limine-bios-hdd.bin", "/mnt/esp/boot/limine/limine-bios-hdd.bin", 0 },
{ "/boot/BOOTX64.EFI", "/mnt/esp/EFI/BOOT/BOOTX64.EFI", 0 },
{ "/boot/BOOTIA32.EFI", "/mnt/esp/EFI/BOOT/BOOTIA32.EFI", 0 },
{ "/boot/wallpaper.png", "/mnt/esp/boot/wallpaper.png", 0 },
{ "/boot/wallpaper.png", "/mnt/esp/wallpaper.png", 0 },
{ NULL, NULL, 0 }
};
for (int i = 0; boot_files[i].src; i++) {
struct stat st;
if (stat(boot_files[i].src, &st) != 0) {
if (boot_files[i].required) {
fputs(C_RED " MISSING required: " C_RESET, stdout);
fputs(boot_files[i].src, stdout);
putchar(10);
} else {
fputs(C_YELLOW " skip (missing): " C_RESET, stdout);
fputs(boot_files[i].src, stdout);
putchar(10);
}
continue;
}
if (copy_one_file_progress(boot_files[i].src, boot_files[i].dst, boot_files[i].src) < 0) {
fputs(C_RED " FAILED: " C_RESET, stdout);
fputs(boot_files[i].dst, stdout);
putchar(10);
} else {
fputs(C_GREEN " " C_RESET, stdout);
fputs(boot_files[i].dst, stdout);
putchar(10);
}
}
fputs(" [6/8] Writing limine.conf...\n", stdout);
int ok1 = write_limine_conf("/mnt/esp/boot/limine/limine.conf");
int ok2 = write_limine_conf("/mnt/esp/EFI/BOOT/limine.conf");
int ok3 = write_limine_conf("/mnt/esp/limine.conf");
if (ok1 < 0 && ok2 < 0 && ok3 < 0)
progress_fail("failed to write limine.conf");
else
progress_done("limine.conf written (3 locations)");
fputs(" [7/8] Populating root filesystem...\n", stdout);
const char *rdirs[] = {
"/mnt/root/bin", "/mnt/root/apps", "/mnt/root/etc",
"/mnt/root/home", "/mnt/root/tmp", "/mnt/root/var",
"/mnt/root/usr",
NULL
};
for (int i = 0; rdirs[i]; i++) ensure_dir(rdirs[i]);
fputs(" copying /bin...\n", stdout);
copy_tree("/bin", "/mnt/root/bin");
fputs(" copying /apps...\n", stdout);
copy_tree("/apps", "/mnt/root/apps");
struct stat ust;
if (stat("/usr", &ust) == 0) {
fputs(" copying /usr (sysroot)...\n", stdout);
copy_tree("/usr", "/mnt/root/usr");
} else {
fputs(" /usr not present in live image — sysroot skipped\n", stdout);
}
struct stat est;
if (stat("/etc", &est) == 0) {
fputs(" copying /etc...\n", stdout);
static dir_entry_t etc_entries[MAX_ENTRIES];
int nn = read_dir_entries("/etc", etc_entries, MAX_ENTRIES);
for (int i = 0; i < nn; i++) {
const char *nm = etc_entries[i].name;
size_t nl = strlen(nm);
int is_txt = (nl >= 5 && strcmp(nm + nl - 4, ".txt") == 0);
if (etc_entries[i].type == 0) {
char sp[256], dp[256];
path_join("/etc", nm, sp, sizeof(sp));
if (is_txt) path_join("/mnt/root/home", nm, dp, sizeof(dp));
else path_join("/mnt/root/etc", nm, dp, sizeof(dp));
copy_one_file(sp, dp);
} else if (etc_entries[i].type == 1) {
char sp[256], dp[256];
path_join("/etc", nm, sp, sizeof(sp));
path_join("/mnt/root/etc", nm, dp, sizeof(dp));
copy_tree(sp, dp);
}
}
}
struct stat hst;
if (stat("/home", &hst) == 0) {
fputs(" copying /home...\n", stdout);
copy_tree("/home", "/mnt/root/home");
}
fputs(" [8/8] Installing BIOS stage1 to MBR...\n", stdout);
{
static uint8_t sys_buf[300 * 1024];
int fd = open("/mnt/esp/boot/limine/limine-bios-hdd.bin", O_RDONLY, 0);
if (fd < 0) {
progress_fail("limine-bios-hdd.bin not found on ESP");
} else {
struct stat st;
int sr = stat("/mnt/esp/boot/limine/limine-bios-hdd.bin", &st);
uint32_t sys_size = (sr == 0) ? (uint32_t)st.st_size : 0;
if (sys_size == 0 || sys_size > sizeof(sys_buf)) {
close(fd);
progress_fail("bad limine-bios-hdd.bin size");
} else {
uint32_t got = 0;
int ok = 1;
while (got < sys_size) {
ssize_t n = read(fd, sys_buf + got, sys_size - got);
if (n <= 0) { ok = 0; break; }
got += (uint32_t)n;
}
close(fd);
if (!ok || got != sys_size) {
progress_fail("short read on limine-bios-hdd.bin");
} else {
long r = cervus_disk_bios_install(chosen_disk_name, sys_buf, sys_size);
if (r < 0) progress_fail("BIOS install syscall failed");
else progress_done("BIOS stage1 installed");
}
}
}
}
cervus_disk_umount("/mnt/esp");
cervus_disk_umount("/mnt/root");
clear_screen();
fputs("\n" C_GREEN " Installation complete!" C_RESET "\n", stdout);
fputs(" The system will reboot in 3 seconds.\n\n", stdout);
for (int i = 3; i > 0; i--) {
fputs(" Rebooting in ", stdout);
putchar((char)('0' + i));
fputs("...\r", stdout);
syscall1(SYS_SLEEP_NS, 1000000000ULL);
}
fputs("\n", stdout);
syscall0(SYS_REBOOT);
return 0;
}
int main(int argc, char **argv)
{
const char *mode = getenv_argv(argc, argv, "MODE", "");
if (strcmp(mode, "live") != 0) {
fputs(C_RED "install-on-disk: this command is only available in Live mode.\n" C_RESET, stderr);
fputs("The system is already installed on disk.\n", stderr);
return 1;
}
do_install();
return 0;
}