From: Tulio A M Mendes Date: Wed, 20 May 2026 17:06:48 +0000 (-0300) Subject: mount/VFS: remove all auto-format paths; add mkfs; fix persistfs and flags X-Git-Url: https://projects.tadryanom.me/?a=commitdiff_plain;h=9df0b36326b6d426c93733aa04df23bb7bff9980;p=AdrOS.git mount/VFS: remove all auto-format paths; add mkfs; fix persistfs and flags Patch A (CRITICAL): diskfs_create_root() no longer auto-formats on missing superblock. Any error from diskfs_super_load() (ENODEV, EINVAL, EIO) now returns NULL immediately. g_ready is only set when the superblock is valid. This eliminates all destructive mount paths. Patch B: Added 'mkfs diskfs /dev/hdX' kconsole command for explicit diskfs formatting. Added diskfs_mkfs() public API that wraps diskfs_format(). This is the ONLY way to create a new diskfs filesystem. Battery/smoke tests now pre-format disk images with a minimal diskfs superblock (magic+version+next_free_lba+root_inode) at LBA 2 before QEMU launch. Patch C (CRITICAL): persistfs_create_root() now calls diskfs_probe() before diskfs_create_root(). If the drive does not contain a valid diskfs superblock, the diskfs initialization is skipped entirely. This prevents persistfs from triggering diskfs_format() on a FAT/ext2 disk via the indirect path: boot detects ext2 -> mounts /disk as ext2 -> init_mount_fs(persistfs) -> persistfs_create_root() -> [was] diskfs_create_root() -> diskfs_format() -> corrupts ext2 superblock. Patch D: init_mount_fs() now accepts unsigned long flags parameter and passes it to vfs_mount_full(). sys_mount passes mount_flags through to init_mount_fs. Boot-internal callers pass flags=0. This allows mount -o ro,nodev,noexec to work for disk-based FS. Patch E: mount CLI improvements — unknown -o options now cause error exit (not just warning). Added nosuid, nodev, noexec option parsing. show_mounts() now returns error code when /proc/mounts is unavailable. Patch F: Updated all test disk images to use create_diskfs_disk helper that stamps a minimal diskfs superblock. Updated Makefile run/test-gdb targets similarly. Updated fstab comment. --- diff --git a/Makefile b/Makefile index 9198ca62..4739f933 100644 --- a/Makefile +++ b/Makefile @@ -288,7 +288,7 @@ $(INITRD_IMG): $(INITRD_DEPS) run: iso @rm -f serial.log qemu.log - @test -f disk.img || dd if=/dev/zero of=disk.img bs=1M count=4 2>/dev/null + @test -f disk.img || { dd if=/dev/zero of=disk.img bs=1M count=4 2>/dev/null && printf '\x31\x53\x46\x44\x03\x00\x00\x00\x04\x00\x00\x00' | dd of=disk.img bs=1 seek=1024 conv=notrunc 2>/dev/null; } @qemu-system-i386 -boot d -cdrom adros-$(ARCH).iso -m 128M -display none \ -drive file=disk.img,if=ide,format=raw \ -nic user,model=e1000 \ @@ -384,7 +384,7 @@ test-host: test-gdb: $(KERNEL_NAME) iso @echo "[TEST-GDB] Starting QEMU with GDB stub..." @rm -f serial.log - @test -f disk.img || dd if=/dev/zero of=disk.img bs=1M count=4 2>/dev/null + @test -f disk.img || { dd if=/dev/zero of=disk.img bs=1M count=4 2>/dev/null && printf '\x31\x53\x46\x44\x03\x00\x00\x00\x04\x00\x00\x00' | dd of=disk.img bs=1 seek=1024 conv=notrunc 2>/dev/null; } @qemu-system-i386 -smp 4 -boot d -cdrom adros-$(ARCH).iso -m 128M -display none \ -drive file=disk.img,if=ide,format=raw \ -serial file:serial.log -monitor none -no-reboot -no-shutdown \ diff --git a/include/diskfs.h b/include/diskfs.h index fadc5105..5b4bec6e 100644 --- a/include/diskfs.h +++ b/include/diskfs.h @@ -19,6 +19,10 @@ fs_node_t* diskfs_create_root(int drive); // -ENODEV if not diskfs, or other negative errno on I/O error. int diskfs_probe(int drive); +// Explicit formatting: write a fresh diskfs superblock to the drive. +// Returns 0 on success, negative errno on error. +int diskfs_mkfs(int drive); + // Open (and optionally create) a diskfs file at the root (flat namespace). // rel_path must not contain '/'. // flags: supports O_CREAT (0x40) and O_TRUNC (0x200) semantics (minimal). diff --git a/include/kernel/init.h b/include/kernel/init.h index 4602bfd0..f2328378 100644 --- a/include/kernel/init.h +++ b/include/kernel/init.h @@ -20,7 +20,8 @@ int init_start(const struct boot_info* bi); * drive: ATA_DEV_PRIMARY_MASTER .. ATA_DEV_SECONDARY_SLAVE * lba: partition start LBA (0 for whole disk) * mountpoint: e.g. "/disk", "/fat", "/ext2" - * Returns 0 on success, -1 on failure. */ -int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mountpoint); + * flags: mount flags (MS_RDONLY, etc.) — stored in VFS mount table + * Returns 0 on success, negative errno on failure. */ +int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mountpoint, unsigned long flags); #endif diff --git a/rootfs/etc/fstab b/rootfs/etc/fstab index c7cd6587..6ed54a3b 100644 --- a/rootfs/etc/fstab +++ b/rootfs/etc/fstab @@ -1,4 +1,5 @@ # /etc/fstab — AdrOS filesystem table # +# Note: diskfs requires a pre-formatted disk (use 'mkfs diskfs /dev/hdX'). /dev/hda /disk diskfs defaults /dev/hda /persist persistfs defaults diff --git a/src/kernel/diskfs.c b/src/kernel/diskfs.c index c22f8e64..76ed0c34 100644 --- a/src/kernel/diskfs.c +++ b/src/kernel/diskfs.c @@ -1168,11 +1168,8 @@ static int diskfs_vfs_link(struct fs_node* dir, const char* name, struct fs_node fs_node_t* diskfs_create_root(int drive) { if (!g_ready) { g_diskfs_drive = drive; - if (ata_pio_drive_present(drive)) { - g_ready = 1; - } else { - g_ready = 0; - } + if (!ata_pio_drive_present(drive)) + return NULL; memset(&g_root, 0, sizeof(g_root)); strcpy(g_root.vfs.name, "disk"); @@ -1183,22 +1180,18 @@ fs_node_t* diskfs_create_root(int drive) { g_root.vfs.i_ops = &diskfs_dir_iops; g_root.ino = 0; - if (g_ready) { - struct diskfs_super sb; - int rc = diskfs_super_load(&sb); - if (rc == -ENODEV) { - /* No diskfs superblock found — format a fresh one. - * This preserves the auto-format behavior for boot-time - * mounting of blank disks (e.g. battery test root_hda.img). - * Autodetect probing will check diskfs_probe() first and - * skip diskfs if it returns -ENODEV, so this path is only - * reached when the caller explicitly requests diskfs. */ - (void)diskfs_format(&sb); - } + struct diskfs_super sb; + int rc = diskfs_super_load(&sb); + if (rc < 0) { + /* No valid diskfs superblock — do NOT auto-format. + * Use 'mkfs diskfs /dev/hdX' from kconsole to format. */ + return NULL; } + + g_ready = 1; } - return g_ready ? &g_root.vfs : NULL; + return &g_root.vfs; } int diskfs_probe(int drive) { @@ -1214,3 +1207,23 @@ int diskfs_probe(int drive) { if (magic != DISKFS_MAGIC) return -ENODEV; return 0; } + +int diskfs_mkfs(int drive) { + /* Explicit formatting: write a fresh diskfs superblock to the drive. + * This is the ONLY way to create a new diskfs filesystem. + * Returns 0 on success, negative errno on error. */ + if (!ata_pio_drive_present(drive)) return -ENODEV; + + /* If diskfs is already initialized on this drive, reject */ + if (g_ready && g_diskfs_drive == drive) return -EBUSY; + + /* Temporarily set the drive so diskfs_format can write */ + int saved_drive = g_diskfs_drive; + g_diskfs_drive = drive; + + struct diskfs_super sb; + int rc = diskfs_format(&sb); + + g_diskfs_drive = saved_drive; + return rc; +} diff --git a/src/kernel/init.c b/src/kernel/init.c index 3faf40bd..8774aa01 100644 --- a/src/kernel/init.c +++ b/src/kernel/init.c @@ -44,7 +44,7 @@ /* ---- Mount helper: used by fstab parser and kconsole 'mount' command ---- */ -int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mountpoint) { +int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mountpoint, unsigned long flags) { fs_node_t* root = NULL; if (strcmp(fstype, "diskfs") == 0) { @@ -79,7 +79,7 @@ int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mount *dp = '\0'; } - int rc = vfs_mount_full(mountpoint, root, fstype, devname, 0); + int rc = vfs_mount_full(mountpoint, root, fstype, devname, flags); if (rc < 0) { kprintf("[MOUNT] Failed to register mount at %s (err=%d)\n", mountpoint, rc); return rc; @@ -209,8 +209,8 @@ int init_start(const struct boot_info* bi) { if (drive >= 0 && ata_pio_drive_present(drive)) { /* Auto-detect: try ext2, fat first (non-destructive), then diskfs. * diskfs_probe() is non-destructive — it only checks the magic - * without formatting. diskfs_create_root() will auto-format - * blank disks, so it is only called when probe confirms diskfs. */ + * without formatting. diskfs_create_root() no longer auto-formats; + * use 'mkfs diskfs /dev/hdX' from kconsole to create a new fs. */ static const char* fstypes[] = { "ext2", "fat", "diskfs", NULL }; int mounted = 0; for (int i = 0; fstypes[i]; i++) { @@ -219,7 +219,7 @@ int init_start(const struct boot_info* bi) { extern int diskfs_probe(int drive); if (diskfs_probe(drive) != 0) continue; } - if (init_mount_fs(fstypes[i], drive, 0, "/disk") == 0) { + if (init_mount_fs(fstypes[i], drive, 0, "/disk", 0) == 0) { kprintf("[INIT] root=%s mounted as %s on /disk\n", root_dev, fstypes[i]); mounted = 1; @@ -239,13 +239,13 @@ int init_start(const struct boot_info* bi) { extern int diskfs_probe(int drive); if (diskfs_probe(0) != 0) continue; } - if (init_mount_fs(fstypes[i], 0, 0, "/disk") == 0) { + if (init_mount_fs(fstypes[i], 0, 0, "/disk", 0) == 0) { kprintf("[INIT] /dev/hda auto-mounted as %s on /disk\n", fstypes[i]); break; } } /* Also mount persistfs on /persist (was previously in /etc/fstab) */ - if (init_mount_fs("persistfs", 0, 0, "/persist") == 0) { + if (init_mount_fs("persistfs", 0, 0, "/persist", 0) == 0) { kprintf("[INIT] /dev/hda auto-mounted as persistfs on /persist\n"); } } diff --git a/src/kernel/kconsole.c b/src/kernel/kconsole.c index 5ea18bdb..1769eb98 100644 --- a/src/kernel/kconsole.c +++ b/src/kernel/kconsole.c @@ -21,6 +21,7 @@ #include "hal/cpu.h" #include "kernel/init.h" #include "ata_pio.h" +#include "errno.h" #define KCMD_MAX 128 @@ -258,6 +259,7 @@ static void kconsole_help(void) { kc_puts(" dmesg - Show kernel log buffer\n"); kc_puts(" lsblk - List detected ATA drives\n"); kc_puts(" mount -t /dev/ - Mount filesystem\n"); + kc_puts(" mkfs diskfs /dev/ - Format diskfs filesystem\n"); kc_puts(" reboot - Restart system\n"); kc_puts(" halt - Halt the CPU\n"); } @@ -363,7 +365,61 @@ static void kconsole_mount(const char* args) { return; } - (void)init_mount_fs(fstype, drive, 0, mountpoint); + (void)init_mount_fs(fstype, drive, 0, mountpoint, 0); +} + +/* mkfs diskfs /dev/ */ +static void kconsole_mkfs(const char* args) { + const char* p = args; + while (*p == ' ') p++; + + /* Extract fstype */ + const char* fs_start = p; + while (*p && *p != ' ') p++; + char fstype[16]; + size_t fs_len = (size_t)(p - fs_start); + if (fs_len >= sizeof(fstype)) fs_len = sizeof(fstype) - 1; + memcpy(fstype, fs_start, fs_len); + fstype[fs_len] = '\0'; + + if (strcmp(fstype, "diskfs") != 0) { + kprintf("mkfs: unsupported filesystem type: %s (only 'diskfs')\n", fstype); + return; + } + + while (*p == ' ') p++; + + /* Extract device */ + const char* dev_start = p; + while (*p && *p != ' ') p++; + char device[32]; + size_t dev_len = (size_t)(p - dev_start); + if (dev_len >= sizeof(device)) dev_len = sizeof(device) - 1; + memcpy(device, dev_start, dev_len); + device[dev_len] = '\0'; + + int drive = -1; + if (strncmp(device, "/dev/", 5) == 0) { + drive = ata_name_to_drive(device + 5); + } + if (drive < 0) { + kprintf("mkfs: unknown device: %s\n", device); + return; + } + if (!ata_pio_drive_present(drive)) { + kprintf("mkfs: device %s not present\n", device); + return; + } + + extern int diskfs_mkfs(int drive); + int rc = diskfs_mkfs(drive); + if (rc == 0) { + kprintf("mkfs: diskfs filesystem created on %s\n", device); + } else if (rc == -EBUSY) { + kprintf("mkfs: %s already has an active diskfs mount\n", device); + } else { + kprintf("mkfs: failed to format %s (err=%d)\n", device, rc); + } } static void kconsole_exec(const char* cmd) { @@ -438,6 +494,9 @@ static void kconsole_exec(const char* cmd) { else if (strncmp(cmd, "mount ", 6) == 0) { kconsole_mount(cmd + 6); } + else if (strncmp(cmd, "mkfs ", 5) == 0) { + kconsole_mkfs(cmd + 5); + } else if (cmd[0] != '\0') { kprintf("unknown command: %s\n", cmd); } diff --git a/src/kernel/persistfs.c b/src/kernel/persistfs.c index efeab7aa..20198f52 100644 --- a/src/kernel/persistfs.c +++ b/src/kernel/persistfs.c @@ -10,6 +10,7 @@ #include "persistfs.h" #include "ata_pio.h" +#include "console.h" #include "diskfs.h" #include "errno.h" #include "heap.h" @@ -100,21 +101,31 @@ fs_node_t* persistfs_create_root(int drive) { } if (g_ready) { - // Ensure diskfs is initialized even if /disk mount happens later. - (void)diskfs_create_root(drive); - - // One-time migration from legacy LBA1 counter storage. - uint8_t sec[512]; - if (ata_pio_read28(drive, PERSISTFS_LBA_COUNTER, sec) == 0) { - fs_node_t* b = persistfs_backing_open(PERSIST_O_CREAT); - if (b) { - uint8_t cur4[4]; - uint32_t rd = vfs_read(b, 0, 4, cur4); - if (rd == 0) { - (void)vfs_write(b, 0, 4, sec); + /* Only initialize diskfs backing if the drive actually contains + * a diskfs filesystem. Without this check, diskfs_create_root() + * would auto-format a non-diskfs disk (FAT/ext2), corrupting + * data. Now that diskfs_create_root() no longer auto-formats, + * the call would simply fail — but we skip it entirely when the + * probe fails to avoid unnecessary I/O and confusing log msgs. */ + extern int diskfs_probe(int drive); + if (diskfs_probe(drive) == 0) { + (void)diskfs_create_root(drive); + + // One-time migration from legacy LBA1 counter storage. + uint8_t sec[512]; + if (ata_pio_read28(drive, PERSISTFS_LBA_COUNTER, sec) == 0) { + fs_node_t* b = persistfs_backing_open(PERSIST_O_CREAT); + if (b) { + uint8_t cur4[4]; + uint32_t rd = vfs_read(b, 0, 4, cur4); + if (rd == 0) { + (void)vfs_write(b, 0, 4, sec); + } + persistfs_backing_close(b); } - persistfs_backing_close(b); } + } else { + kprintf("[PERSISTFS] No diskfs backing on drive %d — skipping\n", drive); } } diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c index 428ff52e..4d0251fd 100644 --- a/src/kernel/syscall.c +++ b/src/kernel/syscall.c @@ -4976,9 +4976,9 @@ static void socket_syscall_dispatch(struct registers* regs, uint32_t syscall_no) int drive = ata_name_to_drive(devname); if (drive < 0) { sc_ret(regs) = (uint32_t)-ENODEV; return; } - extern int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mountpoint); + extern int init_mount_fs(const char* fstype, int drive, uint32_t lba, const char* mountpoint, unsigned long flags); (void)vfs_mkdirp(kmp); /* auto-create mountpoint (recursive) */ - int rc = init_mount_fs(ktype, drive, 0, kmp); + int rc = init_mount_fs(ktype, drive, 0, kmp, mount_flags); sc_ret(regs) = (uint32_t)(rc < 0 ? rc : 0); return; } diff --git a/tests/smoke_test.exp b/tests/smoke_test.exp index 43f1c7db..a26e4a79 100755 --- a/tests/smoke_test.exp +++ b/tests/smoke_test.exp @@ -21,9 +21,17 @@ set iso "adros-x86.iso" set disk "disk.img" set serial_log "serial.log" -# Ensure disk image exists +# Ensure disk image exists with diskfs superblock pre-formatted +# (diskfs no longer auto-formats on mount) if {![file exists $disk]} { exec dd if=/dev/zero of=$disk bs=1M count=4 2>/dev/null + set fd [open $disk r+] + fconfigure $fd -translation binary + seek $fd 1024 + puts -nonewline $fd [binary format iiii 0x44465331 3 4 0] + seek $fd [expr {1024 + 12}] + puts -nonewline $fd [binary format ii 1 0] + close $fd } # Remove old serial log diff --git a/tests/test_battery.exp b/tests/test_battery.exp index 6c1e1d3c..7372e6a9 100644 --- a/tests/test_battery.exp +++ b/tests/test_battery.exp @@ -26,6 +26,28 @@ proc create_disk {path mb} { } } +# Create a disk image with a minimal diskfs superblock pre-formatted. +# diskfs no longer auto-formats on mount, so test disks need this. +proc create_diskfs_disk {path mb} { + if {![file exists $path]} { + exec dd if=/dev/zero of=$path bs=1M count=$mb 2>/dev/null + # Write diskfs superblock at LBA 2 (byte offset 1024): + # magic=0x44465331 version=3 next_free_lba=4 + # root inode: type=1 (DIR) parent=0 + # Using printf + dd to stamp the 12-byte header + root inode + set fd [open $path r+] + fconfigure $fd -translation binary + seek $fd 1024 + # magic (4 bytes LE) + version (4 bytes LE) + next_free_lba (4 bytes LE) + puts -nonewline $fd [binary format iiii 0x44465331 3 4 0] + # Root inode at offset 1024+12: type=1(DIR), parent=0 + # struct diskfs_inode: type(4) parent(4) name(24) start_lba(4) nsectors(4) nlink(4) cap_sectors(4) + seek $fd [expr {1024 + 12}] + puts -nonewline $fd [binary format ii 1 0] + close $fd + } +} + proc run_qemu {iso smp serial_log timeout_sec drive_args} { file delete -force $serial_log set cmd [list qemu-system-i386 \ @@ -142,7 +164,7 @@ puts "=========================================" puts " AdrOS Test Battery" puts "=========================================" -create_disk "disk.img" 4 +create_diskfs_disk "disk.img" 4 set pid [run_qemu $iso $smp $serial_log $timeout_sec \ {{-drive file=disk.img,if=ide,format=raw}}] @@ -306,10 +328,10 @@ kill_qemu $iso report_section "Multi-disk ATA (3 drives)" [lindex $res 0] [lindex $res 1] # ================================================================ -# TEST 3: VFS mount root=/dev/hda (diskfs auto-format) +# TEST 3: VFS mount root=/dev/hda (diskfs pre-formatted) # ================================================================ -create_disk "root_hda.img" 4 +create_diskfs_disk "root_hda.img" 4 set pid [run_qemu $iso $smp $serial_log $timeout_sec \ {{-drive file=root_hda.img,if=ide,index=0,format=raw}}] @@ -366,7 +388,7 @@ report_section "ATA /dev/hdd detection" [lindex $res 0] [lindex $res 1] # TEST 6: SMP=1 boot (single-CPU regression) # ================================================================ -create_disk "smp1_disk.img" 4 +create_diskfs_disk "smp1_disk.img" 4 set pid [run_qemu $iso 1 $serial_log $timeout_sec \ {{-drive file=smp1_disk.img,if=ide,format=raw}}] @@ -394,7 +416,7 @@ report_section "SMP=1 boot regression" [lindex $res 0] [lindex $res 1] # TEST 7: SMP=2 boot (dual-CPU) # ================================================================ -create_disk "smp2_disk.img" 4 +create_diskfs_disk "smp2_disk.img" 4 set pid [run_qemu $iso 2 $serial_log $timeout_sec \ {{-drive file=smp2_disk.img,if=ide,format=raw}}] diff --git a/user/cmds/mount/mount.c b/user/cmds/mount/mount.c index c64a6761..9df7cea8 100644 --- a/user/cmds/mount/mount.c +++ b/user/cmds/mount/mount.c @@ -15,7 +15,7 @@ #include #include -static void show_mounts(void) { +static int show_mounts(void) { int fd = open("/proc/mounts", O_RDONLY); if (fd >= 0) { char buf[2048]; @@ -23,15 +23,16 @@ static void show_mounts(void) { while ((n = read(fd, buf, sizeof(buf))) > 0) write(STDOUT_FILENO, buf, (size_t)n); close(fd); + return 0; } else { fprintf(stderr, "mount: /proc/mounts not available\n"); + return 1; } } int main(int argc, char** argv) { if (argc < 2) { - show_mounts(); - return 0; + return show_mounts(); } const char* fstype = NULL; @@ -57,11 +58,21 @@ int main(int argc, char** argv) { o += 7; } else if (strncmp(o, "rw", 2) == 0 && (o[2] == ',' || o[2] == '\0')) { o += 2; + } else if (strncmp(o, "nosuid", 6) == 0 && (o[6] == ',' || o[6] == '\0')) { + mountflags |= MS_NOSUID; + o += 6; + } else if (strncmp(o, "nodev", 5) == 0 && (o[5] == ',' || o[5] == '\0')) { + mountflags |= MS_NODEV; + o += 5; + } else if (strncmp(o, "noexec", 6) == 0 && (o[6] == ',' || o[6] == '\0')) { + mountflags |= MS_NOEXEC; + o += 6; } else { - /* Warn on unknown option */ + /* Fail on unknown option */ const char* start = o; while (*o && *o != ',') o++; fprintf(stderr, "mount: unknown option: %.*s\n", (int)(o - start), start); + return 1; } if (*o == ',') o++; }