From 7dc50aa57d170e8adf291c6d7bb4cebe26a7d922 Mon Sep 17 00:00:00 2001 From: AI Date: Mon, 23 Feb 2026 16:28:44 +0000 Subject: [PATCH] Implement diskpart MBR partition editor app (AI) Create a user-space diskpart utility that can view and modify MBR partition tables on block devices accessible through /dev. Features: - list: displays all 4 partition slots with type, LBA start, size - create : creates a new partition - delete : removes a partition entry - activate/deactivate : sets or clears the bootable flag The tool reads sector 0 (MBR) via VFS open/read, modifies the partition table in memory, then writes it back via open/write. Supports hex type codes and provides human-readable type names for common partition types (FAT12/16/32, Linux, NTFS, etc.). Usage: diskpart hdd1 create 1 0C 2048 14336 diskpart hdd1 list diskpart hdd1 delete 2 --- apps/diskpart/diskpart.c | 600 +++++++++++++++++++++++++++++++++++++++ build.log | 17 +- 2 files changed, 610 insertions(+), 7 deletions(-) create mode 100644 apps/diskpart/diskpart.c diff --git a/apps/diskpart/diskpart.c b/apps/diskpart/diskpart.c new file mode 100644 index 0000000..97d0ed9 --- /dev/null +++ b/apps/diskpart/diskpart.c @@ -0,0 +1,600 @@ +/** + * @file diskpart.c + * @brief MBR partition table editor. + * + * Allows viewing, creating, and deleting MBR partitions on block devices. + * + * Usage: diskpart [command] [args...] + * + * Commands: + * list Show partition table (default) + * create Create a partition + * delete Delete a partition + * activate Set bootable flag + * deactivate Clear bootable flag + * + * is 1-4 (MBR supports up to 4 primary partitions). + * is a hex partition type code (e.g., 0C for FAT32 LBA). + * and are decimal LBA values. + * + * Examples: + * diskpart hdd1 list + * diskpart hdd1 create 1 0C 2048 14336 + * diskpart hdd1 delete 2 + * diskpart hdd1 activate 1 + */ + +#include "syscalls.h" + +typedef unsigned char uint8_t; + +/** MBR signature. */ +#define MBR_SIG_LO 0x55 +#define MBR_SIG_HI 0xAA + +/** MBR partition entry offsets. */ +#define MBR_PART_TABLE 446 +#define MBR_PART_SIZE 16 + +/* ================================================================ + * Numeric formatting helpers + * ================================================================ */ + +/** + * Print a 32-bit value as a hexadecimal string with "0x" prefix. + */ +static void print_hex(uint32_t val) { + char buf[11]; /* "0x" + 8 digits + NUL */ + buf[0] = '0'; + buf[1] = 'x'; + for (int i = 9; i >= 2; i--) { + int nibble = (int)(val & 0xF); + buf[i] = (nibble < 10) ? (char)('0' + nibble) + : (char)('A' + nibble - 10); + val >>= 4; + } + buf[10] = '\0'; + puts(buf); +} + +/** + * Print a 32-bit value as a decimal string. + */ +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]); +} + +/** + * Parse a decimal string to uint32_t. + * Returns the parsed value; sets *ok to 1 on success, 0 on failure. + */ +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; +} + +/** + * Parse a hexadecimal string (without "0x" prefix) to uint32_t. + */ +static uint32_t parse_hex(const char *s, int *ok) { + uint32_t val = 0; + *ok = 0; + if (!s || !*s) return 0; + + /* Skip optional "0x" prefix */ + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } + if (!*s) return 0; + + while (*s) { + uint32_t digit; + if (*s >= '0' && *s <= '9') digit = (uint32_t)(*s - '0'); + else if (*s >= 'a' && *s <= 'f') digit = (uint32_t)(*s - 'a' + 10); + else if (*s >= 'A' && *s <= 'F') digit = (uint32_t)(*s - 'A' + 10); + else { *ok = 0; return 0; } + val = (val << 4) | digit; + s++; + } + *ok = 1; + return val; +} + +/* ================================================================ + * Argument parsing + * ================================================================ */ + +/** Maximum number of tokens we parse from ARG1. */ +#define MAX_TOKENS 8 + +/** Token buffer storage. */ +static char token_buf[256]; +static char *tokens[MAX_TOKENS]; +static int token_count; + +/** + * Split the ARG1 string into space-separated tokens. + */ +static void tokenize(const char *input) { + /* Copy input to mutable buffer */ + int i = 0; + while (input[i] && i < 255) { + token_buf[i] = input[i]; + i++; + } + token_buf[i] = '\0'; + + token_count = 0; + char *p = token_buf; + while (*p && token_count < MAX_TOKENS) { + /* Skip whitespace */ + while (*p == ' ') p++; + if (!*p) break; + + tokens[token_count++] = p; + + /* Advance to next space or end */ + while (*p && *p != ' ') p++; + if (*p) { + *p = '\0'; + p++; + } + } +} + +/* ================================================================ + * Partition type name lookup + * ================================================================ */ + +/** + * Return a human-readable name for an MBR partition type code. + */ +static const char *type_name(uint32_t type) { + switch (type) { + case 0x00: return "Empty"; + case 0x01: return "FAT12"; + case 0x04: return "FAT16 <32M"; + case 0x05: return "Extended"; + case 0x06: return "FAT16 >=32M"; + case 0x07: return "NTFS/HPFS"; + case 0x0B: return "FAT32"; + case 0x0C: return "FAT32 LBA"; + case 0x0E: return "FAT16 LBA"; + case 0x82: return "Linux swap"; + case 0x83: return "Linux"; + default: return "Unknown"; + } +} + +/* ================================================================ + * MBR reading / writing + * ================================================================ */ + +/** MBR sector buffer (512 bytes). */ +static uint8_t mbr[512]; + +/** + * Read a 32-bit little-endian value from a byte array. + */ +static uint32_t read32le(const uint8_t *p) { + return (uint32_t)p[0] + | ((uint32_t)p[1] << 8) + | ((uint32_t)p[2] << 16) + | ((uint32_t)p[3] << 24); +} + +/** + * Write a 32-bit little-endian value to a byte array. + */ +static void write32le(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); +} + +/** + * Read MBR (sector 0) from a device. + * @param dev_path Full path like "/dev/hdd1". + * @return 0 on success, -1 on failure. + */ +static int read_mbr(const char *dev_path) { + int32_t fd = open(dev_path, 0); + if (fd < 0) { + puts("diskpart: cannot open "); + puts(dev_path); + puts("\n"); + return -1; + } + int32_t n = read(fd, mbr, 512); + close(fd); + if (n != 512) { + puts("diskpart: failed to read MBR (got "); + print_dec((uint32_t)(n < 0 ? 0 : n)); + puts(" bytes)\n"); + return -1; + } + return 0; +} + +/** + * Write MBR (sector 0) to a device. + * @param dev_path Full path like "/dev/hdd1". + * @return 0 on success, -1 on failure. + */ +static int write_mbr(const char *dev_path) { + int32_t fd = open(dev_path, 0); + if (fd < 0) { + puts("diskpart: cannot open "); + puts(dev_path); + puts(" for writing\n"); + return -1; + } + int32_t n = write(fd, mbr, 512); + close(fd); + if (n != 512) { + puts("diskpart: failed to write MBR\n"); + return -1; + } + return 0; +} + +/* ================================================================ + * Commands + * ================================================================ */ + +/** + * Display the partition table. + */ +static void cmd_list(void) { + /* Check signature */ + if (mbr[510] != MBR_SIG_LO || mbr[511] != MBR_SIG_HI) { + puts("No valid MBR signature found.\n"); + puts("Use 'create' to initialize partitions (signature will be added).\n"); + return; + } + + puts("Slot Boot Type Start LBA Sectors Size\n"); + puts("---- ---- ---------- ---------- ---------- ----------\n"); + + for (int i = 0; i < 4; i++) { + uint8_t *entry = &mbr[MBR_PART_TABLE + i * MBR_PART_SIZE]; + uint8_t status = entry[0]; + uint8_t type = entry[4]; + uint32_t lba_start = read32le(&entry[8]); + uint32_t sectors = read32le(&entry[12]); + + /* Slot number */ + putchar(' '); + putchar((char)('1' + i)); + puts(" "); + + /* Boot flag */ + if (status == 0x80) + puts(" * "); + else + puts(" "); + + /* Type */ + puts(" "); + print_hex(type); + puts(" "); + + /* Type name (pad to ~10 chars) */ + const char *tn = type_name(type); + puts(tn); + int tnlen = (int)strlen(tn); + for (int j = tnlen; j < 12; j++) putchar(' '); + + /* Start LBA */ + print_dec(lba_start); + /* Pad */ + char tmp[12]; + int tpos = 11; + tmp[tpos] = '\0'; + uint32_t v = lba_start; + if (v == 0) { tmp[--tpos] = '0'; } + else { while (v > 0) { tmp[--tpos] = (char)('0' + v % 10); v /= 10; } } + for (int j = 11 - tpos; j < 12; j++) putchar(' '); + + /* Sectors */ + print_dec(sectors); + tpos = 11; + tmp[tpos] = '\0'; + v = sectors; + if (v == 0) { tmp[--tpos] = '0'; } + else { while (v > 0) { tmp[--tpos] = (char)('0' + v % 10); v /= 10; } } + for (int j = 11 - tpos; j < 12; j++) putchar(' '); + + /* Size in KiB or MiB */ + uint32_t kib = sectors / 2; + if (kib >= 1024) { + print_dec(kib / 1024); + puts(" MiB"); + } else { + print_dec(kib); + puts(" KiB"); + } + + puts("\n"); + } +} + +/** + * Create a partition. + * Args: + */ +static int cmd_create(void) { + if (token_count < 6) { + puts("Usage: diskpart create \n"); + puts(" slot: 1-4\n"); + puts(" type: hex partition type (e.g., 0C for FAT32 LBA)\n"); + puts(" start: start LBA (decimal)\n"); + puts(" sectors: partition size in sectors (decimal)\n"); + return -1; + } + + int ok; + uint32_t slot = parse_dec(tokens[2], &ok); + if (!ok || slot < 1 || slot > 4) { + puts("diskpart: invalid slot (must be 1-4)\n"); + return -1; + } + + uint32_t type = parse_hex(tokens[3], &ok); + if (!ok || type > 0xFF || type == 0) { + puts("diskpart: invalid type (must be 01-FF hex)\n"); + return -1; + } + + uint32_t start = parse_dec(tokens[4], &ok); + if (!ok) { + puts("diskpart: invalid start LBA\n"); + return -1; + } + + uint32_t sectors = parse_dec(tokens[5], &ok); + if (!ok || sectors == 0) { + puts("diskpart: invalid sector count\n"); + return -1; + } + + uint32_t idx = slot - 1; + uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE]; + + /* Check if slot is already in use */ + if (entry[4] != 0x00) { + puts("diskpart: slot "); + print_dec(slot); + puts(" is already in use (type "); + print_hex(entry[4]); + puts("). Delete it first.\n"); + return -1; + } + + /* Clear the entry */ + memset(entry, 0, MBR_PART_SIZE); + + /* Set type */ + entry[4] = (uint8_t)type; + + /* Set LBA start and sector count */ + write32le(&entry[8], start); + write32le(&entry[12], sectors); + + /* Ensure MBR signature is present */ + mbr[510] = MBR_SIG_LO; + mbr[511] = MBR_SIG_HI; + + puts("Created partition "); + print_dec(slot); + puts(": type="); + print_hex(type); + puts(" ("); + puts(type_name(type)); + puts(") start="); + print_dec(start); + puts(" sectors="); + print_dec(sectors); + puts("\n"); + + return 0; +} + +/** + * Delete a partition. + */ +static int cmd_delete(void) { + if (token_count < 3) { + puts("Usage: diskpart delete \n"); + return -1; + } + + int ok; + uint32_t slot = parse_dec(tokens[2], &ok); + if (!ok || slot < 1 || slot > 4) { + puts("diskpart: invalid slot (must be 1-4)\n"); + return -1; + } + + uint32_t idx = slot - 1; + uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE]; + + if (entry[4] == 0x00) { + puts("diskpart: slot "); + print_dec(slot); + puts(" is already empty\n"); + return 0; + } + + uint8_t old_type = entry[4]; + memset(entry, 0, MBR_PART_SIZE); + + puts("Deleted partition "); + print_dec(slot); + puts(" (was type "); + print_hex(old_type); + puts(" - "); + puts(type_name(old_type)); + puts(")\n"); + + return 0; +} + +/** + * Set or clear the bootable flag on a partition. + */ +static int cmd_boot(int activate) { + if (token_count < 3) { + puts("Usage: diskpart "); + puts(activate ? "activate" : "deactivate"); + puts(" \n"); + return -1; + } + + int ok; + uint32_t slot = parse_dec(tokens[2], &ok); + if (!ok || slot < 1 || slot > 4) { + puts("diskpart: invalid slot (must be 1-4)\n"); + return -1; + } + + uint32_t idx = slot - 1; + uint8_t *entry = &mbr[MBR_PART_TABLE + idx * MBR_PART_SIZE]; + + if (entry[4] == 0x00) { + puts("diskpart: slot "); + print_dec(slot); + puts(" is empty\n"); + return -1; + } + + if (activate) { + /* Clear bootable flag on all other partitions */ + for (int i = 0; i < 4; i++) { + mbr[MBR_PART_TABLE + i * MBR_PART_SIZE] = 0x00; + } + entry[0] = 0x80; + puts("Partition "); + print_dec(slot); + puts(" is now bootable\n"); + } else { + entry[0] = 0x00; + puts("Partition "); + print_dec(slot); + puts(" is no longer bootable\n"); + } + + return 0; +} + +/* ================================================================ + * Main + * ================================================================ */ + +int main(void) { + char arg1[256]; + if (getenv("ARG1", arg1, sizeof(arg1)) < 0 || arg1[0] == '\0') { + puts("diskpart - MBR partition table editor\n"); + puts("\n"); + puts("Usage: diskpart [command] [args...]\n"); + puts("\n"); + puts("Commands:\n"); + puts(" list Show partitions (default)\n"); + puts(" create Create partition\n"); + puts(" delete Delete partition\n"); + puts(" activate Set bootable flag\n"); + puts(" deactivate Clear bootable flag\n"); + puts("\n"); + puts("Slot: 1-4, type: hex (e.g., 0C=FAT32 LBA, 83=Linux)\n"); + return 1; + } + + tokenize(arg1); + if (token_count < 1) { + puts("diskpart: no device specified\n"); + return 1; + } + + /* Token 0 is the device name. Build /dev/ path. */ + char dev_path[64]; + strcpy(dev_path, "/dev/"); + /* Append device name */ + char *dp = dev_path + 5; + char *dn = tokens[0]; + while (*dn && dp < dev_path + 62) { + *dp++ = *dn++; + } + *dp = '\0'; + + /* Determine command (default is "list") */ + const char *cmd = "list"; + if (token_count >= 2) { + cmd = tokens[1]; + } + + /* For "list" we only read; for others we read-modify-write */ + int need_write = 0; + + if (strcmp(cmd, "list") == 0) { + /* Just read and display */ + if (read_mbr(dev_path) != 0) return 1; + cmd_list(); + return 0; + } + + /* Read current MBR for modification commands */ + if (read_mbr(dev_path) != 0) return 1; + + int result = 0; + if (strcmp(cmd, "create") == 0) { + result = cmd_create(); + if (result == 0) need_write = 1; + } else if (strcmp(cmd, "delete") == 0) { + result = cmd_delete(); + if (result == 0) need_write = 1; + } else if (strcmp(cmd, "activate") == 0) { + result = cmd_boot(1); + if (result == 0) need_write = 1; + } else if (strcmp(cmd, "deactivate") == 0) { + result = cmd_boot(0); + if (result == 0) need_write = 1; + } else { + puts("diskpart: unknown command '"); + puts(cmd); + puts("'\n"); + return 1; + } + + if (result != 0) return 1; + + /* Write back modified MBR */ + if (need_write) { + if (write_mbr(dev_path) != 0) { + puts("diskpart: WARNING - failed to write MBR!\n"); + return 1; + } + puts("MBR written successfully.\n"); + } + + return 0; +} diff --git a/build.log b/build.log index c4969ff..26ce50c 100644 --- a/build.log +++ b/build.log @@ -1,6 +1,9 @@ [ 3%] Building user-mode applications Building app: cat Built: /workspaces/claude-os/build/apps_bin/cat (310 bytes) +Building app: diskpart +/usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/diskpart.elf has a LOAD segment with RWX permissions + Built: /workspaces/claude-os/build/apps_bin/diskpart (8406 bytes) Building app: env-test /usr/bin/ld: warning: /workspaces/claude-os/build/apps_bin/env-test.elf has a LOAD segment with RWX permissions Built: /workspaces/claude-os/build/apps_bin/env-test (389 bytes) @@ -19,9 +22,9 @@ Building app: sh 1 warning generated. Built: /workspaces/claude-os/build/apps_bin/sh (3428 bytes) [ 3%] Built target apps +[ 6%] Generating CPIO initial ramdisk +Generated initrd: 15040 bytes [ 6%] Built target initrd -[ 9%] Building C object src/CMakeFiles/kernel.dir/floppy.c.o -[ 12%] Linking C executable ../bin/kernel [ 96%] Built target kernel [100%] Generating bootable ISO image xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. @@ -29,15 +32,15 @@ xorriso 1.5.6 : RockRidge filesystem manipulator, libburnia project. 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, 127g free -Added to ISO image: directory '/'='/tmp/grub.oPDKGd' +Media summary: 0 sessions, 0 data blocks, 0 data, 126g free +Added to ISO image: directory '/'='/tmp/grub.MJcmop' 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 : Thank you for being patient. Working since 0 seconds. -ISO image produced: 5918 sectors -Written to medium : 5918 sectors at LBA 0 +xorriso : UPDATE : 96.99% done +ISO image produced: 5922 sectors +Written to medium : 5922 sectors at LBA 0 Writing to 'stdio:/workspaces/claude-os/release/claude-os.iso' completed successfully. [100%] Built target iso