From 27b20425237a8a8b6cb9db52902c3c682e3568f4 Mon Sep 17 00:00:00 2001 From: AI Date: Mon, 23 Feb 2026 16:33:12 +0000 Subject: [PATCH] Implement mkfs.fat32 filesystem formatter app (AI) Create a user-space utility that formats block devices with FAT32. Writes all required on-disk structures sequentially: - Boot sector (BPB) with BIOS Parameter Block at sector 0 - FSInfo sector at sector 1 with free cluster count/hint - Backup boot sector at sector 6, backup FSInfo at sector 7 - Two FAT tables with entries 0-2 initialized (media marker, EOC markers, root directory cluster EOC) - Root directory cluster with volume label entry Geometry calculation: - Sectors per cluster chosen based on volume size (1-32) - FAT size computed using Microsoft's formula - Supports volumes from 128 sectors (~64K) upward Usage: mkfs.fat32 hdd1mbr1 14336 [LABEL] The total sector count is required since there is no stat() syscall yet. Users can find it via 'diskpart list'. Also checked off diskpart in README (committed previously). --- README.md | 4 +- apps/mkfs.fat32/mkfs.fat32.c | 511 +++++++++++++++++++++++++++++++++++ build.log | 17 +- 3 files changed, 525 insertions(+), 7 deletions(-) create mode 100644 apps/mkfs.fat32/mkfs.fat32.c diff --git a/README.md b/README.md index 4be5f28..574e1f5 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,8 @@ Once a task is completed, it should be checked off. - [x] Create the `mount` app. It should allow on to mount a block device using the fat32 driver. Internally, it should use sysfs (which should be mounted automatically by the kernel to `/sys`) to setup a new mount. - [x] Create a floppy driver. Each floppy device should be exposed as `/dev/floppyN`. - [x] Add support for character device to the devicefs subsystem. -- [ ] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device. -- [ ] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem. +- [x] Create an app called `diskpart`. This app can be used to modify the MBR partitions on a block device. +- [x] Create an app called `mkfs.fat32`. This app can be used to format a block into a FAT32 filesystem. - [ ] Create a network driver for the NE2000 NIC. - [ ] Create a network driver for the 3C509B NIC. It should only support RJ45 and 10base-T. - [ ] Create an ethernet subsytsem. Each ethernet device should be shown as a character device with the name `ethN`. diff --git a/apps/mkfs.fat32/mkfs.fat32.c b/apps/mkfs.fat32/mkfs.fat32.c new file mode 100644 index 0000000..a541be1 --- /dev/null +++ b/apps/mkfs.fat32/mkfs.fat32.c @@ -0,0 +1,511 @@ +/** + * @file mkfs.fat32.c + * @brief Format a block device with a FAT32 filesystem. + * + * Writes the FAT32 boot sector (BPB), FSInfo sector, two File Allocation + * Tables, and an empty root directory cluster to the specified device. + * + * Usage: mkfs.fat32 + * e.g.: mkfs.fat32 hdd1mbr1 14336 + * + * The device is accessed via /dev/. Total sectors must be + * specified because the OS does not yet expose device size to userspace. + */ + +#include "syscalls.h" + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; + +/* ================================================================ + * Constants + * ================================================================ */ + +#define SECTOR_SIZE 512 +#define RESERVED_SECTS 32 +#define NUM_FATS 2 +#define ROOT_CLUSTER 2 +#define FSINFO_SECTOR 1 +#define BACKUP_BOOT 6 + +/* ================================================================ + * Helpers + * ================================================================ */ + +/** + * Print a 32-bit decimal value. + */ +static void print_dec(uint32_t val) { + char buf[12]; + int pos = 11; + buf[pos] = '\0'; + if (val == 0) { + buf[--pos] = '0'; + } else { + while (val > 0 && pos > 0) { + buf[--pos] = (char)('0' + (val % 10)); + val /= 10; + } + } + puts(&buf[pos]); +} + +/** + * Print a hex value. + */ +static void print_hex(uint32_t val) { + char buf[11]; + buf[0] = '0'; + buf[1] = 'x'; + for (int i = 9; i >= 2; i--) { + int n = (int)(val & 0xF); + buf[i] = (n < 10) ? (char)('0' + n) : (char)('A' + n - 10); + val >>= 4; + } + buf[10] = '\0'; + puts(buf); +} + +/** + * Parse decimal string. + */ +static uint32_t parse_dec(const char *s, int *ok) { + uint32_t val = 0; + *ok = 0; + if (!s || !*s) return 0; + while (*s) { + if (*s < '0' || *s > '9') { *ok = 0; return 0; } + val = val * 10 + (uint32_t)(*s - '0'); + s++; + } + *ok = 1; + return val; +} + +/** + * Write a 16-bit little-endian value. + */ +static void w16(uint8_t *p, uint32_t val) { + p[0] = (uint8_t)(val & 0xFF); + p[1] = (uint8_t)((val >> 8) & 0xFF); +} + +/** + * Write a 32-bit little-endian value. + */ +static void w32(uint8_t *p, uint32_t val) { + p[0] = (uint8_t)(val & 0xFF); + p[1] = (uint8_t)((val >> 8) & 0xFF); + p[2] = (uint8_t)((val >> 16) & 0xFF); + p[3] = (uint8_t)((val >> 24) & 0xFF); +} + +/* ================================================================ + * Argument parsing + * ================================================================ */ + +#define MAX_TOKENS 4 +static char tok_buf[256]; +static char *tokens[MAX_TOKENS]; +static int token_count; + +static void tokenize(const char *input) { + int i = 0; + while (input[i] && i < 255) { tok_buf[i] = input[i]; i++; } + tok_buf[i] = '\0'; + token_count = 0; + char *p = tok_buf; + while (*p && token_count < MAX_TOKENS) { + while (*p == ' ') p++; + if (!*p) break; + tokens[token_count++] = p; + while (*p && *p != ' ') p++; + if (*p) { *p = '\0'; p++; } + } +} + +/* ================================================================ + * Sector buffer (reused for each write) + * ================================================================ */ + +static uint8_t sector[SECTOR_SIZE]; + +/** + * Write the sector buffer to the device fd. + * Returns 0 on success, -1 on failure. + */ +static int write_sector(int32_t fd) { + int32_t n = write(fd, sector, SECTOR_SIZE); + return (n == SECTOR_SIZE) ? 0 : -1; +} + +/** + * Write N zero sectors to the device fd. + */ +static int write_zero_sectors(int32_t fd, uint32_t count) { + memset(sector, 0, SECTOR_SIZE); + for (uint32_t i = 0; i < count; i++) { + if (write_sector(fd) != 0) return -1; + } + return 0; +} + +/* ================================================================ + * FAT32 geometry calculation + * ================================================================ */ + +/** + * Determine sectors per cluster based on volume size. + * Uses Microsoft's recommended values for FAT32. + */ +static uint32_t choose_spc(uint32_t total_sectors) { + /* For small volumes, use small clusters to maximize cluster count. + * FAT32 "officially" needs >= 65525 clusters, but our driver + * doesn't enforce this minimum. */ + if (total_sectors <= 16384) return 1; /* <= 8 MiB: 512B */ + if (total_sectors <= 131072) return 8; /* <= 64 MiB: 4K */ + if (total_sectors <= 524288) return 8; /* <= 256 MiB: 4K */ + if (total_sectors <= 16777216) return 8; /* <= 8 GiB: 4K */ + if (total_sectors <= 33554432) return 16; /* <= 16 GiB: 8K */ + return 32; /* > 16 GiB: 16K */ +} + +/** + * Calculate FAT size in sectors using Microsoft's formula. + * + * fat_size = ceil((total - reserved) / (entries_per_sec * spc + num_fats)) + * where entries_per_sec = 128 (512 / 4 bytes per FAT32 entry). + */ +static uint32_t calc_fat_size(uint32_t total_sectors, uint32_t spc) { + uint32_t data_area = total_sectors - RESERVED_SECTS; + uint32_t tmp = 128 * spc + NUM_FATS; + return (data_area + tmp - 1) / tmp; +} + +/* ================================================================ + * BPB / Boot sector construction + * ================================================================ */ + +/** + * Build the FAT32 boot sector (BPB) into the sector buffer. + */ +static void build_boot_sector(uint32_t total_sectors, uint32_t spc, + uint32_t fat_size, const char *label) { + memset(sector, 0, SECTOR_SIZE); + + /* Jump instruction: EB 58 90 (jump over BPB to boot code area) */ + sector[0] = 0xEB; + sector[1] = 0x58; + sector[2] = 0x90; + + /* OEM Name (offset 3, 8 bytes) */ + memcpy(§or[3], "CLAUDEOS", 8); + + /* BPB — BIOS Parameter Block */ + w16(§or[11], SECTOR_SIZE); /* bytes_per_sector */ + sector[13] = (uint8_t)spc; /* sectors_per_cluster */ + w16(§or[14], RESERVED_SECTS); /* reserved_sectors */ + sector[16] = NUM_FATS; /* num_fats */ + w16(§or[17], 0); /* root_entry_count (0 for FAT32) */ + w16(§or[19], 0); /* total_sectors_16 (0, use 32-bit) */ + sector[21] = 0xF8; /* media_type (hard disk) */ + w16(§or[22], 0); /* fat_size_16 (0, use 32-bit) */ + w16(§or[24], 63); /* sectors_per_track */ + w16(§or[26], 255); /* num_heads */ + w32(§or[28], 0); /* hidden_sectors */ + w32(§or[32], total_sectors); /* total_sectors_32 */ + + /* FAT32 Extended BPB (offset 36) */ + w32(§or[36], fat_size); /* fat_size_32 */ + w16(§or[40], 0); /* ext_flags */ + w16(§or[42], 0); /* fs_version */ + w32(§or[44], ROOT_CLUSTER); /* root_cluster */ + w16(§or[48], FSINFO_SECTOR); /* fs_info sector */ + w16(§or[50], BACKUP_BOOT); /* backup_boot sector */ + /* Reserved (12 bytes at offset 52, already zeroed) */ + + sector[64] = 0x80; /* drive_number (HDD) */ + sector[65] = 0; /* reserved1 */ + sector[66] = 0x29; /* boot_sig (extended boot sig) */ + + /* Volume serial number (we'll use a simple hash) */ + uint32_t serial = total_sectors ^ (spc << 16) ^ 0xC1A0DE05; + w32(§or[67], serial); + + /* Volume label (11 bytes, space-padded) */ + int li = 0; + while (label[li] && li < 11) { + char c = label[li]; + /* Convert to uppercase for FAT compatibility */ + if (c >= 'a' && c <= 'z') c = (char)(c - 32); + sector[71 + li] = (uint8_t)c; + li++; + } + while (li < 11) { + sector[71 + li] = ' '; + li++; + } + + /* FS type string (8 bytes) */ + memcpy(§or[82], "FAT32 ", 8); + + /* Boot sector signature */ + sector[510] = 0x55; + sector[511] = 0xAA; +} + +/* ================================================================ + * FSInfo sector construction + * ================================================================ */ + +/** + * Build the FSInfo sector into the sector buffer. + */ +static void build_fsinfo(uint32_t free_clusters, uint32_t next_free) { + memset(sector, 0, SECTOR_SIZE); + + /* FSInfo signatures */ + w32(§or[0], 0x41615252); /* Lead signature */ + w32(§or[484], 0x61417272); /* Structure signature */ + w32(§or[488], free_clusters); /* Free cluster count */ + w32(§or[492], next_free); /* Next free cluster hint */ + w32(§or[508], 0xAA550000); /* Trail signature */ +} + +/* ================================================================ + * FAT table construction + * ================================================================ */ + +/** + * Build the first sector of the FAT into the sector buffer. + * Entry 0 = media byte marker, Entry 1 = EOC, Entry 2 = EOC (root dir). + */ +static void build_fat_first_sector(void) { + memset(sector, 0, SECTOR_SIZE); + + /* FAT entry 0: media type in low byte, rest 0xFF */ + w32(§or[0], 0x0FFFFFF8); + + /* FAT entry 1: end-of-chain marker */ + w32(§or[4], 0x0FFFFFFF); + + /* FAT entry 2: root directory cluster (end-of-chain) */ + w32(§or[8], 0x0FFFFFFF); +} + +/* ================================================================ + * Root directory construction + * ================================================================ */ + +/** + * Build the root directory cluster's first sector. + * Contains a volume label entry only. + */ +static void build_root_dir(const char *label) { + memset(sector, 0, SECTOR_SIZE); + + /* Volume label directory entry (32 bytes) */ + int li = 0; + while (label[li] && li < 11) { + char c = label[li]; + if (c >= 'a' && c <= 'z') c = (char)(c - 32); + sector[li] = (uint8_t)c; + li++; + } + while (li < 11) { + sector[li] = ' '; + li++; + } + sector[11] = 0x08; /* ATTR_VOLUME_ID */ +} + +/* ================================================================ + * Main formatting routine + * ================================================================ */ + +/** + * Format the device with FAT32. + * + * Writes sectors sequentially from sector 0: + * [0] Boot sector (BPB) + * [1] FSInfo + * [2..5] Zero (reserved) + * [6] Backup boot sector + * [7] Backup FSInfo + * [8..31] Zero (reserved) + * [32..32+fat_size-1] FAT 1 + * [32+fat_size..32+2*fat_size-1] FAT 2 + * [data_start..data_start+spc-1] Root directory cluster + */ +static int format_device(const char *dev_path, uint32_t total_sectors, + const char *label) { + uint32_t spc = choose_spc(total_sectors); + uint32_t fat_size = calc_fat_size(total_sectors, spc); + uint32_t data_start = RESERVED_SECTS + NUM_FATS * fat_size; + uint32_t data_sectors = total_sectors - data_start; + uint32_t total_clusters = data_sectors / spc; + uint32_t free_clusters = total_clusters - 1; /* Root dir uses cluster 2 */ + + /* Print format parameters */ + puts("Formatting "); + puts(dev_path); + puts(" as FAT32\n"); + puts(" Total sectors: "); + print_dec(total_sectors); + puts("\n Sectors/cluster: "); + print_dec(spc); + puts("\n Reserved sectors: "); + print_dec(RESERVED_SECTS); + puts("\n FAT size (sectors): "); + print_dec(fat_size); + puts("\n Number of FATs: "); + print_dec(NUM_FATS); + puts("\n Data start sector: "); + print_dec(data_start); + puts("\n Total clusters: "); + print_dec(total_clusters); + puts("\n Volume label: "); + puts(label); + puts("\n"); + + /* Sanity checks */ + if (total_sectors < 128) { + puts("Error: volume too small for FAT32\n"); + return -1; + } + if (data_start >= total_sectors) { + puts("Error: FAT tables don't fit in volume\n"); + return -1; + } + + /* Open device for writing */ + int32_t fd = open(dev_path, 0); + if (fd < 0) { + puts("Error: cannot open "); + puts(dev_path); + puts("\n"); + return -1; + } + + puts("Writing boot sector...\n"); + + /* Sector 0: Boot sector */ + build_boot_sector(total_sectors, spc, fat_size, label); + if (write_sector(fd) != 0) goto fail; + + /* Sector 1: FSInfo */ + build_fsinfo(free_clusters, 3); /* Next free = cluster 3 */ + if (write_sector(fd) != 0) goto fail; + + /* Sectors 2-5: zero */ + if (write_zero_sectors(fd, 4) != 0) goto fail; + + /* Sector 6: Backup boot sector */ + build_boot_sector(total_sectors, spc, fat_size, label); + if (write_sector(fd) != 0) goto fail; + + /* Sector 7: Backup FSInfo */ + build_fsinfo(free_clusters, 3); + if (write_sector(fd) != 0) goto fail; + + /* Sectors 8-31: zero (remaining reserved area) */ + if (write_zero_sectors(fd, RESERVED_SECTS - 8) != 0) goto fail; + + puts("Writing FAT 1...\n"); + + /* FAT 1: first sector has entries 0-2 */ + build_fat_first_sector(); + if (write_sector(fd) != 0) goto fail; + + /* FAT 1: remaining sectors (all zero = free) */ + if (fat_size > 1) { + if (write_zero_sectors(fd, fat_size - 1) != 0) goto fail; + } + + puts("Writing FAT 2...\n"); + + /* FAT 2: identical copy */ + build_fat_first_sector(); + if (write_sector(fd) != 0) goto fail; + if (fat_size > 1) { + if (write_zero_sectors(fd, fat_size - 1) != 0) goto fail; + } + + puts("Writing root directory...\n"); + + /* Root directory cluster (first sector has volume label) */ + build_root_dir(label); + if (write_sector(fd) != 0) goto fail; + + /* Remaining sectors in root cluster (zero) */ + if (spc > 1) { + if (write_zero_sectors(fd, spc - 1) != 0) goto fail; + } + + close(fd); + + puts("Format complete!\n"); + puts(" "); + print_dec(data_start + spc); + puts(" sectors written\n"); + + return 0; + +fail: + close(fd); + puts("Error: write failed!\n"); + return -1; +} + +/* ================================================================ + * Main + * ================================================================ */ + +int main(void) { + char arg1[256]; + if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') { + puts("mkfs.fat32 - Format a device with FAT32\n"); + puts("\n"); + puts("Usage: mkfs.fat32 [label]\n"); + puts("\n"); + puts(" device: Device name (e.g., hdd1mbr1)\n"); + puts(" total_sectors: Number of 512-byte sectors\n"); + puts(" label: Volume label (default: CLAUDEOS)\n"); + puts("\n"); + puts("Example: mkfs.fat32 hdd1mbr1 14336\n"); + return 1; + } + + tokenize(arg1); + if (token_count < 2) { + puts("mkfs.fat32: need and \n"); + puts(" Use 'diskpart list' to find sector count.\n"); + return 1; + } + + /* Build /dev/ path */ + char dev_path[64]; + strcpy(dev_path, "/dev/"); + char *dp = dev_path + 5; + char *dn = tokens[0]; + while (*dn && dp < dev_path + 62) *dp++ = *dn++; + *dp = '\0'; + + /* Parse total sectors */ + int ok; + uint32_t total_sectors = parse_dec(tokens[1], &ok); + if (!ok || total_sectors == 0) { + puts("mkfs.fat32: invalid sector count '"); + puts(tokens[1]); + puts("'\n"); + return 1; + } + + /* Volume label (default "CLAUDEOS") */ + const char *label = "CLAUDEOS"; + if (token_count >= 3) { + label = tokens[2]; + } + + return (format_device(dev_path, total_sectors, label) == 0) ? 0 : 1; +} diff --git a/build.log b/build.log index 26ce50c..dd3863a 100644 --- a/build.log +++ b/build.log @@ -13,6 +13,13 @@ Building app: hello-world Built: /workspaces/claude-os/build/apps_bin/hello-world (49 bytes) Building app: ls Built: /workspaces/claude-os/build/apps_bin/ls (250 bytes) +Building app: mkfs.fat32 +/workspaces/claude-os/apps/mkfs.fat32/mkfs.fat32.c:56:13: warning: unused function 'print_hex' [-Wunused-function] + 56 | static void print_hex(uint32_t val) { + | ^~~~~~~~~ +1 warning generated. +/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/mkfs.fat32.elf has a LOAD segment with RWX permissions + Built: /workspaces/claude-os/build/apps_bin/mkfs.fat32 (5121 bytes) Building app: mount Built: /workspaces/claude-os/build/apps_bin/mount (992 bytes) Building app: sh @@ -23,7 +30,7 @@ Building app: sh Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes) [ 3%] Built target apps [ 6%] Generating CPIO initial ramdisk -Generated initrd: 15040 bytes +Generated initrd: 20288 bytes [ 6%] Built target initrd [ 96%] Built target kernel [100%] Generating bootable ISO image @@ -33,14 +40,14 @@ Drive current: -outdev 'stdio:/workspaces/claude-os/release/claude-os.iso' Media current: stdio file, overwriteable Media status : is blank Media summary: 0 sessions, 0 data blocks, 0 data, 126g free -Added to ISO image: directory '/'='/tmp/grub.MJcmop' +Added to ISO image: directory '/'='/tmp/grub.bEiDnH' xorriso : UPDATE : 581 files added in 1 seconds Added to ISO image: directory '/'='/workspaces/claude-os/build/isodir' xorriso : UPDATE : 586 files added in 1 seconds xorriso : NOTE : Copying to System Area: 512 bytes from file '/usr/lib/grub/i386-pc/boot_hybrid.img' -xorriso : UPDATE : 96.99% done -ISO image produced: 5922 sectors -Written to medium : 5922 sectors at LBA 0 +xorriso : UPDATE : Thank you for being patient. Working since 0 seconds. +ISO image produced: 5924 sectors +Written to medium : 5924 sectors at LBA 0 Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully. [100%] Built target iso