diff --git a/src/fs/mkfs_fat32.c b/src/fs/mkfs_fat32.c new file mode 100644 index 0000000..730de84 --- /dev/null +++ b/src/fs/mkfs_fat32.c @@ -0,0 +1,288 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +#include "mkfs_fat32.h" +#include "../dev/disk.h" +#include +#include "memory_manager.h" + +extern void serial_write(const char *str); +extern void serial_write_num(uint64_t num); + +// Internal helpers + +static void mf_memset(void *dst, int val, int len) { + unsigned char *p = (unsigned char *)dst; + while (len-- > 0) *p++ = (unsigned char)val; +} + +static void mf_memcpy(void *dst, const void *src, int len) { + unsigned char *d = (unsigned char *)dst; + const unsigned char *s = (const unsigned char *)src; + while (len-- > 0) *d++ = *s++; +} + +static void mf_strncpy(char *dst, const char *src, int n) { + int i = 0; + while (i < n && src[i]) { dst[i] = src[i]; i++; } + while (i < n) { dst[i++] = ' '; } /* FAT labels are space-padded */ +} + +// On-disk BPB structures + +typedef struct __attribute__((packed)) { + /* DOS 2.0 BPB */ + uint8_t jump_boot[3]; /* EB 58 90 */ + char oem_name[8]; /* "MSDOS5.0" */ + uint16_t bytes_per_sector; /* 512 */ + uint8_t sectors_per_cluster; + uint16_t reserved_sector_count; + uint8_t num_fats; /* 2 */ + uint16_t root_entry_count; /* 0 for FAT32 */ + uint16_t total_sectors_16; /* 0 for FAT32 */ + uint8_t media; /* 0xF8 for fixed disk */ + uint16_t fat_size_16; /* 0 for FAT32 */ + uint16_t sectors_per_track; + uint16_t num_heads; + uint32_t hidden_sectors; + uint32_t total_sectors_32; + + /* FAT32 extended BPB */ + uint32_t fat_size_32; + uint16_t ext_flags; + uint16_t fs_version; /* 0x0000 */ + uint32_t root_cluster; /* 2 */ + uint16_t fs_info; /* sector 1 */ + uint16_t backup_boot_sector; /* sector 6 */ + uint8_t reserved[12]; + uint8_t drive_number; /* 0x80 for fixed disk */ + uint8_t reserved1; + uint8_t boot_sig; /* 0x29 */ + uint32_t volume_id; + char volume_label[11]; /* space-padded */ + char fs_type[8]; /* "FAT32 " */ + uint8_t boot_code[420]; + uint16_t boot_signature; /* 0xAA55 */ +} FAT32_BPB; + +typedef struct __attribute__((packed)) { + uint32_t lead_sig; /* 0x41615252 */ + uint8_t reserved1[480]; + uint32_t struct_sig; /* 0x61417272 */ + uint32_t free_count; /* 0xFFFFFFFF = unknown */ + uint32_t next_free; /* 0xFFFFFFFF = unknown */ + uint8_t reserved2[12]; + uint32_t trail_sig; /* 0xAA550000 */ +} FAT32_FSInfo; + +// Public API + +int mkfs_fat32_format(Disk *disk, uint32_t sector_count, const char *label) { + if (sector_count < MIN_FAT32_SECTORS) { + serial_write("[MKFS] Error: partition too small for FAT32 (< 32 MB)\n"); + return -1; + } + if (!disk || !disk->write_sector) { + serial_write("[MKFS] Error: null disk or no write function\n"); + return -1; + } + + uint8_t spc; /* sectors per cluster */ + if (sector_count < 532480) spc = 1; /* < 260 MB -> 512 B clusters */ + else if (sector_count < 1064960) spc = 2; /* < 520 MB -> 1 KB */ + else if (sector_count < 2097152) spc = 4; /* < 1 GB -> 2 KB */ + else if (sector_count < 16777216) spc = 8; /* < 8 GB -> 4 KB */ + else if (sector_count < 33554432) spc = 16; /* < 16 GB -> 8 KB */ + else if (sector_count < 67108864) spc = 32; /* < 32 GB -> 16 KB */ + else spc = 64; /* >= 32 GB -> 32 KB */ + + const uint32_t reserved_sectors = 32; + const uint8_t num_fats = 2; + const uint32_t root_cluster = 2; + + uint32_t data_sectors = sector_count - reserved_sectors; + uint32_t cluster_count = data_sectors / spc; + uint32_t fat_bytes = (cluster_count + 2) * 4; + uint32_t sectors_per_fat = (fat_bytes + 511) / 512; + + uint8_t *buf = (uint8_t *)kmalloc(512); + if (!buf) { + serial_write("[MKFS] Error: out of memory\n"); + return -1; + } + + FAT32_BPB *bpb = (FAT32_BPB *)buf; + mf_memset(bpb, 0, 512); + + bpb->jump_boot[0] = 0xEB; + bpb->jump_boot[1] = 0x58; + bpb->jump_boot[2] = 0x90; + mf_memcpy(bpb->oem_name, "MSDOS5.0", 8); + bpb->bytes_per_sector = 512; + bpb->sectors_per_cluster = spc; + bpb->reserved_sector_count = (uint16_t)reserved_sectors; + bpb->num_fats = num_fats; + bpb->root_entry_count = 0; + bpb->total_sectors_16 = 0; + bpb->media = 0xF8; + bpb->fat_size_16 = 0; + bpb->sectors_per_track = 63; + bpb->num_heads = 255; + bpb->hidden_sectors = disk->partition_lba_offset; + bpb->total_sectors_32 = sector_count; + bpb->fat_size_32 = sectors_per_fat; + bpb->ext_flags = 0; + bpb->fs_version = 0; + bpb->root_cluster = root_cluster; + bpb->fs_info = 1; + bpb->backup_boot_sector = 6; + bpb->drive_number = 0x80; + bpb->boot_sig = 0x29; + bpb->volume_id = 0x12345678; /* arbitrary non-zero volume ID */ + + /* Volume label */ + const char *vol_label = (label && label[0]) ? label : "NO NAME "; + char upper_label[11]; + for (int i = 0; i < 11; i++) { + if (vol_label[i] == 0) { + for (int j = i; j < 11; j++) upper_label[j] = ' '; + break; + } + char c = vol_label[i]; + if (c >= 'a' && c <= 'z') c -= 32; + upper_label[i] = c; + } + mf_memcpy(bpb->volume_label, upper_label, 11); + mf_memcpy(bpb->fs_type, "FAT32 ", 8); + + /* Boot sector signature */ + bpb->boot_signature = 0xAA55; + + /* Write sector 0 (BPB) */ + if (disk->write_sector(disk, 0, buf) != 0) { + serial_write("[MKFS] Error: failed to write BPB (sector 0)\n"); + kfree(buf); + return -1; + } + + FAT32_FSInfo *fsinfo = (FAT32_FSInfo *)buf; + mf_memset(fsinfo, 0, 512); + fsinfo->lead_sig = 0x41615252; + fsinfo->struct_sig = 0x61417272; + fsinfo->free_count = 0xFFFFFFFF; + fsinfo->next_free = 0xFFFFFFFF; + fsinfo->trail_sig = 0xAA550000; + + if (disk->write_sector(disk, 1, buf) != 0) { + serial_write("[MKFS] Error: failed to write FSInfo (sector 1)\n"); + kfree(buf); + return -1; + } + FAT32_BPB *bpb2 = (FAT32_BPB *)buf; + mf_memset(bpb2, 0, 512); + mf_memcpy(bpb2->jump_boot, "\xEB\x58\x90", 3); + mf_memcpy(bpb2->oem_name, "MSDOS5.0", 8); + bpb2->bytes_per_sector = 512; + bpb2->sectors_per_cluster = spc; + bpb2->reserved_sector_count = (uint16_t)reserved_sectors; + bpb2->num_fats = num_fats; + bpb2->root_entry_count = 0; + bpb2->total_sectors_16 = 0; + bpb2->media = 0xF8; + bpb2->fat_size_16 = 0; + bpb2->sectors_per_track = 63; + bpb2->num_heads = 255; + bpb2->hidden_sectors = disk->partition_lba_offset; + bpb2->total_sectors_32 = sector_count; + bpb2->fat_size_32 = sectors_per_fat; + bpb2->ext_flags = 0; + bpb2->fs_version = 0; + bpb2->root_cluster = root_cluster; + bpb2->fs_info = 1; + bpb2->backup_boot_sector = 6; + bpb2->drive_number = 0x80; + bpb2->boot_sig = 0x29; + bpb2->volume_id = 0x12345678; + mf_memcpy(bpb2->volume_label, upper_label, 11); + mf_memcpy(bpb2->fs_type, "FAT32 ", 8); + bpb2->boot_signature = 0xAA55; + + if (disk->write_sector(disk, 6, buf) != 0) { + serial_write("[MKFS] Error: failed to write backup BPB (sector 6)\n"); + kfree(buf); + return -1; + } + + FAT32_FSInfo *fsinfo2 = (FAT32_FSInfo *)buf; + mf_memset(fsinfo2, 0, 512); + fsinfo2->lead_sig = 0x41615252; + fsinfo2->struct_sig = 0x61417272; + fsinfo2->free_count = 0xFFFFFFFF; + fsinfo2->next_free = 0xFFFFFFFF; + fsinfo2->trail_sig = 0xAA550000; + + if (disk->write_sector(disk, 7, buf) != 0) { + serial_write("[MKFS] Error: failed to write backup FSInfo (sector 7)\n"); + kfree(buf); + return -1; + } + + /* Zero both FATs */ + mf_memset(buf, 0, 512); + for (uint32_t f = 0; f < num_fats; f++) { + uint32_t fat_start = reserved_sectors + (f * sectors_per_fat); + for (uint32_t s = 0; s < sectors_per_fat; s++) { + if (disk->write_sector(disk, fat_start + s, buf) != 0) { + serial_write("[MKFS] Error: failed to zero FAT\n"); + kfree(buf); + return -1; + } + } + } + + /* Write markers to both FATs */ + mf_memset(buf, 0, 512); + uint32_t *fat_buf = (uint32_t *)buf; + fat_buf[0] = 0x0FFFFFF8; // Media type + fat_buf[1] = 0x0FFFFFFF; // Reserved + fat_buf[2] = 0x0FFFFFFF; // Root directory (Cluster 2) + + for (uint32_t f = 0; f < num_fats; f++) { + uint32_t fat_start = reserved_sectors + (f * sectors_per_fat); + if (disk->write_sector(disk, fat_start, buf) != 0) { + serial_write("[MKFS] Error: failed to write FAT markers\n"); + kfree(buf); + return -1; + } + } + + /* Zero root cluster */ + mf_memset(buf, 0, 512); + uint32_t root_start = reserved_sectors + num_fats * sectors_per_fat; + for (uint32_t s = 0; s < (uint32_t)spc; s++) { + if (disk->write_sector(disk, root_start + s, buf) != 0) { + serial_write("[MKFS] Error: failed to zero root cluster\n"); + kfree(buf); + return -1; + } + } + + kfree(buf); + + serial_write("[MKFS] FAT32 formatted: "); + serial_write(disk->devname); + serial_write(" label="); + char lb[12]; + mf_memcpy(lb, vol_label, 11); + lb[11] = 0; + for (int i = 0; i < 11; i++) lb[i] = (lb[i] >= 'a' && lb[i] <= 'z') ? lb[i] - 32 : lb[i]; + for (int i = 10; i >= 0 && lb[i] == ' '; i--) lb[i] = 0; + serial_write(lb); + serial_write(" spc="); + serial_write_num(spc); + serial_write(" fat_sectors="); + serial_write_num(sectors_per_fat); + serial_write("\n"); + + return 0; +} diff --git a/src/fs/mkfs_fat32.h b/src/fs/mkfs_fat32.h new file mode 100644 index 0000000..09c563d --- /dev/null +++ b/src/fs/mkfs_fat32.h @@ -0,0 +1,12 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +#ifndef MKFS_FAT32_H +#define MKFS_FAT32_H + +#include +#include "../dev/disk.h" +#define MIN_FAT32_SECTORS 65536 +int mkfs_fat32_format(Disk *disk, uint32_t sector_count, const char *label); + +#endif diff --git a/src/userland/cli/boredos_install.c b/src/userland/cli/boredos_install.c new file mode 100644 index 0000000..f7af24b --- /dev/null +++ b/src/userland/cli/boredos_install.c @@ -0,0 +1,459 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +#include +#include "../libc/syscall.h" +#include "../libc/stdlib.h" +#include "../libc/string.h" +#include "../libc/stdio.h" + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +static int sc_strncpy(char *dst, const char *src, int n) { + int i = 0; + while (i < n - 1 && src[i]) { dst[i] = src[i]; i++; } + dst[i] = 0; + return i; +} + +static void read_line_blocking(char *buf, int max_len) { + int len = 0; + while (1) { + char ch; + if (sys_tty_read_in(&ch, 1) <= 0) { + sleep(10); + continue; + } + if (ch == '\r' || ch == '\n') { + sys_write(1, "\n", 1); + break; + } + if (ch == '\b' || ch == 127) { + if (len > 0) { + len--; + sys_write(1, "\b \b", 3); + } + continue; + } + if (len < max_len - 1) { + buf[len++] = ch; + sys_write(1, &ch, 1); + } + } + buf[len] = '\0'; +} + +static int copy_file(const char *src, const char *dst) { + printf(" [FILE] %s\n", dst); + int sfd = sys_open(src, "r"); + if (sfd < 0) { printf("[ERROR] Cannot open: %s\n", src); return -1; } + sys_delete(dst); + int dfd = sys_open(dst, "w"); + if (dfd < 0) { sys_close(sfd); printf("[ERROR] Cannot create: %s\n", dst); return -1; } + + char *buf = (char*)malloc(65536); + if (!buf) { sys_close(sfd); sys_close(dfd); printf("[ERROR] Out of memory copying: %s\n", dst); return -1; } + int n; + while ((n = sys_read(sfd, buf, 65536)) > 0) { + if (sys_write_fs(dfd, buf, n) != (uint32_t)n) { + sys_close(sfd); sys_close(dfd); + free(buf); + printf("[ERROR] Write error: %s\n", dst); + return -1; + } + } + free(buf); + sys_close(sfd); + sys_close(dfd); + return 0; +} + +static int copy_file_optional(const char *src, const char *dst) { + if (!sys_exists(src)) return 0; + return copy_file(src, dst); +} + +static int copy_tree(const char *src_dir, const char *dst_dir) { + printf(" [DIR ] %s\n", dst_dir); + if (sys_mkdir(dst_dir) != 0) { + } + FAT32_FileInfo entries[64]; + int n = sys_list(src_dir, entries, 64); + for (int i = 0; i < n; i++) { + if (entries[i].name[0] == '.' && entries[i].name[1] == '_') continue; + + char src_path[512], dst_path[512]; + int sl = 0, dl = 0; + for (; src_dir[sl]; sl++) src_path[sl] = src_dir[sl]; + src_path[sl++] = '/'; + for (int j = 0; entries[i].name[j]; j++) src_path[sl++] = entries[i].name[j]; + src_path[sl] = 0; + + for (; dst_dir[dl]; dl++) dst_path[dl] = dst_dir[dl]; + dst_path[dl++] = '/'; + for (int j = 0; entries[i].name[j]; j++) dst_path[dl++] = entries[i].name[j]; + dst_path[dl] = 0; + + if (entries[i].is_directory) { + if (copy_tree(src_path, dst_path) != 0) return -1; + } else { + if (copy_file(src_path, dst_path) != 0) return -1; + } + } + return 0; +} + +static void print_usage(void) { + printf("boredos_install [OPTIONS] /dev/DEVICE\n"); + printf(" --uefi UEFI (GPT + ESP) [default]\n"); + printf(" --bios BIOS/MBR\n"); + printf(" --no-partition Skip fdisk\n"); + printf(" --no-format Skip mkfs\n"); + printf(" --no-files Skip file copy\n"); + printf(" --no-bootloader Skip bootloader files\n"); + printf(" --esp-size N ESP MB (default: 512)\n"); + printf(" --esp-dev DEV Explicit ESP device\n"); + printf(" --root-dev DEV Explicit root device\n"); + printf(" -y, --yes Auto-accept warning\n"); + printf(" -v, --verbose\n"); + printf(" -h, --help\n"); +} + +static uint64_t get_ticks(void) { + return (uint64_t)sys_system(16 /* SYSTEM_CMD_GET_TICKS */, 0, 0, 0, 0); +} + +#define SYS_SERIAL_WRITE 8 + +static void serial_write_user(const char *str) { + __asm__ volatile("mov $8, %%rax; mov %0, %%rdi; syscall" : : "r"(str) : "rax", "rdi"); +} + +static void serial_printf(const char *fmt, ...) { + char buf[512]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + serial_write_user(buf); +} + +int main(int argc, char **argv) { + int is_uefi = 1; + int do_partition = 1; + int do_format = 1; + int do_files = 1; + int do_bootloader = 1; + int opt_yes = 0; + int esp_size_mb = 512; + const char *devname = NULL; + const char *esp_dev_arg = NULL; + const char *root_dev_arg = NULL; + + for (int i = 1; i < argc; i++) { + if (sc_strcmp(argv[i], "--uefi") == 0) is_uefi = 1; + else if (sc_strcmp(argv[i], "--bios") == 0) is_uefi = 0; + else if (sc_strcmp(argv[i], "--no-partition") == 0) do_partition = 0; + else if (sc_strcmp(argv[i], "--no-format") == 0) do_format = 0; + else if (sc_strcmp(argv[i], "--no-files") == 0) do_files = 0; + else if (sc_strcmp(argv[i], "--no-bootloader") == 0) do_bootloader = 0; + else if ((sc_strcmp(argv[i], "-y") == 0 || sc_strcmp(argv[i], "--yes") == 0)) opt_yes = 1; + else if (sc_strcmp(argv[i], "--esp-size") == 0 && i + 1 < argc) { + esp_size_mb = 0; + const char *s = argv[++i]; + while (*s >= '0' && *s <= '9') esp_size_mb = esp_size_mb * 10 + (*s++ - '0'); + } else if (sc_strcmp(argv[i], "--esp-dev") == 0 && i + 1 < argc) { + esp_dev_arg = argv[++i]; + if (esp_dev_arg[0]=='/' && esp_dev_arg[1]=='d' && esp_dev_arg[2]=='e' && esp_dev_arg[3]=='v' && esp_dev_arg[4]=='/') + esp_dev_arg += 5; + } else if (sc_strcmp(argv[i], "--root-dev") == 0 && i + 1 < argc) { + root_dev_arg = argv[++i]; + if (root_dev_arg[0]=='/' && root_dev_arg[1]=='d' && root_dev_arg[2]=='e' && root_dev_arg[3]=='v' && root_dev_arg[4]=='/') + root_dev_arg += 5; + } else if (sc_strcmp(argv[i], "-h") == 0 || sc_strcmp(argv[i], "--help") == 0) { + print_usage(); return 0; + } else if (argv[i][0] != '-') { + devname = argv[i]; + if (devname[0]=='/' && devname[1]=='d' && devname[2]=='e' && devname[3]=='v' && devname[4]=='/') + devname += 5; + } + } + + if (!devname) { print_usage(); return 1; } + + /* --- Step 0: Prerequisites --- */ + if (!sys_exists("/boot/boredos.elf")) { + printf("[ERROR] /boot/boredos.elf not found. Cannot install.\n"); + return 1; + } + if (is_uefi && !sys_exists("/boot/BOOTX64.EFI")) { + printf("[ERROR] /boot/BOOTX64.EFI not found. Cannot install UEFI.\n"); + return 1; + } + + disk_info_t disk_info; + { + int found = 0; + int n = sys_disk_get_count(); + for (int i = 0; i < n; i++) { + if (sys_disk_get_info(i, &disk_info) != 0) continue; + if (!disk_info.is_partition && sc_strcmp(disk_info.devname, devname) == 0) { found = 1; break; } + } + if (!found) { printf("[ERROR] Device not found: /dev/%s\n", devname); return 1; } + } + + if (disk_info.total_sectors < MIN_INSTALL_SECTORS) { + printf("[ERROR] Disk too small: %u sectors. Minimum is 1 GB (%u sectors).\n", + disk_info.total_sectors, MIN_INSTALL_SECTORS); + return 1; + } + + if (do_partition || do_format) { + uint32_t mb = disk_info.total_sectors / 2048; + printf("╔══════════════════════════════════════════════════════╗\n"); + printf("║ WARNING: ALL DATA on /dev/%s will be ERASED. ║\n", devname); + printf("║ Disk: %u sectors (%u MB) ║\n", disk_info.total_sectors, mb); + printf("║ This is IRREVERSIBLE. ║\n"); + printf("╚══════════════════════════════════════════════════════╝\n"); + if (opt_yes) { + printf("Auto-accepted (--yes).\n"); + } else { + printf("Proceed with installation? (y/n): "); + char resp[8]; + read_line_blocking(resp, 8); + if (resp[0] != 'y' && resp[0] != 'Y') { + printf("Aborted.\n"); + return 0; + } + } + } + + sys_mkdir("/mnt"); + sys_mkdir("/mnt/boot"); + sys_mkdir("/mnt/esp"); + + if (do_partition) { + char fdisk_args[128]; + int ai = 0; + const char *cmd = "--script "; + for (; *cmd; cmd++) fdisk_args[ai++] = *cmd; + if (is_uefi) { + const char *u = "--uefi --esp-size "; + for (; *u; u++) fdisk_args[ai++] = *u; + char num[16]; int ni = 0; + int v = esp_size_mb; + if (v == 0) { num[ni++] = '0'; } else { + char tmp[16]; int ti = 0; + while (v > 0) { tmp[ti++] = '0' + (v % 10); v /= 10; } + for (int j = ti - 1; j >= 0; j--) num[ni++] = tmp[j]; + } + for (int j = 0; j < ni; j++) fdisk_args[ai++] = num[j]; + fdisk_args[ai++] = ' '; + } else { + const char *b = "--mbr "; for (; *b; b++) fdisk_args[ai++] = *b; + } + fdisk_args[ai++] = '/'; fdisk_args[ai++] = 'd'; fdisk_args[ai++] = 'e'; + fdisk_args[ai++] = 'v'; fdisk_args[ai++] = '/'; + for (int j = 0; devname[j]; j++) fdisk_args[ai++] = devname[j]; + fdisk_args[ai] = 0; + + int pid = sys_spawn("/bin/fdisk.elf", fdisk_args, SPAWN_FLAG_INHERIT_TTY, 0); + if (pid < 0) { printf("[ERROR] Failed to run fdisk.\n"); return 1; } + int status = 0; + sys_waitpid(pid, &status, 0); + if (status != 0) { printf("[ERROR] fdisk failed.\n"); return 1; } + + sys_disk_rescan(devname); + } + + char esp_dev[16] = {0}; + char root_dev[16] = {0}; + + if (esp_dev_arg) sc_strncpy(esp_dev, esp_dev_arg, 16); + if (root_dev_arg) sc_strncpy(root_dev, root_dev_arg, 16); + + if (!esp_dev[0] || !root_dev[0]) { + int n = sys_disk_get_count(); + for (int i = 0; i < n; i++) { + disk_info_t d; + if (sys_disk_get_info(i, &d) != 0) continue; + if (!d.is_partition) continue; + int match = 1; + for (int j = 0; devname[j]; j++) { + if (d.devname[j] != devname[j]) { match = 0; break; } + } + if (!match) continue; + if (is_uefi && d.is_esp && !esp_dev[0]) + sc_strncpy(esp_dev, d.devname, 16); + else if (!d.is_esp && !root_dev[0]) { + if (d.is_fat32 || do_format) + sc_strncpy(root_dev, d.devname, 16); + } + } + } + + if (!root_dev[0]) { printf("[ERROR] Could not find root partition.\n"); return 1; } + if (is_uefi && !esp_dev[0]) { printf("[ERROR] Could not find ESP.\n"); return 1; } + + if (do_format) { + if (is_uefi) { + if (sys_disk_mkfs_fat32(esp_dev, "EFI") != 0) { + printf("[ERROR] Failed to format ESP.\n"); return 1; + } + } + if (sys_disk_mkfs_fat32(root_dev, "BOREDOS") != 0) { + printf("[ERROR] Failed to format root partition.\n"); return 1; + } + } + + if (sys_disk_mount(root_dev, "/mnt") != 0) { + printf("[ERROR] Cannot mount /dev/%s to /mnt\n", root_dev); + return -1; + } + + if (is_uefi) { + sys_mkdir("/mnt/boot"); + if (sys_disk_mount(esp_dev, "/mnt/boot") != 0) { + printf("[ERROR] Cannot mount /dev/%s to /mnt/boot\n", esp_dev); + return -1; + } + } else { + // In BIOS mode, /boot is just a directory on the root partition + sys_mkdir("/mnt/boot"); + } + + if (sys_mkdir("/mnt/Library") == 0 || sys_exists("/mnt/Library")) { + int mfd = sys_open("/mnt/Library/.boredos_root", "w"); + if (mfd >= 0) sys_close(mfd); + } + + if (do_files) { + serial_printf("Copying system files...\n"); + uint64_t t0 = get_ticks(); + if (copy_tree("/bin", "/mnt/bin") != 0) { serial_printf("[ERROR] copy /bin failed\n"); return 1; } + if (copy_tree("/Library", "/mnt/Library") != 0) { serial_printf("[ERROR] copy /Library failed\n"); return 1; } + if (copy_tree("/docs", "/mnt/docs") != 0) { serial_printf("[ERROR] copy /docs failed\n"); return 1; } + if (copy_tree("/root", "/mnt/root") != 0) { serial_printf("[ERROR] copy /root failed\n"); return 1; } + + serial_printf("Copying kernel and initrd...\n"); + if (copy_file("/boot/boredos.elf", "/mnt/boot/boredos.elf") != 0) { + serial_printf("[ERROR] Failed to copy kernel to ESP\n"); + return 1; + } + if (sys_exists("/boot/initrd.tar")) { + if (copy_file("/boot/initrd.tar", "/mnt/boot/initrd.tar") != 0) { + printf("[ERROR] Failed to copy /boot/initrd.tar to ESP\n"); + return 1; + } + } else if (sys_exists("/initrd.tar")) { + if (copy_file("/initrd.tar", "/mnt/boot/initrd.tar") != 0) { + serial_printf("[ERROR] Failed to copy /initrd.tar to ESP\n"); + return 1; + } + } else { + serial_printf("[WARNING] initrd.tar not found in live system (checked /boot/ and /)!\n"); + } + + copy_file_optional("README.md", "/mnt/README.md"); + copy_file_optional("LICENSE", "/mnt/LICENSE"); + uint64_t t1 = get_ticks(); + serial_printf("Files copied (%llu ticks).\n", (unsigned long long)(t1 - t0)); + } + + + if (do_bootloader) { + if (is_uefi) { + if (sys_mkdir("/mnt/boot/EFI") != 0 && !sys_exists("/mnt/boot/EFI")) { + printf("[ERROR] Failed to create /mnt/boot/EFI\n"); + return 1; + } + if (sys_mkdir("/mnt/boot/EFI/BOOT") != 0 && !sys_exists("/mnt/boot/EFI/BOOT")) { + printf("[ERROR] Failed to create /mnt/boot/EFI/BOOT\n"); + return 1; + } + if (copy_file("/boot/BOOTX64.EFI", "/mnt/boot/EFI/BOOT/BOOTX64.EFI") != 0) { + printf("[ERROR] Failed to copy BOOTX64.EFI\n"); + return 1; + } + copy_file_optional("/boot/BOOTIA32.EFI", "/mnt/boot/EFI/BOOT/BOOTIA32.EFI"); + + int fd = sys_open("/mnt/boot/limine.conf", "w"); + if (fd >= 0) { + char cfg[512]; + int len = snprintf(cfg, sizeof(cfg), + "timeout: 3\n" + "verbose: yes\n" + "\n" + "/BoredOS\n" + " protocol: limine\n" + " path: boot():/boredos.elf\n" + " cmdline: -v root=/dev/%s --disk --accept-tos\n" + " module_path: boot():/initrd.tar\n", + root_dev); + if (len > 0) sys_write_fs(fd, cfg, len); + sys_close(fd); + } + } else { + copy_file_optional("/boot/limine-bios.sys", "/mnt/limine-bios.sys"); + + int fd = sys_open("/mnt/limine.conf", "w"); + if (fd >= 0) { + char cfg[512]; + int len = snprintf(cfg, sizeof(cfg), + "timeout: 3\n" + "verbose: yes\n" + "\n" + "/BoredOS\n" + " protocol: limine\n" + " root: boot()\n" + " path: /boredos.elf\n" + " cmdline: -v root=/dev/%s --disk --accept-tos\n" + " module_path: /initrd.tar\n", + root_dev); + if (len > 0) sys_write_fs(fd, cfg, len); + sys_close(fd); + } + + printf("[NOTICE] BIOS bootstrap requires host-side: limine bios-install /dev/%s\n", devname); + printf(" limine-bios.sys has been copied to the root of the target partition.\n"); + } + } + + if (is_uefi) { + printf("Verifying BOOTX64.EFI...\n"); + int vfd = sys_open("/mnt/boot/EFI/BOOT/BOOTX64.EFI", "r"); + if (vfd >= 0) { + char magic[2]; + if (sys_read(vfd, magic, 2) == 2) { + if (magic[0] == 'M' && magic[1] == 'Z') { + printf("[OK] BOOTX64.EFI verification successful (MZ found)\n"); + } else { + printf("[ERROR] BOOTX64.EFI verification failed: Invalid magic bytes %02x %02x\n", magic[0], magic[1]); + } + } else { + printf("[ERROR] BOOTX64.EFI verification failed: Could not read 2 bytes\n"); + } + sys_close(vfd); + } else { + printf("[ERROR] BOOTX64.EFI verification failed: Could not open file for verification\n"); + } + sys_disk_sync("/mnt/boot"); + sys_disk_umount("/mnt/boot"); + } + sys_disk_sync("/mnt"); + sys_disk_umount("/mnt"); + + // Sync physical disk + sys_disk_sync(devname); + + + printf("\nInstallation complete.\n"); + printf(" Root partition: /dev/%s\n", root_dev); + if (is_uefi) printf(" ESP: /dev/%s\n", esp_dev); + printf(" Mode: %s\n", is_uefi ? "UEFI" : "BIOS"); + printf("Reboot and select the target disk to boot BoredOS.\n"); + return 0; +} diff --git a/src/userland/cli/fdisk.c b/src/userland/cli/fdisk.c new file mode 100644 index 0000000..1ae4fd4 --- /dev/null +++ b/src/userland/cli/fdisk.c @@ -0,0 +1,366 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +#include "../libc/syscall.h" +#include "../libc/stdlib.h" +#include "../libc/string.h" +#include "../libc/stdio.h" + +#define MAX_PARTS 4 +#define SECTOR_SIZE_BYTES 512ULL +#define ONE_MB (1024ULL * 1024ULL) +#define ONE_GB (1024ULL * 1024ULL * 1024ULL) + +static void print_usage(void) { + printf("fdisk [OPTIONS] /dev/DEVICE\n"); + printf(" -p, --print Print partition table and exit\n"); + printf(" -s, --script Non-interactive auto-partition\n"); + printf(" --mbr Use MBR instead of GPT\n"); + printf(" --uefi Include ESP (default with GPT)\n"); + printf(" --esp-size N ESP size (b/mb/gb, default: 512mb)\n"); + printf(" -h, --help\n"); +} + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +static int sc_atoi(const char *s) { + int n = 0; + while (*s >= '0' && *s <= '9') n = n * 10 + (*s++ - '0'); + return n; +} + +static char sc_tolower(char ch) { + if (ch >= 'A' && ch <= 'Z') return (char)(ch + 32); + return ch; +} + +static uint64_t sc_parse_size_bytes(const char *s) { + uint64_t n = 0; + int has_digit = 0; + + while (*s == ' ' || *s == '\t') s++; + if (*s == '+') s++; + + while (*s >= '0' && *s <= '9') { + has_digit = 1; + n = n * 10ULL + (uint64_t)(*s - '0'); + s++; + } + + if (!has_digit) return 0; + + while (*s == ' ' || *s == '\t') s++; + + if (*s == '\0') return n * ONE_MB; + + { + char c1 = sc_tolower(*s); + char c2 = sc_tolower(*(s + 1)); + + if (c1 == 'g' && c2 == 'b') return n * ONE_GB; + if (c1 == 'm' && c2 == 'b') return n * ONE_MB; + if (c1 == 'b') return n; + if (c1 == 'g' && c2 == '\0') return n * ONE_GB; + if (c1 == 'm' && c2 == '\0') return n * ONE_MB; + } + + return n * ONE_MB; +} + +static uint32_t sc_bytes_to_sectors_ceil(uint64_t bytes) { + if (bytes == 0) return 0; + return (uint32_t)((bytes + SECTOR_SIZE_BYTES - 1ULL) / SECTOR_SIZE_BYTES); +} + +static void sc_format_size(uint64_t bytes, char *out, size_t out_len) { + if (bytes >= ONE_GB) { + unsigned long long whole = (unsigned long long)(bytes / ONE_GB); + uint64_t rem = bytes % ONE_GB; + unsigned long long frac = (unsigned long long)((rem * 100ULL + (ONE_GB / 2)) / ONE_GB); + if (frac >= 100ULL) { whole++; frac = 0; } + snprintf(out, out_len, "%llu.%02llugb", whole, frac); + } else if (bytes >= ONE_MB) { + unsigned long long whole = (unsigned long long)(bytes / ONE_MB); + uint64_t rem = bytes % ONE_MB; + unsigned long long frac = (unsigned long long)((rem * 100ULL + (ONE_MB / 2)) / ONE_MB); + if (frac >= 100ULL) { whole++; frac = 0; } + snprintf(out, out_len, "%llu.%02llumb", whole, frac); + } else { + snprintf(out, out_len, "%llub", (unsigned long long)bytes); + } +} + +static void read_line_blocking(char *buf, int max_len) { + int len = 0; + while (1) { + char ch; + if (sys_tty_read_in(&ch, 1) <= 0) { + sleep(10); + continue; + } + if (ch == '\r' || ch == '\n') { + sys_write(1, "\n", 1); + break; + } + if (ch == '\b' || ch == 127) { + if (len > 0) { + len--; + sys_write(1, "\b \b", 3); + } + continue; + } + if (len < max_len - 1) { + buf[len++] = ch; + sys_write(1, &ch, 1); + } + } + buf[len] = '\0'; +} + +static void print_partition_table(const char *devname) { + int n = sys_disk_get_count(); + int found = 0; + printf("Partition table for /dev/%s:\n", devname); + printf("%-10s %-12s %-12s %-10s %-6s %s\n", + "Device", "Start", "End", "Size", "ESP", "FAT32"); + for (int i = 0; i < n; i++) { + disk_info_t d; + if (sys_disk_get_info(i, &d) != 0) continue; + if (!d.is_partition) continue; + char parent[16]; + int len = 0; + while (devname[len]) len++; + int match = 1; + for (int j = 0; j < len; j++) { + if (d.devname[j] != devname[j]) { match = 0; break; } + } + if (!match) continue; + { + char start_buf[24]; + char end_buf[24]; + char size_buf[24]; + uint64_t start_bytes = (uint64_t)d.lba_offset * SECTOR_SIZE_BYTES; + uint64_t end_bytes = (uint64_t)(d.lba_offset + d.total_sectors - 1) * SECTOR_SIZE_BYTES; + uint64_t size_bytes = (uint64_t)d.total_sectors * SECTOR_SIZE_BYTES; + sc_format_size(start_bytes, start_buf, sizeof(start_buf)); + sc_format_size(end_bytes, end_buf, sizeof(end_buf)); + sc_format_size(size_bytes, size_buf, sizeof(size_buf)); + printf("/dev/%-5s %-12s %-12s %-10s %-6s %s\n", + d.devname, + start_buf, + end_buf, + size_buf, + d.is_esp ? "yes" : "no", + d.is_fat32 ? "yes" : "no"); + } + found++; + } + if (!found) printf(" (no partitions)\n"); +} + +int main(int argc, char **argv) { + int opt_print = 0; + int opt_script = 0; + int opt_mbr = 0; + int opt_uefi = 1; + uint64_t esp_size_bytes = 512ULL * ONE_MB; + const char *devname = NULL; + + for (int i = 1; i < argc; i++) { + if (sc_strcmp(argv[i], "-p") == 0 || sc_strcmp(argv[i], "--print") == 0) + opt_print = 1; + else if (sc_strcmp(argv[i], "-s") == 0 || sc_strcmp(argv[i], "--script") == 0) + opt_script = 1; + else if (sc_strcmp(argv[i], "--mbr") == 0) { opt_mbr = 1; opt_uefi = 0; } + else if (sc_strcmp(argv[i], "--uefi") == 0) opt_uefi = 1; + else if (sc_strcmp(argv[i], "--esp-size") == 0 && i + 1 < argc) { + uint64_t parsed = sc_parse_size_bytes(argv[++i]); + if (parsed > 0) esp_size_bytes = parsed; + } + else if (sc_strcmp(argv[i], "-h") == 0 || sc_strcmp(argv[i], "--help") == 0) { + print_usage(); + return 0; + } else if (argv[i][0] != '-') { + devname = argv[i]; + if (devname[0]=='/' && devname[1]=='d' && devname[2]=='e' && devname[3]=='v' && devname[4]=='/') + devname += 5; + } + } + + if (!devname) { print_usage(); return 1; } + + if (opt_print) { + print_partition_table(devname); + return 0; + } + + if (!opt_script) { + disk_info_t disk; + int found = -1; + int n = sys_disk_get_count(); + for (int i = 0; i < n; i++) { + disk_info_t d; + if (sys_disk_get_info(i, &d) != 0) continue; + if (!d.is_partition && sc_strcmp(d.devname, devname) == 0) { + disk = d; + found = i; + break; + } + } + if (found < 0) { printf("[ERROR] Device not found: /dev/%s\n", devname); return 1; } + + { + char disk_size[24]; + sc_format_size((uint64_t)disk.total_sectors * SECTOR_SIZE_BYTES, disk_size, sizeof(disk_size)); + printf("fdisk /dev/%s (%s)\n", devname, disk_size); + } + printf("Commands: p (print), n (new), d NUM (delete), w (write), q (quit)\n"); + printf("Sizes accept b/mb/gb (default unit: mb).\n"); + + partition_spec_t parts[MAX_PARTS]; + int part_count = 0; + char line[128]; + + while (1) { + printf("fdisk> "); + read_line_blocking(line, 128); + int len = 0; + while(line[len]) len++; + if (len == 0) continue; + + if (line[0] == 'q') break; + if (line[0] == 'p') { + for (int i = 0; i < part_count; i++) { + char start_buf[24]; + char size_buf[24]; + sc_format_size((uint64_t)parts[i].lba_start * SECTOR_SIZE_BYTES, + start_buf, sizeof(start_buf)); + sc_format_size((uint64_t)parts[i].sector_count * SECTOR_SIZE_BYTES, + size_buf, sizeof(size_buf)); + printf(" %d: start=%s size=%s esp=%s\n", i + 1, + start_buf, size_buf, + (parts[i].flags & PART_FLAG_ESP) ? "yes" : "no"); + } + continue; + } + if (line[0] == 'n') { + if (part_count >= MAX_PARTS) { printf("Max partitions reached\n"); continue; } + uint32_t start = 2048; + if (part_count > 0) + start = parts[part_count-1].lba_start + parts[part_count-1].sector_count; + if (start % 2048) start = ((start + 2047) / 2048) * 2048; + { + char start_buf[24]; + sc_format_size((uint64_t)start * SECTOR_SIZE_BYTES, start_buf, sizeof(start_buf)); + printf("First offset [%s]: ", start_buf); + } + read_line_blocking(line, 64); + len = 0; while(line[len]) len++; + if (len > 0) start = sc_bytes_to_sectors_ceil(sc_parse_size_bytes(line)); + if (start % 2048) start = ((start + 2047) / 2048) * 2048; + + uint32_t last_usable = opt_mbr ? (disk.total_sectors - 1) : (disk.total_sectors - 34); + uint32_t remaining = (last_usable >= start) ? (last_usable - start + 1) : 0; + { + char rem_buf[24]; + sc_format_size((uint64_t)remaining * SECTOR_SIZE_BYTES, rem_buf, sizeof(rem_buf)); + printf("Size [%s]: ", rem_buf); + } + read_line_blocking(line, 64); + len = 0; while(line[len]) len++; + uint32_t size = remaining; + if (len > 0) size = sc_bytes_to_sectors_ceil(sc_parse_size_bytes(line)); + + parts[part_count].lba_start = start; + parts[part_count].sector_count = size; + parts[part_count].part_type = 0; + parts[part_count].flags = 0; + parts[part_count].label[0] = 0; + part_count++; + printf("Partition %d added\n", part_count); + continue; + } + if (line[0] == 'd') { + int idx = sc_atoi(line + 1) - 1; + if (idx < 0 || idx >= part_count) { printf("Invalid partition\n"); continue; } + for (int i = idx; i < part_count - 1; i++) parts[i] = parts[i+1]; + part_count--; + printf("Partition %d deleted\n", idx + 1); + continue; + } + if (line[0] == 'w') { + int ret; + if (opt_mbr) + ret = sys_disk_write_mbr(devname, parts, part_count); + else + ret = sys_disk_write_gpt(devname, parts, part_count); + if (ret == 0) printf("Partition table written.\n"); + else printf("[ERROR] Failed to write partition table.\n"); + break; + } + } + return 0; + } + + disk_info_t disk; + int found = 0; + { + int n = sys_disk_get_count(); + for (int i = 0; i < n; i++) { + disk_info_t d; + if (sys_disk_get_info(i, &d) != 0) continue; + if (!d.is_partition && sc_strcmp(d.devname, devname) == 0) { + disk = d; + found = 1; + break; + } + } + } + if (!found) { printf("[ERROR] Device not found: /dev/%s\n", devname); return 1; } + + partition_spec_t parts[2]; + int count = 0; + int ret; + + if (!opt_mbr && opt_uefi) { + uint32_t esp_sectors = sc_bytes_to_sectors_ceil(esp_size_bytes); + if (esp_sectors % 2048) esp_sectors = ((esp_sectors + 2047) / 2048) * 2048; + parts[0].lba_start = 2048; + parts[0].sector_count = esp_sectors; + parts[0].part_type = 0; + parts[0].flags = PART_FLAG_ESP; + parts[0].label[0]='E'; parts[0].label[1]='F'; parts[0].label[2]='I'; + parts[0].label[3]=' '; parts[0].label[4]='S'; parts[0].label[5]='y'; + parts[0].label[6]='s'; parts[0].label[7]='t'; parts[0].label[8]='e'; + parts[0].label[9]='m'; parts[0].label[10]=0; + + uint32_t root_start = 2048 + esp_sectors; + if (root_start % 2048) root_start = ((root_start + 2047) / 2048) * 2048; + parts[1].lba_start = root_start; + parts[1].sector_count = disk.total_sectors - root_start - 34; + parts[1].part_type = 0; + parts[1].flags = 0; + parts[1].label[0]='B'; parts[1].label[1]='o'; parts[1].label[2]='r'; + parts[1].label[3]='e'; parts[1].label[4]='d'; parts[1].label[5]='O'; + parts[1].label[6]='S'; parts[1].label[7]=0; + count = 2; + ret = sys_disk_write_gpt(devname, parts, count); + } else { + parts[0].lba_start = 2048; + parts[0].sector_count = disk.total_sectors - 2048; + parts[0].part_type = 0; + parts[0].flags = 0; + parts[0].label[0]='B'; parts[0].label[1]='o'; parts[0].label[2]='r'; + parts[0].label[3]='e'; parts[0].label[4]='d'; parts[0].label[5]='O'; + parts[0].label[6]='S'; parts[0].label[7]=0; + count = 1; + ret = sys_disk_write_mbr(devname, parts, count); + } + + if (ret != 0) { printf("[ERROR] Partition write failed.\n"); return 1; } + printf("Partition table written to /dev/%s.\n", devname); + return 0; +} diff --git a/src/userland/cli/mkfs_fat.c b/src/userland/cli/mkfs_fat.c new file mode 100644 index 0000000..051ee35 --- /dev/null +++ b/src/userland/cli/mkfs_fat.c @@ -0,0 +1,66 @@ +// Copyright (c) 2023-2026 Chris (boreddevnl) +// This software is released under the GNU General Public License v3.0. See LICENSE file for details. +// This header needs to maintain in any file it is present in, as per the GPL license terms. +#include "../libc/syscall.h" +#include "../libc/stdlib.h" +#include "../libc/string.h" +#include "../libc/stdio.h" + +static int sc_strcmp(const char *a, const char *b) { + while (*a && *a == *b) { a++; b++; } + return (unsigned char)*a - (unsigned char)*b; +} + +int main(int argc, char **argv) { + const char *devname = NULL; + const char *label = "BOREDOS"; + int fat_type = 32; + + for (int i = 1; i < argc; i++) { + if (sc_strcmp(argv[i], "-F") == 0 && i + 1 < argc) { + fat_type = 0; + const char *s = argv[++i]; + while (*s >= '0' && *s <= '9') fat_type = fat_type * 10 + (*s++ - '0'); + } else if (sc_strcmp(argv[i], "-n") == 0 && i + 1 < argc) { + label = argv[++i]; + } else if (sc_strcmp(argv[i], "-h") == 0 || sc_strcmp(argv[i], "--help") == 0) { + printf("mkfs.fat [OPTIONS] /dev/DEVICE\n"); + printf(" -F 32 FAT type (32 only)\n"); + printf(" -n LABEL Volume label (max 11 chars, default: BOREDOS)\n"); + return 0; + } else if (argv[i][0] != '-') { + devname = argv[i]; + if (devname[0]=='/' && devname[1]=='d' && devname[2]=='e' && devname[3]=='v' && devname[4]=='/') + devname += 5; + } + } + + if (!devname) { + printf("Usage: mkfs.fat -F 32 [-n LABEL] /dev/DEVICE\n"); + return 1; + } + if (fat_type != 32) { + printf("[ERROR] Only FAT32 (-F 32) is supported.\n"); + return 1; + } + + disk_info_t d; + int found = 0; + int n = sys_disk_get_count(); + for (int i = 0; i < n; i++) { + if (sys_disk_get_info(i, &d) != 0) continue; + if (sc_strcmp(d.devname, devname) == 0) { found = 1; break; } + } + if (!found) { printf("[ERROR] Device not found: /dev/%s\n", devname); return 1; } + if (!d.is_partition) { printf("[ERROR] /dev/%s is a whole disk, not a partition.\n", devname); return 1; } + if (d.total_sectors < 65536) { + printf("[ERROR] Partition too small (< 32 MB) for FAT32.\n"); + return 1; + } + + printf("Formatting /dev/%s as FAT32 (label: %s)...\n", devname, label); + int ret = sys_disk_mkfs_fat32(devname, label); + if (ret != 0) { printf("[ERROR] Format failed.\n"); return 1; } + printf("Done.\n"); + return 0; +}