From 420a2e6ace303a6a94e64fb13b478d0b6a83fc48 Mon Sep 17 00:00:00 2001 From: Garren Date: Sun, 18 Jan 2026 11:43:48 +0800 Subject: [PATCH 1/5] Implement VirtIO Block Driver and FAT32 Filesystem Support - Added a new VirtIO block device driver (drivers/block/virtio_blk.c) for persistent storage, enabling read/write operations for FAT32/ext4 filesystems. - Updated the Makefile to include new build targets for running the kernel with persistent disk storage and creating FAT32 disk images. - Introduced a FAT32 filesystem driver (kernel/fs/fat32.c) that supports long filenames and integrates with the VirtIO block driver for disk operations. - Enhanced the kernel initialization process to initialize the VirtIO block driver and attempt to mount a FAT32 filesystem from disk, falling back to RamFS if necessary. - Created a script (scripts/create-disk-image.sh) for generating FAT32 disk images for testing and development purposes. This commit lays the groundwork for persistent storage capabilities in Vib-OS, allowing data to survive reboots. --- Makefile | 29 +- drivers/block/virtio_blk.c | 545 ++++++++++++ kernel/core/main.c | 59 +- kernel/fs/fat32.c | 1426 ++++++++++++++++++++++++++++++++ kernel/fs/vfs.c | 7 + kernel/gui/window.c | 42 +- kernel/include/drivers/block.h | 147 ++++ scripts/create-disk-image.sh | 96 +++ 8 files changed, 2314 insertions(+), 37 deletions(-) create mode 100644 drivers/block/virtio_blk.c create mode 100644 kernel/fs/fat32.c create mode 100644 kernel/include/drivers/block.h create mode 100755 scripts/create-disk-image.sh diff --git a/Makefile b/Makefile index dd2b828..b1227ca 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,7 @@ QEMU_FLAGS := -M $(QEMU_MACHINE) -cpu $(QEMU_CPU) -m $(QEMU_MEMORY) \ # Main Targets # ============================================================================ -.PHONY: all clean kernel drivers libc userspace runtimes image qemu qemu-debug test help +.PHONY: all clean kernel drivers libc userspace runtimes image qemu qemu-debug test help run run-gui run-disk disk-image all: kernel drivers libc userspace runtimes image @echo "==========================================" @@ -108,6 +108,10 @@ help: @echo "Test targets:" @echo " qemu - Run in QEMU emulator" @echo " qemu-debug - Run with GDB server" + @echo " run - Run kernel in QEMU (text mode)" + @echo " run-gui - Run kernel with GUI display" + @echo " run-disk - Run with persistent FAT32 disk storage" + @echo " disk-image - Create FAT32 disk image for persistence" @echo " test - Run test suite" @echo "" @echo "Utility targets:" @@ -273,6 +277,29 @@ run-gui: kernel -serial stdio \ -kernel $(KERNEL_BINARY) +# Run with persistent disk storage +run-disk: kernel disk-image + @echo "[RUN] Starting Vib-OS with persistent disk..." + @qemu-system-aarch64 -M virt,gic-version=3 \ + -cpu max -m 512M \ + -global virtio-mmio.force-legacy=false \ + -device ramfb \ + -device virtio-keyboard-device \ + -device virtio-tablet-device \ + -device virtio-blk-device,drive=hd0 \ + -drive id=hd0,if=none,format=raw,file=$(IMAGE_DIR)/disk.img \ + -device virtio-net-device,netdev=net0 \ + -netdev user,id=net0 \ + -serial stdio \ + -kernel $(KERNEL_BINARY) + +# Create a FAT32 disk image +disk-image: $(IMAGE_DIR) + @echo "[DISK] Creating FAT32 disk image..." + @./scripts/create-disk-image.sh 64 $(IMAGE_DIR)/disk.img || \ + (dd if=/dev/zero of=$(IMAGE_DIR)/disk.img bs=1M count=64 2>/dev/null && \ + echo "[DISK] Created raw disk image (format manually if needed)") + # ============================================================================ # Toolchain Setup # ============================================================================ diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c new file mode 100644 index 0000000..33db8bd --- /dev/null +++ b/drivers/block/virtio_blk.c @@ -0,0 +1,545 @@ +/* + * Vib-OS - VirtIO Block Device Driver + * + * Provides persistent storage through QEMU's virtio-blk device. + * Supports read/write operations for FAT32/ext4 filesystems. + * + * Based on VirtIO 1.0 specification and QEMU virt machine layout. + */ + +#include "types.h" +#include "printk.h" +#include "mm/kmalloc.h" + +/* ===================================================================== */ +/* VirtIO MMIO registers (QEMU virt machine) */ +/* ===================================================================== */ + +#define VIRTIO_MMIO_BASE 0x0a000000 +#define VIRTIO_MMIO_STRIDE 0x200 + +/* VirtIO MMIO register offsets */ +#define VIRTIO_MMIO_MAGIC 0x000 +#define VIRTIO_MMIO_VERSION 0x004 +#define VIRTIO_MMIO_DEVICE_ID 0x008 +#define VIRTIO_MMIO_VENDOR_ID 0x00c +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 +#define VIRTIO_MMIO_DEVICE_FEATURES_SEL 0x014 +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 +#define VIRTIO_MMIO_DRIVER_FEATURES_SEL 0x024 +#define VIRTIO_MMIO_QUEUE_SEL 0x030 +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 +#define VIRTIO_MMIO_QUEUE_NUM 0x038 +#define VIRTIO_MMIO_QUEUE_READY 0x044 +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 +#define VIRTIO_MMIO_STATUS 0x070 +#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 +#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 +#define VIRTIO_MMIO_QUEUE_AVAIL_LOW 0x090 +#define VIRTIO_MMIO_QUEUE_AVAIL_HIGH 0x094 +#define VIRTIO_MMIO_QUEUE_USED_LOW 0x0a0 +#define VIRTIO_MMIO_QUEUE_USED_HIGH 0x0a4 +#define VIRTIO_MMIO_CONFIG 0x100 + +/* VirtIO device status bits */ +#define VIRTIO_STATUS_ACK 1 +#define VIRTIO_STATUS_DRIVER 2 +#define VIRTIO_STATUS_DRIVER_OK 4 +#define VIRTIO_STATUS_FEATURES_OK 8 +#define VIRTIO_STATUS_FAILED 128 + +/* VirtIO device types */ +#define VIRTIO_DEV_BLK 2 + +/* VirtIO block features */ +#define VIRTIO_BLK_F_SIZE_MAX (1 << 1) +#define VIRTIO_BLK_F_SEG_MAX (1 << 2) +#define VIRTIO_BLK_F_GEOMETRY (1 << 4) +#define VIRTIO_BLK_F_RO (1 << 5) +#define VIRTIO_BLK_F_BLK_SIZE (1 << 6) +#define VIRTIO_BLK_F_FLUSH (1 << 9) + +/* VirtIO block request types */ +#define VIRTIO_BLK_T_IN 0 /* Read */ +#define VIRTIO_BLK_T_OUT 1 /* Write */ +#define VIRTIO_BLK_T_FLUSH 4 /* Flush */ + +/* VirtIO block status codes */ +#define VIRTIO_BLK_S_OK 0 +#define VIRTIO_BLK_S_IOERR 1 +#define VIRTIO_BLK_S_UNSUPP 2 + +/* Sector size */ +#define SECTOR_SIZE 512 + +/* ===================================================================== */ +/* VirtIO structures */ +/* ===================================================================== */ + +/* VirtIO descriptor */ +typedef struct __attribute__((packed, aligned(16))) { + uint64_t addr; /* Physical address of buffer */ + uint32_t len; /* Length of buffer */ + uint16_t flags; /* Descriptor flags */ + uint16_t next; /* Next descriptor index */ +} virtq_desc_t; + +#define VIRTQ_DESC_F_NEXT 1 /* Buffer continues in next descriptor */ +#define VIRTQ_DESC_F_WRITE 2 /* Buffer is write-only (device writes) */ + +/* VirtIO available ring */ +typedef struct __attribute__((packed, aligned(2))) { + uint16_t flags; + uint16_t idx; + uint16_t ring[8]; + uint16_t used_event; /* Only if VIRTIO_F_EVENT_IDX */ +} virtq_avail_t; + +/* VirtIO used ring element */ +typedef struct __attribute__((packed)) { + uint32_t id; + uint32_t len; +} virtq_used_elem_t; + +/* VirtIO used ring */ +typedef struct __attribute__((packed, aligned(4))) { + uint16_t flags; + uint16_t idx; + virtq_used_elem_t ring[8]; + uint16_t avail_event; /* Only if VIRTIO_F_EVENT_IDX */ +} virtq_used_t; + +/* VirtIO block request header */ +typedef struct __attribute__((packed)) { + uint32_t type; /* Request type (IN/OUT/FLUSH) */ + uint32_t reserved; + uint64_t sector; /* Sector number */ +} virtio_blk_req_t; + +/* ===================================================================== */ +/* Queue configuration - use small queue for simplicity */ +/* ===================================================================== */ + +#define QUEUE_SIZE 8 + +/* ===================================================================== */ +/* Driver state */ +/* ===================================================================== */ + +static volatile uint32_t *blk_base = NULL; +static bool blk_initialized = false; +static uint64_t blk_capacity = 0; /* Disk capacity in sectors */ +static uint32_t blk_sector_size = 512; /* Sector size in bytes */ + +/* Queue structures - properly aligned */ +static virtq_desc_t desc_ring[QUEUE_SIZE] __attribute__((aligned(16))); +static virtq_avail_t avail_ring __attribute__((aligned(4096))); +static virtq_used_t used_ring __attribute__((aligned(4096))); + +static uint16_t last_used_idx = 0; + +/* Single request buffer - we process one request at a time for simplicity */ +static virtio_blk_req_t request_hdr __attribute__((aligned(16))); +static uint8_t data_buffer[SECTOR_SIZE] __attribute__((aligned(16))); +static uint8_t status_byte __attribute__((aligned(16))); + +/* ===================================================================== */ +/* MMIO Helpers */ +/* ===================================================================== */ + +static inline void mmio_barrier(void) +{ + asm volatile("dsb sy" ::: "memory"); +} + +static inline uint32_t mmio_read32(volatile uint32_t *base, uint32_t offset) +{ + volatile uint32_t *addr = (volatile uint32_t *)((uint8_t *)base + offset); + uint32_t val = *addr; + mmio_barrier(); + return val; +} + +static inline void mmio_write32(volatile uint32_t *base, uint32_t offset, uint32_t val) +{ + volatile uint32_t *addr = (volatile uint32_t *)((uint8_t *)base + offset); + mmio_barrier(); + *addr = val; + mmio_barrier(); +} + +static inline uint64_t mmio_read64(volatile uint32_t *base, uint32_t offset) +{ + uint32_t lo = mmio_read32(base, offset); + uint32_t hi = mmio_read32(base, offset + 4); + return ((uint64_t)hi << 32) | lo; +} + +/* ===================================================================== */ +/* Find VirtIO Block Device */ +/* ===================================================================== */ + +static volatile uint32_t *find_virtio_blk(void) +{ + for (int i = 0; i < 32; i++) { + volatile uint32_t *base = (volatile uint32_t *)(uintptr_t)(VIRTIO_MMIO_BASE + i * VIRTIO_MMIO_STRIDE); + + uint32_t magic = mmio_read32(base, VIRTIO_MMIO_MAGIC); + uint32_t device_id = mmio_read32(base, VIRTIO_MMIO_DEVICE_ID); + + if (magic == 0x74726976 && device_id == VIRTIO_DEV_BLK) { + printk(KERN_INFO "VIRTIO_BLK: Found block device at slot %d\n", i); + return base; + } + } + + return NULL; +} + +/* ===================================================================== */ +/* Single-Request I/O (synchronous, one at a time) */ +/* ===================================================================== */ + +static int do_block_io(uint32_t type, uint64_t sector, void *buffer) +{ + if (!blk_initialized) { + return -1; + } + + /* Setup request header */ + request_hdr.type = type; + request_hdr.reserved = 0; + request_hdr.sector = sector; + + /* For writes, copy data to our aligned buffer */ + if (type == VIRTIO_BLK_T_OUT && buffer) { + uint8_t *src = (uint8_t *)buffer; + for (int i = 0; i < SECTOR_SIZE; i++) { + data_buffer[i] = src[i]; + } + } + + /* Initialize status to invalid value */ + status_byte = 0xFF; + + /* Setup descriptor chain: header -> data -> status */ + /* Descriptor 0: Request header (device reads) */ + desc_ring[0].addr = (uint64_t)(uintptr_t)&request_hdr; + desc_ring[0].len = sizeof(virtio_blk_req_t); + desc_ring[0].flags = VIRTQ_DESC_F_NEXT; + desc_ring[0].next = 1; + + /* Descriptor 1: Data buffer */ + desc_ring[1].addr = (uint64_t)(uintptr_t)data_buffer; + desc_ring[1].len = SECTOR_SIZE; + if (type == VIRTIO_BLK_T_IN) { + /* Read: device writes to buffer */ + desc_ring[1].flags = VIRTQ_DESC_F_WRITE | VIRTQ_DESC_F_NEXT; + } else { + /* Write: device reads from buffer */ + desc_ring[1].flags = VIRTQ_DESC_F_NEXT; + } + desc_ring[1].next = 2; + + /* Descriptor 2: Status byte (device writes) */ + desc_ring[2].addr = (uint64_t)(uintptr_t)&status_byte; + desc_ring[2].len = 1; + desc_ring[2].flags = VIRTQ_DESC_F_WRITE; + desc_ring[2].next = 0; + + /* Memory barrier before making descriptor available */ + mmio_barrier(); + + /* Add descriptor chain head to available ring */ + uint16_t avail_idx = avail_ring.idx; + avail_ring.ring[avail_idx % QUEUE_SIZE] = 0; /* First descriptor index */ + mmio_barrier(); + avail_ring.idx = avail_idx + 1; + mmio_barrier(); + + /* Notify device */ + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_NOTIFY, 0); + + /* Wait for completion - poll used ring */ + int timeout = 10000000; + while (used_ring.idx == last_used_idx && timeout > 0) { + mmio_barrier(); + timeout--; + /* Small delay */ + for (volatile int d = 0; d < 10; d++) { } + } + + /* Acknowledge interrupt immediately */ + uint32_t isr = mmio_read32(blk_base, VIRTIO_MMIO_INTERRUPT_STATUS); + if (isr) { + mmio_write32(blk_base, VIRTIO_MMIO_INTERRUPT_ACK, isr); + } + + if (timeout == 0) { + printk(KERN_WARNING "VIRTIO_BLK: Request timeout (sector %llu)\n", + (unsigned long long)sector); + return -1; + } + + /* Update last used index */ + last_used_idx = used_ring.idx; + + /* Check status */ + if (status_byte != VIRTIO_BLK_S_OK) { + printk(KERN_ERR "VIRTIO_BLK: I/O error at sector %llu, status=%d\n", + (unsigned long long)sector, status_byte); + return -1; + } + + /* For reads, copy data back to caller's buffer */ + if (type == VIRTIO_BLK_T_IN && buffer) { + uint8_t *dst = (uint8_t *)buffer; + for (int i = 0; i < SECTOR_SIZE; i++) { + dst[i] = data_buffer[i]; + } + } + + return 0; +} + +/* ===================================================================== */ +/* Block I/O Operations */ +/* ===================================================================== */ + +int virtio_blk_read(uint64_t sector, uint32_t count, void *buffer) +{ + if (!blk_initialized) { + return -1; + } + + if (sector + count > blk_capacity) { + printk(KERN_ERR "VIRTIO_BLK: Read beyond disk capacity\n"); + return -1; + } + + uint8_t *buf = (uint8_t *)buffer; + + for (uint32_t i = 0; i < count; i++) { + if (do_block_io(VIRTIO_BLK_T_IN, sector + i, buf + i * SECTOR_SIZE) < 0) { + return -1; + } + } + + return 0; +} + +int virtio_blk_write(uint64_t sector, uint32_t count, const void *buffer) +{ + if (!blk_initialized) { + return -1; + } + + if (sector + count > blk_capacity) { + printk(KERN_ERR "VIRTIO_BLK: Write beyond disk capacity\n"); + return -1; + } + + const uint8_t *buf = (const uint8_t *)buffer; + + for (uint32_t i = 0; i < count; i++) { + if (do_block_io(VIRTIO_BLK_T_OUT, sector + i, (void *)(buf + i * SECTOR_SIZE)) < 0) { + return -1; + } + } + + return 0; +} + +int virtio_blk_flush(void) +{ + if (!blk_initialized) { + return -1; + } + + /* For flush, we don't need a data buffer */ + request_hdr.type = VIRTIO_BLK_T_FLUSH; + request_hdr.reserved = 0; + request_hdr.sector = 0; + + status_byte = 0xFF; + + /* Setup 2-descriptor chain: header -> status */ + desc_ring[0].addr = (uint64_t)(uintptr_t)&request_hdr; + desc_ring[0].len = sizeof(virtio_blk_req_t); + desc_ring[0].flags = VIRTQ_DESC_F_NEXT; + desc_ring[0].next = 1; + + desc_ring[1].addr = (uint64_t)(uintptr_t)&status_byte; + desc_ring[1].len = 1; + desc_ring[1].flags = VIRTQ_DESC_F_WRITE; + desc_ring[1].next = 0; + + mmio_barrier(); + + uint16_t avail_idx = avail_ring.idx; + avail_ring.ring[avail_idx % QUEUE_SIZE] = 0; + mmio_barrier(); + avail_ring.idx = avail_idx + 1; + mmio_barrier(); + + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_NOTIFY, 0); + + int timeout = 10000000; + while (used_ring.idx == last_used_idx && timeout > 0) { + mmio_barrier(); + timeout--; + } + + uint32_t isr = mmio_read32(blk_base, VIRTIO_MMIO_INTERRUPT_STATUS); + if (isr) { + mmio_write32(blk_base, VIRTIO_MMIO_INTERRUPT_ACK, isr); + } + + last_used_idx = used_ring.idx; + + return (status_byte == VIRTIO_BLK_S_OK) ? 0 : -1; +} + +/* ===================================================================== */ +/* Public API */ +/* ===================================================================== */ + +uint64_t virtio_blk_get_capacity(void) +{ + return blk_capacity; +} + +uint32_t virtio_blk_get_sector_size(void) +{ + return blk_sector_size; +} + +bool virtio_blk_is_ready(void) +{ + return blk_initialized; +} + +/* ===================================================================== */ +/* Initialization */ +/* ===================================================================== */ + +int virtio_blk_init(void) +{ + printk(KERN_INFO "VIRTIO_BLK: Initializing VirtIO block driver...\n"); + + /* Find VirtIO block device */ + blk_base = find_virtio_blk(); + if (!blk_base) { + printk(KERN_WARNING "VIRTIO_BLK: No block device found\n"); + return -1; + } + + /* Reset device */ + mmio_write32(blk_base, VIRTIO_MMIO_STATUS, 0); + while (mmio_read32(blk_base, VIRTIO_MMIO_STATUS) != 0) { + asm volatile("nop"); + } + + /* Acknowledge device */ + mmio_write32(blk_base, VIRTIO_MMIO_STATUS, VIRTIO_STATUS_ACK); + + /* Indicate driver loaded */ + mmio_write32(blk_base, VIRTIO_MMIO_STATUS, + VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER); + + /* Read device features */ + uint32_t features = mmio_read32(blk_base, VIRTIO_MMIO_DEVICE_FEATURES); + printk(KERN_INFO "VIRTIO_BLK: Device features: 0x%08x\n", features); + + /* Accept minimal features - don't require any special features */ + mmio_write32(blk_base, VIRTIO_MMIO_DRIVER_FEATURES, 0); + + /* Features OK */ + mmio_write32(blk_base, VIRTIO_MMIO_STATUS, + VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK); + + /* Verify features accepted */ + uint32_t status = mmio_read32(blk_base, VIRTIO_MMIO_STATUS); + if (!(status & VIRTIO_STATUS_FEATURES_OK)) { + printk(KERN_ERR "VIRTIO_BLK: Feature negotiation failed\n"); + mmio_write32(blk_base, VIRTIO_MMIO_STATUS, VIRTIO_STATUS_FAILED); + return -1; + } + + /* Setup virtqueue 0 */ + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_SEL, 0); + + uint32_t max_queue = mmio_read32(blk_base, VIRTIO_MMIO_QUEUE_NUM_MAX); + if (max_queue == 0) { + printk(KERN_ERR "VIRTIO_BLK: Queue not available\n"); + return -1; + } + + /* Use small queue size */ + uint32_t queue_size = (max_queue < QUEUE_SIZE) ? max_queue : QUEUE_SIZE; + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_NUM, queue_size); + + /* Initialize queue structures */ + for (int i = 0; i < QUEUE_SIZE; i++) { + desc_ring[i].addr = 0; + desc_ring[i].len = 0; + desc_ring[i].flags = 0; + desc_ring[i].next = 0; + } + avail_ring.flags = 0; + avail_ring.idx = 0; + used_ring.flags = 0; + used_ring.idx = 0; + last_used_idx = 0; + + /* Set queue addresses */ + uint64_t desc_addr = (uint64_t)(uintptr_t)desc_ring; + uint64_t avail_addr = (uint64_t)(uintptr_t)&avail_ring; + uint64_t used_addr = (uint64_t)(uintptr_t)&used_ring; + + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_DESC_LOW, (uint32_t)desc_addr); + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_DESC_HIGH, (uint32_t)(desc_addr >> 32)); + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_AVAIL_LOW, (uint32_t)avail_addr); + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_AVAIL_HIGH, (uint32_t)(avail_addr >> 32)); + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_USED_LOW, (uint32_t)used_addr); + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_USED_HIGH, (uint32_t)(used_addr >> 32)); + + /* Queue ready */ + mmio_write32(blk_base, VIRTIO_MMIO_QUEUE_READY, 1); + + /* Read disk configuration */ + blk_capacity = mmio_read64(blk_base, VIRTIO_MMIO_CONFIG); + blk_sector_size = 512; /* Default, could read from config if feature enabled */ + + printk(KERN_INFO "VIRTIO_BLK: Capacity: %llu sectors (%llu MB)\n", + (unsigned long long)blk_capacity, + (unsigned long long)(blk_capacity * blk_sector_size / (1024 * 1024))); + printk(KERN_INFO "VIRTIO_BLK: Sector size: %u bytes\n", blk_sector_size); + + /* Driver OK */ + mmio_write32(blk_base, VIRTIO_MMIO_STATUS, + VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | + VIRTIO_STATUS_FEATURES_OK | VIRTIO_STATUS_DRIVER_OK); + + /* Verify no failure */ + status = mmio_read32(blk_base, VIRTIO_MMIO_STATUS); + if (status & VIRTIO_STATUS_FAILED) { + printk(KERN_ERR "VIRTIO_BLK: Device initialization failed\n"); + return -1; + } + + blk_initialized = true; + printk(KERN_INFO "VIRTIO_BLK: Block device initialized successfully\n"); + + /* Test read sector 0 to verify driver works */ + uint8_t test_buf[512]; + if (virtio_blk_read(0, 1, test_buf) == 0) { + printk(KERN_INFO "VIRTIO_BLK: Test read successful\n"); + } else { + printk(KERN_WARNING "VIRTIO_BLK: Test read failed\n"); + } + + return 0; +} diff --git a/kernel/core/main.c b/kernel/core/main.c index 2952d76..6708e5e 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -158,13 +158,17 @@ static void init_subsystems(void *dtb) process_init(); /* ================================================================= */ - /* Phase 4: Filesystems */ + /* Phase 4: Block Devices & Filesystems */ /* ================================================================= */ - printk(KERN_INFO "[INIT] Phase 4: Filesystems\n"); + printk(KERN_INFO "[INIT] Phase 4: Block Devices & Filesystems\n"); + + /* Initialize VirtIO Block driver for persistent storage */ + printk(KERN_INFO " Initializing VirtIO block driver...\n"); + extern int virtio_blk_init(void); + extern bool virtio_blk_is_ready(void); + int blk_ret = virtio_blk_init(); - /* Initialize Virtual Filesystem */ - printk(KERN_INFO " Initializing VFS...\n"); /* Initialize Virtual Filesystem */ printk(KERN_INFO " Initializing VFS...\n"); vfs_init(); @@ -174,22 +178,41 @@ static void init_subsystems(void *dtb) extern int ramfs_init(void); ramfs_init(); - /* Mount root filesystem */ - printk(KERN_INFO " Mounting root filesystem...\n"); - if (vfs_mount("ramfs", "/", "ramfs", 0, NULL) != 0) { - panic("Failed to mount root filesystem!"); + /* Initialize FAT32 filesystem driver */ + printk(KERN_INFO " Initializing FAT32 driver...\n"); + extern int fat32_init(void); + fat32_init(); + + /* Try to mount FAT32 from disk first, fall back to RamFS */ + int mounted_disk = 0; + if (blk_ret == 0 && virtio_blk_is_ready()) { + printk(KERN_INFO " Attempting to mount FAT32 from disk...\n"); + if (vfs_mount("vda", "/", "fat32", 0, NULL) == 0) { + printk(KERN_INFO " Mounted FAT32 filesystem from disk!\n"); + mounted_disk = 1; + } else { + printk(KERN_WARNING " FAT32 mount failed, falling back to RamFS\n"); + } } - /* Populate filesystem with sample data */ - extern int ramfs_create_dir(const char *path, mode_t mode); - extern int ramfs_create_file(const char *path, mode_t mode, const char *content); - - ramfs_create_dir("Documents", 0755); - ramfs_create_dir("Downloads", 0755); - ramfs_create_dir("Pictures", 0755); - ramfs_create_dir("System", 0755); - ramfs_create_file("readme.txt", 0644, "Welcome to Vib-OS!\nThis is a real file in RamFS."); - ramfs_create_file("todo.txt", 0644, "- Implement Browser\n- Fix Bugs\n- Sleep"); + /* Fall back to RamFS if no disk available */ + if (!mounted_disk) { + printk(KERN_INFO " Mounting RamFS as root filesystem...\n"); + if (vfs_mount("ramfs", "/", "ramfs", 0, NULL) != 0) { + panic("Failed to mount root filesystem!"); + } + + /* Populate RamFS with sample data */ + extern int ramfs_create_dir(const char *path, mode_t mode); + extern int ramfs_create_file(const char *path, mode_t mode, const char *content); + + ramfs_create_dir("Documents", 0755); + ramfs_create_dir("Downloads", 0755); + ramfs_create_dir("Pictures", 0755); + ramfs_create_dir("System", 0755); + ramfs_create_file("readme.txt", 0644, "Welcome to Vib-OS!\nThis is a real file in RamFS.\nNote: Data will be lost on reboot."); + ramfs_create_file("todo.txt", 0644, "- Add persistent storage\n- Implement Browser\n- Fix Bugs"); + } /* Mount proc, sys, dev (placeholders) */ printk(KERN_INFO " Mounting procfs...\n"); diff --git a/kernel/fs/fat32.c b/kernel/fs/fat32.c new file mode 100644 index 0000000..5cd5599 --- /dev/null +++ b/kernel/fs/fat32.c @@ -0,0 +1,1426 @@ +/* + * Vib-OS Kernel - FAT32 Filesystem Driver + * + * Provides read/write support for FAT32 filesystems on block devices. + * Supports long filenames (LFN) and persistent storage. + */ + +#include "fs/vfs.h" +#include "drivers/block.h" +#include "mm/kmalloc.h" +#include "printk.h" +#include "types.h" + +/* ===================================================================== */ +/* FAT32 Constants */ +/* ===================================================================== */ + +#define FAT32_SIGNATURE 0xAA55 +#define FAT32_FSTYPE "FAT32 " +#define FAT32_BOOT_SIGNATURE 0x29 + +#define FAT_ENTRY_FREE 0x00000000 +#define FAT_ENTRY_RESERVED 0x00000001 +#define FAT_ENTRY_BAD 0x0FFFFFF7 +#define FAT_ENTRY_EOC 0x0FFFFFF8 /* End of chain marker */ +#define FAT_ENTRY_MASK 0x0FFFFFFF /* 28 bits for cluster number */ + +#define FAT_ATTR_READ_ONLY 0x01 +#define FAT_ATTR_HIDDEN 0x02 +#define FAT_ATTR_SYSTEM 0x04 +#define FAT_ATTR_VOLUME_ID 0x08 +#define FAT_ATTR_DIRECTORY 0x10 +#define FAT_ATTR_ARCHIVE 0x20 +#define FAT_ATTR_LONG_NAME 0x0F + +#define FAT_DIR_ENTRY_SIZE 32 +#define FAT_NAME_MAX 11 /* 8.3 format */ +#define FAT_LFN_MAX 255 + +/* ===================================================================== */ +/* FAT32 On-disk Structures */ +/* ===================================================================== */ + +/* BIOS Parameter Block (BPB) - Boot Sector */ +struct fat32_bpb { + uint8_t jmp_boot[3]; /* Jump instruction */ + char oem_name[8]; /* OEM identifier */ + uint16_t bytes_per_sector; /* Sector size (usually 512) */ + uint8_t sectors_per_cluster; /* Cluster size in sectors */ + uint16_t reserved_sectors; /* Reserved sectors before FAT */ + uint8_t num_fats; /* Number of FAT copies */ + uint16_t root_entry_count; /* Root entries (0 for FAT32) */ + uint16_t total_sectors_16; /* Total sectors (0 for FAT32) */ + uint8_t media_type; /* Media descriptor */ + uint16_t fat_size_16; /* FAT size in sectors (0 for FAT32) */ + uint16_t sectors_per_track; /* Sectors per track */ + uint16_t num_heads; /* Number of heads */ + uint32_t hidden_sectors; /* Hidden sectors */ + uint32_t total_sectors_32; /* Total sectors (FAT32) */ + /* FAT32 specific */ + uint32_t fat_size_32; /* FAT size in sectors */ + uint16_t ext_flags; /* Extended flags */ + uint16_t fs_version; /* Filesystem version */ + uint32_t root_cluster; /* Root directory cluster */ + uint16_t fs_info_sector; /* FSInfo sector number */ + uint16_t backup_boot_sector; /* Backup boot sector */ + uint8_t reserved[12]; + uint8_t drive_number; /* BIOS drive number */ + uint8_t reserved1; + uint8_t boot_signature; /* Extended boot signature (0x29) */ + uint32_t volume_id; /* Volume serial number */ + char volume_label[11]; /* Volume label */ + char fs_type[8]; /* Filesystem type string */ +} __attribute__((packed)); + +/* FSInfo Structure */ +struct fat32_fsinfo { + uint32_t lead_sig; /* 0x41615252 */ + uint8_t reserved1[480]; + uint32_t struc_sig; /* 0x61417272 */ + uint32_t free_count; /* Free cluster count */ + uint32_t next_free; /* Next free cluster hint */ + uint8_t reserved2[12]; + uint32_t trail_sig; /* 0xAA550000 */ +} __attribute__((packed)); + +/* Directory Entry (8.3 short name) */ +struct fat32_dir_entry { + char name[8]; /* Filename (space-padded) */ + char ext[3]; /* Extension (space-padded) */ + uint8_t attr; /* Attributes */ + uint8_t nt_reserved; /* Reserved for Windows NT */ + uint8_t create_time_tenth; /* Creation time (tenths of sec) */ + uint16_t create_time; /* Creation time */ + uint16_t create_date; /* Creation date */ + uint16_t access_date; /* Last access date */ + uint16_t cluster_high; /* High 16 bits of cluster */ + uint16_t modify_time; /* Modification time */ + uint16_t modify_date; /* Modification date */ + uint16_t cluster_low; /* Low 16 bits of cluster */ + uint32_t file_size; /* File size in bytes */ +} __attribute__((packed)); + +/* Long Filename Entry */ +struct fat32_lfn_entry { + uint8_t order; /* Order of this entry */ + uint16_t name1[5]; /* Characters 1-5 */ + uint8_t attr; /* Always 0x0F */ + uint8_t type; /* Reserved (0) */ + uint8_t checksum; /* Checksum of short name */ + uint16_t name2[6]; /* Characters 6-11 */ + uint16_t cluster; /* Always 0 */ + uint16_t name3[2]; /* Characters 12-13 */ +} __attribute__((packed)); + +/* ===================================================================== */ +/* FAT32 In-memory Structures */ +/* ===================================================================== */ + +struct fat32_fs { + struct fat32_bpb bpb; /* Boot sector data */ + uint32_t fat_start; /* First FAT sector */ + uint32_t data_start; /* First data sector */ + uint32_t root_cluster; /* Root directory cluster */ + uint32_t cluster_size; /* Bytes per cluster */ + uint32_t total_clusters; /* Total data clusters */ + uint32_t *fat_cache; /* Cached FAT table */ + uint32_t fat_cache_size; /* Size of cached FAT */ + + /* Block device access */ + int (*read_sectors)(uint64_t sector, uint32_t count, void *buf); + int (*write_sectors)(uint64_t sector, uint32_t count, const void *buf); + void *device; +}; + +struct fat32_file { + struct fat32_fs *fs; + uint32_t start_cluster; /* First cluster of file */ + uint32_t current_cluster; /* Current cluster */ + uint32_t cluster_offset; /* Offset within cluster */ + uint32_t file_size; /* Total file size */ + uint32_t position; /* Current position */ + uint8_t attr; /* File attributes */ + char name[FAT_LFN_MAX + 1]; /* Filename */ + /* Directory entry location for updating file size */ + uint32_t dir_cluster; /* Cluster containing directory entry */ + uint32_t dir_entry_offset; /* Byte offset of entry within cluster */ +}; + +/* ===================================================================== */ +/* Static data */ +/* ===================================================================== */ + +static struct fat32_fs *mounted_fs = NULL; + +/* ===================================================================== */ +/* Forward Declarations */ +/* ===================================================================== */ + +static void string_to_fat_name(const char *str, char *name, char *ext); + +/* ===================================================================== */ +/* Helper Functions */ +/* ===================================================================== */ + +static inline uint32_t cluster_to_sector(struct fat32_fs *fs, uint32_t cluster) +{ + return fs->data_start + (cluster - 2) * fs->bpb.sectors_per_cluster; +} + +static uint32_t get_cluster_entry(struct fat32_fs *fs, uint32_t cluster) +{ + /* Try cache first */ + if (fs->fat_cache && cluster < fs->fat_cache_size) { + return fs->fat_cache[cluster] & FAT_ENTRY_MASK; + } + + /* Fall back to reading from disk */ + uint32_t fat_offset = cluster * 4; /* 4 bytes per FAT32 entry */ + uint32_t fat_sector = fs->fat_start + (fat_offset / fs->bpb.bytes_per_sector); + uint32_t entry_offset = fat_offset % fs->bpb.bytes_per_sector; + + uint8_t sector_buf[512]; /* Stack buffer for single sector */ + if (fs->read_sectors(fat_sector, 1, sector_buf) < 0) { + return FAT_ENTRY_EOC; /* Return EOC on error */ + } + + uint32_t entry = *((uint32_t *)(sector_buf + entry_offset)); + return entry & FAT_ENTRY_MASK; +} + +static int read_cluster(struct fat32_fs *fs, uint32_t cluster, void *buffer) +{ + if (cluster < 2) { + return -1; + } + + uint32_t sector = cluster_to_sector(fs, cluster); + return fs->read_sectors(sector, fs->bpb.sectors_per_cluster, buffer); +} + +static int write_cluster(struct fat32_fs *fs, uint32_t cluster, const void *buffer) +{ + if (cluster < 2) { + return -1; + } + + uint32_t sector = cluster_to_sector(fs, cluster); + return fs->write_sectors(sector, fs->bpb.sectors_per_cluster, buffer); +} + +static uint32_t next_cluster(struct fat32_fs *fs, uint32_t cluster) +{ + uint32_t entry = get_cluster_entry(fs, cluster); + + if (entry >= FAT_ENTRY_EOC) { + return 0; /* End of chain */ + } + if (entry == FAT_ENTRY_FREE || entry == FAT_ENTRY_BAD) { + return 0; /* Invalid */ + } + + return entry; +} + +/* Set a FAT entry (update both cache and disk) */ +static int set_cluster_entry(struct fat32_fs *fs, uint32_t cluster, uint32_t value) +{ + if (cluster < 2 || cluster >= fs->total_clusters + 2) { + return -1; + } + + /* Update cache */ + if (fs->fat_cache && cluster < fs->fat_cache_size) { + fs->fat_cache[cluster] = value; + } + + /* Calculate sector and offset within FAT */ + uint32_t fat_offset = cluster * 4; /* 4 bytes per entry in FAT32 */ + uint32_t fat_sector = fs->fat_start + (fat_offset / fs->bpb.bytes_per_sector); + uint32_t entry_offset = fat_offset % fs->bpb.bytes_per_sector; + + /* Read FAT sector */ + uint8_t *sector_buf = kmalloc(fs->bpb.bytes_per_sector); + if (!sector_buf) { + return -1; + } + + if (fs->read_sectors(fat_sector, 1, sector_buf) < 0) { + kfree(sector_buf); + return -1; + } + + /* Update entry (preserve upper 4 bits) */ + uint32_t *entry_ptr = (uint32_t *)(sector_buf + entry_offset); + *entry_ptr = (*entry_ptr & 0xF0000000) | (value & FAT_ENTRY_MASK); + + /* Write back to all FAT copies */ + for (int i = 0; i < fs->bpb.num_fats; i++) { + uint32_t fat_copy_sector = fat_sector + i * fs->bpb.fat_size_32; + if (fs->write_sectors(fat_copy_sector, 1, sector_buf) < 0) { + kfree(sector_buf); + return -1; + } + } + + kfree(sector_buf); + return 0; +} + +/* Allocate a free cluster */ +static uint32_t allocate_cluster(struct fat32_fs *fs) +{ + /* Search for a free cluster */ + uint32_t search_limit = fs->total_clusters + 2; + uint32_t checked = 0; + + for (uint32_t cluster = 2; cluster < search_limit; cluster++) { + uint32_t entry = get_cluster_entry(fs, cluster); + + /* Debug: print first few entries being checked */ + if (checked < 5) { + printk(KERN_DEBUG "FAT32: Cluster %u entry=0x%08x\n", cluster, entry); + } + checked++; + + if (entry == FAT_ENTRY_FREE) { + /* Mark as end of chain */ + if (set_cluster_entry(fs, cluster, FAT_ENTRY_EOC | 0x0FFFFFF8) == 0) { + printk(KERN_DEBUG "FAT32: Allocated cluster %u\n", cluster); + return cluster; + } + printk(KERN_ERR "FAT32: Failed to mark cluster %u\n", cluster); + return 0; /* Failed to mark */ + } + } + + printk(KERN_ERR "FAT32: No free clusters available (checked %u clusters, limit=%u)\n", + checked, search_limit); + return 0; /* No free clusters */ +} + +/* Zero out a cluster */ +static int zero_cluster(struct fat32_fs *fs, uint32_t cluster) +{ + uint8_t *buf = kzalloc(fs->cluster_size, GFP_KERNEL); + if (!buf) { + return -1; + } + + int ret = write_cluster(fs, cluster, buf); + kfree(buf); + return ret; +} + +/* Find a free directory entry slot in a directory */ +static int find_free_dir_entry(struct fat32_fs *fs, uint32_t dir_cluster, + uint32_t *out_cluster, int *out_index) +{ + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + uint32_t cluster = dir_cluster; + uint32_t prev_cluster = 0; + + while (cluster != 0 && cluster < FAT_ENTRY_EOC) { + if (read_cluster(fs, cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -1; + } + + struct fat32_dir_entry *entries = (struct fat32_dir_entry *)cluster_buf; + int num_entries = fs->cluster_size / FAT_DIR_ENTRY_SIZE; + + for (int i = 0; i < num_entries; i++) { + /* Check for free or deleted entry */ + if (entries[i].name[0] == 0x00 || (uint8_t)entries[i].name[0] == 0xE5) { + *out_cluster = cluster; + *out_index = i; + kfree(cluster_buf); + return 0; + } + } + + prev_cluster = cluster; + cluster = next_cluster(fs, cluster); + } + + /* No free entry found - need to allocate a new cluster */ + kfree(cluster_buf); + + uint32_t new_cluster = allocate_cluster(fs); + if (new_cluster == 0) { + return -1; + } + + /* Link to previous cluster */ + if (set_cluster_entry(fs, prev_cluster, new_cluster) < 0) { + return -1; + } + + /* Zero the new cluster */ + if (zero_cluster(fs, new_cluster) < 0) { + return -1; + } + + *out_cluster = new_cluster; + *out_index = 0; + return 0; +} + +/* Add a directory entry to a directory */ +static int add_dir_entry(struct fat32_fs *fs, uint32_t dir_cluster, + const char *name, uint8_t attr, uint32_t file_cluster, + uint32_t file_size, uint32_t *out_cluster, + uint32_t *out_offset) +{ + uint32_t entry_cluster; + int entry_index; + + if (find_free_dir_entry(fs, dir_cluster, &entry_cluster, &entry_index) < 0) { + return -1; + } + + /* Read the cluster containing the entry */ + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + if (read_cluster(fs, entry_cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -1; + } + + /* Create the directory entry */ + struct fat32_dir_entry *entries = (struct fat32_dir_entry *)cluster_buf; + struct fat32_dir_entry *entry = &entries[entry_index]; + + /* Clear entry */ + for (size_t i = 0; i < sizeof(struct fat32_dir_entry); i++) { + ((uint8_t *)entry)[i] = 0; + } + + /* Set 8.3 name */ + string_to_fat_name(name, entry->name, entry->ext); + + /* Set attributes */ + entry->attr = attr; + entry->nt_reserved = 0; + + /* Set cluster */ + entry->cluster_high = (file_cluster >> 16) & 0xFFFF; + entry->cluster_low = file_cluster & 0xFFFF; + + /* Set size (0 for directories) */ + entry->file_size = (attr & FAT_ATTR_DIRECTORY) ? 0 : file_size; + + /* Set timestamps (use a fixed date for simplicity: 2024-01-01 12:00:00) */ + /* Time: hours=12, minutes=0, seconds=0 -> (12<<11)|(0<<5)|0 = 0x6000 */ + /* Date: year=2024-1980=44, month=1, day=1 -> (44<<9)|(1<<5)|1 = 0x5821 */ + entry->create_time = 0x6000; + entry->create_date = 0x5821; + entry->modify_time = 0x6000; + entry->modify_date = 0x5821; + entry->access_date = 0x5821; + + /* Write back */ + if (write_cluster(fs, entry_cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -1; + } + + /* Return entry location */ + if (out_cluster) *out_cluster = entry_cluster; + if (out_offset) *out_offset = entry_index * FAT_DIR_ENTRY_SIZE; + + kfree(cluster_buf); + return 0; +} + +/* Update file size in directory entry on disk */ +static int update_dir_entry_size(struct fat32_file *ff) +{ + struct fat32_fs *fs = ff->fs; + if (!fs || ff->dir_cluster == 0) { + return -1; /* No directory entry location stored */ + } + + /* Read the cluster containing the directory entry */ + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + if (read_cluster(fs, ff->dir_cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -1; + } + + /* Get the directory entry */ + struct fat32_dir_entry *entry = (struct fat32_dir_entry *) + (cluster_buf + ff->dir_entry_offset); + + /* Update the file size */ + entry->file_size = ff->file_size; + + /* Update modification timestamp */ + entry->modify_time = 0x6000; /* 12:00:00 */ + entry->modify_date = 0x5821; /* 2024-01-01 */ + + /* Write back to disk */ + if (write_cluster(fs, ff->dir_cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -1; + } + + printk("FAT32: Updated dir entry size to %u bytes\n", ff->file_size); + kfree(cluster_buf); + return 0; +} + +/* Convert 8.3 name to readable format */ +static void fat_name_to_string(const char *name, const char *ext, char *out) +{ + int i, j = 0; + + /* Copy name, trimming trailing spaces */ + for (i = 0; i < 8 && name[i] != ' '; i++) { + out[j++] = (name[i] >= 'A' && name[i] <= 'Z') ? + name[i] + 32 : name[i]; /* Lowercase */ + } + + /* Add extension if present */ + if (ext[0] != ' ') { + out[j++] = '.'; + for (i = 0; i < 3 && ext[i] != ' '; i++) { + out[j++] = (ext[i] >= 'A' && ext[i] <= 'Z') ? + ext[i] + 32 : ext[i]; + } + } + + out[j] = '\0'; +} + +/* Convert string to 8.3 format */ +static void string_to_fat_name(const char *str, char *name, char *ext) +{ + int i = 0, j = 0; + + /* Initialize with spaces */ + for (i = 0; i < 8; i++) name[i] = ' '; + for (i = 0; i < 3; i++) ext[i] = ' '; + + i = 0; + + /* Copy name part */ + while (str[i] && str[i] != '.' && j < 8) { + char c = str[i++]; + if (c >= 'a' && c <= 'z') c -= 32; /* Uppercase */ + name[j++] = c; + } + + /* Skip dot */ + if (str[i] == '.') i++; + + /* Copy extension */ + j = 0; + while (str[i] && j < 3) { + char c = str[i++]; + if (c >= 'a' && c <= 'z') c -= 32; + ext[j++] = c; + } +} + +/* ===================================================================== */ +/* Directory Operations */ +/* ===================================================================== */ + +static int fat32_find_entry(struct fat32_fs *fs, uint32_t dir_cluster, + const char *name, struct fat32_dir_entry *entry, + uint32_t *out_cluster, uint32_t *out_offset) +{ + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + char target_name[12], target_ext[4]; + string_to_fat_name(name, target_name, target_ext); + + uint32_t cluster = dir_cluster; + + while (cluster != 0 && cluster < FAT_ENTRY_EOC) { + if (read_cluster(fs, cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -1; + } + + struct fat32_dir_entry *entries = (struct fat32_dir_entry *)cluster_buf; + int num_entries = fs->cluster_size / FAT_DIR_ENTRY_SIZE; + + for (int i = 0; i < num_entries; i++) { + /* Skip free entries */ + if (entries[i].name[0] == 0x00) { + /* No more entries */ + kfree(cluster_buf); + return -1; + } + if ((uint8_t)entries[i].name[0] == 0xE5) { + /* Deleted entry */ + continue; + } + if (entries[i].attr == FAT_ATTR_LONG_NAME) { + /* Long filename entry - skip for now */ + continue; + } + if (entries[i].attr & FAT_ATTR_VOLUME_ID) { + continue; + } + + /* Compare names */ + int match = 1; + for (int j = 0; j < 8; j++) { + if (entries[i].name[j] != target_name[j]) { + match = 0; + break; + } + } + if (match) { + for (int j = 0; j < 3; j++) { + if (entries[i].ext[j] != target_ext[j]) { + match = 0; + break; + } + } + } + + if (match) { + /* Found it */ + for (size_t j = 0; j < sizeof(struct fat32_dir_entry); j++) { + ((uint8_t *)entry)[j] = ((uint8_t *)&entries[i])[j]; + } + /* Return directory entry location */ + if (out_cluster) *out_cluster = cluster; + if (out_offset) *out_offset = i * FAT_DIR_ENTRY_SIZE; + kfree(cluster_buf); + return 0; + } + } + + cluster = next_cluster(fs, cluster); + } + + kfree(cluster_buf); + return -1; /* Not found */ +} + +static int fat32_list_dir(struct fat32_fs *fs, uint32_t dir_cluster, + void *ctx, + int (*callback)(void *ctx, const char *name, + uint8_t attr, uint32_t size, + uint32_t cluster)) +{ + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + uint32_t cluster = dir_cluster; + int count = 0; + + while (cluster != 0 && cluster < FAT_ENTRY_EOC) { + if (read_cluster(fs, cluster, cluster_buf) < 0) { + break; + } + + struct fat32_dir_entry *entries = (struct fat32_dir_entry *)cluster_buf; + int num_entries = fs->cluster_size / FAT_DIR_ENTRY_SIZE; + + for (int i = 0; i < num_entries; i++) { + if (entries[i].name[0] == 0x00) { + goto done; + } + if ((uint8_t)entries[i].name[0] == 0xE5) { + continue; + } + if (entries[i].attr == FAT_ATTR_LONG_NAME) { + continue; + } + if (entries[i].attr & FAT_ATTR_VOLUME_ID) { + continue; + } + + /* Convert name */ + char name[13]; + fat_name_to_string(entries[i].name, entries[i].ext, name); + + uint32_t entry_cluster = ((uint32_t)entries[i].cluster_high << 16) | + entries[i].cluster_low; + + if (callback) { + callback(ctx, name, entries[i].attr, entries[i].file_size, + entry_cluster); + } + count++; + } + + cluster = next_cluster(fs, cluster); + } + +done: + kfree(cluster_buf); + return count; +} + +/* ===================================================================== */ +/* File Operations */ +/* ===================================================================== */ + +static ssize_t fat32_file_read(struct file *file, char *buf, size_t count, + loff_t *pos) +{ + struct fat32_file *ff = (struct fat32_file *)file->private_data; + if (!ff || !ff->fs) { + return -1; + } + + if (*pos >= ff->file_size) { + return 0; + } + + if (*pos + count > ff->file_size) { + count = ff->file_size - *pos; + } + + struct fat32_fs *fs = ff->fs; + size_t bytes_read = 0; + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + /* Navigate to the correct cluster */ + uint32_t cluster_index = *pos / fs->cluster_size; + uint32_t cluster_offset = *pos % fs->cluster_size; + + uint32_t cluster = ff->start_cluster; + for (uint32_t i = 0; i < cluster_index && cluster != 0; i++) { + cluster = next_cluster(fs, cluster); + } + + while (bytes_read < count && cluster != 0 && cluster < FAT_ENTRY_EOC) { + if (read_cluster(fs, cluster, cluster_buf) < 0) { + break; + } + + size_t to_read = fs->cluster_size - cluster_offset; + if (to_read > count - bytes_read) { + to_read = count - bytes_read; + } + + for (size_t i = 0; i < to_read; i++) { + buf[bytes_read + i] = cluster_buf[cluster_offset + i]; + } + + bytes_read += to_read; + cluster_offset = 0; + cluster = next_cluster(fs, cluster); + } + + kfree(cluster_buf); + *pos += bytes_read; + + return bytes_read; +} + +static ssize_t fat32_file_write(struct file *file, const char *buf, + size_t count, loff_t *pos) +{ + struct fat32_file *ff = (struct fat32_file *)file->private_data; + if (!ff || !ff->fs) { + return -1; + } + + struct fat32_fs *fs = ff->fs; + size_t bytes_written = 0; + uint8_t *cluster_buf = kmalloc(fs->cluster_size); + if (!cluster_buf) { + return -1; + } + + /* Navigate to the correct cluster */ + uint32_t cluster_index = *pos / fs->cluster_size; + uint32_t cluster_offset = *pos % fs->cluster_size; + + uint32_t cluster = ff->start_cluster; + + for (uint32_t i = 0; i < cluster_index && cluster != 0; i++) { + cluster = next_cluster(fs, cluster); + } + + /* TODO: Allocate new clusters if needed */ + /* For now, only support writing within existing file bounds */ + + while (bytes_written < count && cluster != 0 && cluster < FAT_ENTRY_EOC) { + /* Read existing cluster data */ + if (cluster_offset > 0 || + (count - bytes_written) < fs->cluster_size) { + if (read_cluster(fs, cluster, cluster_buf) < 0) { + break; + } + } + + size_t to_write = fs->cluster_size - cluster_offset; + if (to_write > count - bytes_written) { + to_write = count - bytes_written; + } + + for (size_t i = 0; i < to_write; i++) { + cluster_buf[cluster_offset + i] = buf[bytes_written + i]; + } + + if (write_cluster(fs, cluster, cluster_buf) < 0) { + break; + } + + bytes_written += to_write; + cluster_offset = 0; + cluster = next_cluster(fs, cluster); + } + + kfree(cluster_buf); + *pos += bytes_written; + + /* Update file size if extended */ + if (*pos > ff->file_size) { + ff->file_size = *pos; + /* Update directory entry on disk */ + update_dir_entry_size(ff); + } + + return bytes_written; +} + +static int fat32_file_open(struct inode *vfs_inode, struct file *file) +{ + file->private_data = vfs_inode->i_private; + + /* Handle O_TRUNC - reset file size to 0 */ + if (file->f_flags & O_TRUNC) { + struct fat32_file *ff = (struct fat32_file *)file->private_data; + if (ff) { + ff->file_size = 0; + ff->position = 0; + ff->current_cluster = ff->start_cluster; + ff->cluster_offset = 0; + /* Update directory entry on disk */ + update_dir_entry_size(ff); + printk(KERN_DEBUG "FAT32: Truncated file to 0 bytes\n"); + } + } + + return 0; +} + +static int fat32_file_release(struct inode *vfs_inode, struct file *file) +{ + (void)vfs_inode; + /* Don't free private_data here - it's managed by the inode */ + file->private_data = NULL; + return 0; +} + +static const struct file_operations fat32_file_ops = { + .read = fat32_file_read, + .write = fat32_file_write, + .open = fat32_file_open, + .release = fat32_file_release, + .llseek = NULL, + .readdir = NULL, + .ioctl = NULL, + .mmap = NULL, +}; + +/* ===================================================================== */ +/* Directory Operations (VFS integration) */ +/* ===================================================================== */ + +struct fat32_readdir_ctx { + void *user_ctx; + int (*filldir)(void *, const char *, int, loff_t, ino_t, unsigned); + loff_t pos; +}; + +static int fat32_readdir_callback(void *ctx, const char *name, + uint8_t attr, uint32_t size, + uint32_t cluster) +{ + struct fat32_readdir_ctx *rctx = (struct fat32_readdir_ctx *)ctx; + (void)size; + + unsigned type = (attr & FAT_ATTR_DIRECTORY) ? (S_IFDIR >> 12) : (S_IFREG >> 12); + int name_len = 0; + while (name[name_len]) name_len++; + + if (rctx->filldir) { + rctx->filldir(rctx->user_ctx, name, name_len, rctx->pos++, + cluster, type); + } + + return 0; +} + +static int fat32_dir_readdir(struct file *file, void *ctx, + int (*filldir)(void *, const char *, int, + loff_t, ino_t, unsigned)) +{ + struct fat32_file *ff = (struct fat32_file *)file->private_data; + if (!ff || !ff->fs) { + return -1; + } + + struct fat32_readdir_ctx rctx = { + .user_ctx = ctx, + .filldir = filldir, + .pos = 0 + }; + + /* Add . and .. entries */ + if (filldir) { + filldir(ctx, ".", 1, rctx.pos++, ff->start_cluster, S_IFDIR >> 12); + filldir(ctx, "..", 2, rctx.pos++, ff->start_cluster, S_IFDIR >> 12); + } + + return fat32_list_dir(ff->fs, ff->start_cluster, &rctx, + fat32_readdir_callback); +} + +static const struct file_operations fat32_dir_ops = { + .read = NULL, + .write = NULL, + .open = fat32_file_open, + .release = fat32_file_release, + .llseek = NULL, + .readdir = fat32_dir_readdir, + .ioctl = NULL, + .mmap = NULL, +}; + +/* ===================================================================== */ +/* Inode Operations */ +/* ===================================================================== */ + +static struct inode_operations fat32_inode_ops; + +static struct dentry *fat32_lookup(struct inode *dir, struct dentry *dentry) +{ + struct fat32_file *dir_file = (struct fat32_file *)dir->i_private; + if (!dir_file || !dir_file->fs) { + return NULL; + } + + struct fat32_dir_entry entry; + uint32_t entry_cluster = 0, entry_offset = 0; + if (fat32_find_entry(dir_file->fs, dir_file->start_cluster, + dentry->d_name, &entry, + &entry_cluster, &entry_offset) < 0) { + return NULL; + } + + /* Create fat32_file for this entry */ + struct fat32_file *ff = kzalloc(sizeof(struct fat32_file), GFP_KERNEL); + if (!ff) { + return NULL; + } + + ff->fs = dir_file->fs; + ff->start_cluster = ((uint32_t)entry.cluster_high << 16) | entry.cluster_low; + ff->current_cluster = ff->start_cluster; + ff->file_size = entry.file_size; + ff->attr = entry.attr; + ff->position = 0; + ff->cluster_offset = 0; + ff->dir_cluster = entry_cluster; + ff->dir_entry_offset = entry_offset; + + /* Copy name */ + int i; + for (i = 0; i < NAME_MAX && dentry->d_name[i]; i++) { + ff->name[i] = dentry->d_name[i]; + } + ff->name[i] = '\0'; + + /* Create VFS inode */ + struct inode *inode = kzalloc(sizeof(struct inode), GFP_KERNEL); + if (!inode) { + kfree(ff); + return NULL; + } + + inode->i_ino = ff->start_cluster; + inode->i_mode = (entry.attr & FAT_ATTR_DIRECTORY) ? + (S_IFDIR | 0755) : (S_IFREG | 0644); + if (entry.attr & FAT_ATTR_READ_ONLY) { + inode->i_mode &= ~0222; /* Remove write permissions */ + } + inode->i_size = entry.file_size; + inode->i_sb = dir->i_sb; + inode->i_op = &fat32_inode_ops; + inode->i_fop = (entry.attr & FAT_ATTR_DIRECTORY) ? + &fat32_dir_ops : &fat32_file_ops; + inode->i_private = ff; + + dentry->d_inode = inode; + + return NULL; /* Success - dentry populated */ +} + +static int fat32_create(struct inode *dir, struct dentry *dentry, mode_t mode) +{ + struct fat32_file *dir_file = (struct fat32_file *)dir->i_private; + if (!dir_file || !dir_file->fs) { + return -EINVAL; + } + + struct fat32_fs *fs = dir_file->fs; + (void)mode; /* FAT32 doesn't use Unix permissions */ + + /* Check if file already exists */ + struct fat32_dir_entry existing; + if (fat32_find_entry(fs, dir_file->start_cluster, dentry->d_name, &existing, NULL, NULL) == 0) { + return -EEXIST; + } + + /* Allocate a cluster for the file (even empty files get one cluster) */ + uint32_t file_cluster = allocate_cluster(fs); + if (file_cluster == 0) { + return -ENOSPC; + } + + /* Zero the cluster */ + if (zero_cluster(fs, file_cluster) < 0) { + /* TODO: Free the allocated cluster on error */ + return -EIO; + } + + /* Add directory entry */ + uint32_t entry_cluster = 0, entry_offset = 0; + if (add_dir_entry(fs, dir_file->start_cluster, dentry->d_name, + FAT_ATTR_ARCHIVE, file_cluster, 0, + &entry_cluster, &entry_offset) < 0) { + /* TODO: Free the allocated cluster on error */ + return -EIO; + } + + /* Create fat32_file structure for the new file */ + struct fat32_file *ff = kzalloc(sizeof(struct fat32_file), GFP_KERNEL); + if (!ff) { + return -ENOMEM; + } + + ff->fs = fs; + ff->start_cluster = file_cluster; + ff->current_cluster = file_cluster; + ff->file_size = 0; + ff->attr = FAT_ATTR_ARCHIVE; + ff->position = 0; + ff->cluster_offset = 0; + ff->dir_cluster = entry_cluster; + ff->dir_entry_offset = entry_offset; + + /* Copy name */ + int i; + for (i = 0; i < NAME_MAX && dentry->d_name[i]; i++) { + ff->name[i] = dentry->d_name[i]; + } + ff->name[i] = '\0'; + + /* Create VFS inode */ + struct inode *inode = kzalloc(sizeof(struct inode), GFP_KERNEL); + if (!inode) { + kfree(ff); + return -ENOMEM; + } + + inode->i_ino = file_cluster; + inode->i_mode = S_IFREG | 0644; + inode->i_size = 0; + inode->i_sb = dir->i_sb; + inode->i_op = &fat32_inode_ops; + inode->i_fop = &fat32_file_ops; + inode->i_private = ff; + + dentry->d_inode = inode; + + printk(KERN_INFO "FAT32: Created file '%s' at cluster %u\n", + dentry->d_name, file_cluster); + + return 0; +} + +static int fat32_mkdir(struct inode *dir, struct dentry *dentry, mode_t mode) +{ + struct fat32_file *dir_file = (struct fat32_file *)dir->i_private; + if (!dir_file || !dir_file->fs) { + return -EINVAL; + } + + struct fat32_fs *fs = dir_file->fs; + (void)mode; /* FAT32 doesn't use Unix permissions */ + + /* Check if directory already exists */ + struct fat32_dir_entry existing; + if (fat32_find_entry(fs, dir_file->start_cluster, dentry->d_name, &existing, NULL, NULL) == 0) { + return -EEXIST; + } + + /* Allocate a cluster for the new directory */ + uint32_t new_cluster = allocate_cluster(fs); + if (new_cluster == 0) { + return -ENOSPC; + } + + /* Zero the cluster */ + if (zero_cluster(fs, new_cluster) < 0) { + return -EIO; + } + + /* Create . and .. entries in the new directory */ + uint8_t *cluster_buf = kzalloc(fs->cluster_size, GFP_KERNEL); + if (!cluster_buf) { + return -ENOMEM; + } + + struct fat32_dir_entry *entries = (struct fat32_dir_entry *)cluster_buf; + + /* "." entry - points to self */ + for (int i = 0; i < 8; i++) entries[0].name[i] = ' '; + entries[0].name[0] = '.'; + for (int i = 0; i < 3; i++) entries[0].ext[i] = ' '; + entries[0].attr = FAT_ATTR_DIRECTORY; + entries[0].cluster_high = (new_cluster >> 16) & 0xFFFF; + entries[0].cluster_low = new_cluster & 0xFFFF; + entries[0].file_size = 0; + entries[0].create_time = 0x6000; + entries[0].create_date = 0x5821; + entries[0].modify_time = 0x6000; + entries[0].modify_date = 0x5821; + + /* ".." entry - points to parent */ + for (int i = 0; i < 8; i++) entries[1].name[i] = ' '; + entries[1].name[0] = '.'; + entries[1].name[1] = '.'; + for (int i = 0; i < 3; i++) entries[1].ext[i] = ' '; + entries[1].attr = FAT_ATTR_DIRECTORY; + uint32_t parent_cluster = dir_file->start_cluster; + /* Root directory has cluster 0 for ".." in FAT32 */ + if (parent_cluster == fs->root_cluster) { + parent_cluster = 0; + } + entries[1].cluster_high = (parent_cluster >> 16) & 0xFFFF; + entries[1].cluster_low = parent_cluster & 0xFFFF; + entries[1].file_size = 0; + entries[1].create_time = 0x6000; + entries[1].create_date = 0x5821; + entries[1].modify_time = 0x6000; + entries[1].modify_date = 0x5821; + + /* Write the new directory cluster */ + if (write_cluster(fs, new_cluster, cluster_buf) < 0) { + kfree(cluster_buf); + return -EIO; + } + kfree(cluster_buf); + + /* Add entry in parent directory */ + if (add_dir_entry(fs, dir_file->start_cluster, dentry->d_name, + FAT_ATTR_DIRECTORY, new_cluster, 0, NULL, NULL) < 0) { + return -EIO; + } + + /* Create fat32_file structure for the new directory */ + struct fat32_file *ff = kzalloc(sizeof(struct fat32_file), GFP_KERNEL); + if (!ff) { + return -ENOMEM; + } + + ff->fs = fs; + ff->start_cluster = new_cluster; + ff->current_cluster = new_cluster; + ff->file_size = 0; + ff->attr = FAT_ATTR_DIRECTORY; + ff->position = 0; + ff->cluster_offset = 0; + + /* Copy name */ + int i; + for (i = 0; i < NAME_MAX && dentry->d_name[i]; i++) { + ff->name[i] = dentry->d_name[i]; + } + ff->name[i] = '\0'; + + /* Create VFS inode */ + struct inode *inode = kzalloc(sizeof(struct inode), GFP_KERNEL); + if (!inode) { + kfree(ff); + return -ENOMEM; + } + + inode->i_ino = new_cluster; + inode->i_mode = S_IFDIR | 0755; + inode->i_size = 0; + inode->i_sb = dir->i_sb; + inode->i_op = &fat32_inode_ops; + inode->i_fop = &fat32_dir_ops; + inode->i_private = ff; + + dentry->d_inode = inode; + + printk(KERN_INFO "FAT32: Created directory '%s' at cluster %u\n", + dentry->d_name, new_cluster); + + return 0; +} + +static struct inode_operations fat32_inode_ops = { + .lookup = fat32_lookup, + .create = fat32_create, + .mkdir = fat32_mkdir, + .rmdir = NULL, + .unlink = NULL, + .rename = NULL, +}; + +/* ===================================================================== */ +/* Mount Operations */ +/* ===================================================================== */ + +static int fat32_read_sectors_wrapper(uint64_t sector, uint32_t count, void *buf) +{ + return virtio_blk_read(sector, count, buf); +} + +static int fat32_write_sectors_wrapper(uint64_t sector, uint32_t count, + const void *buf) +{ + return virtio_blk_write(sector, count, buf); +} + +static struct super_block *fat32_mount_internal(struct file_system_type *fs_type, + int flags, const char *dev_name, + void *data) +{ + (void)flags; + (void)data; + + printk(KERN_INFO "FAT32: Mounting filesystem from %s\n", dev_name); + + /* Check if virtio-blk is available */ + if (!virtio_blk_is_ready()) { + printk(KERN_ERR "FAT32: Block device not available\n"); + return NULL; + } + + /* Allocate filesystem structure */ + struct fat32_fs *fs = kzalloc(sizeof(struct fat32_fs), GFP_KERNEL); + if (!fs) { + return NULL; + } + + fs->read_sectors = fat32_read_sectors_wrapper; + fs->write_sectors = fat32_write_sectors_wrapper; + + /* Read boot sector */ + uint8_t boot_sector[512]; + if (virtio_blk_read(0, 1, boot_sector) < 0) { + printk(KERN_ERR "FAT32: Failed to read boot sector\n"); + kfree(fs); + return NULL; + } + + /* Copy BPB */ + for (size_t i = 0; i < sizeof(struct fat32_bpb); i++) { + ((uint8_t *)&fs->bpb)[i] = boot_sector[i]; + } + + /* Verify signature */ + uint16_t signature = boot_sector[510] | (boot_sector[511] << 8); + if (signature != FAT32_SIGNATURE) { + printk(KERN_ERR "FAT32: Invalid boot sector signature\n"); + kfree(fs); + return NULL; + } + + /* Verify this is FAT32 */ + if (fs->bpb.fat_size_16 != 0) { + printk(KERN_ERR "FAT32: Not a FAT32 filesystem\n"); + kfree(fs); + return NULL; + } + + /* Calculate filesystem parameters */ + fs->fat_start = fs->bpb.reserved_sectors; + fs->data_start = fs->fat_start + fs->bpb.num_fats * fs->bpb.fat_size_32; + fs->root_cluster = fs->bpb.root_cluster; + fs->cluster_size = fs->bpb.bytes_per_sector * fs->bpb.sectors_per_cluster; + + uint32_t total_sectors = fs->bpb.total_sectors_32; + uint32_t data_sectors = total_sectors - fs->data_start; + fs->total_clusters = data_sectors / fs->bpb.sectors_per_cluster; + + printk(KERN_INFO "FAT32: Bytes/sector: %d\n", fs->bpb.bytes_per_sector); + printk(KERN_INFO "FAT32: Sectors/cluster: %d\n", fs->bpb.sectors_per_cluster); + printk(KERN_INFO "FAT32: Cluster size: %d bytes\n", fs->cluster_size); + printk(KERN_INFO "FAT32: Total clusters: %d\n", fs->total_clusters); + printk(KERN_INFO "FAT32: Root cluster: %d\n", fs->root_cluster); + + /* Load FAT into memory (for small disks) */ + /* For large disks, use demand-loading */ + uint32_t fat_sectors = fs->bpb.fat_size_32; + uint32_t fat_bytes = fat_sectors * fs->bpb.bytes_per_sector; + + printk(KERN_INFO "FAT32: FAT size: %u sectors (%u bytes)\n", + fat_sectors, fat_bytes); + + if (fat_bytes < 4 * 1024 * 1024) { /* < 4MB FAT */ + fs->fat_cache = kmalloc(fat_bytes); + if (fs->fat_cache) { + fs->fat_cache_size = fat_bytes / 4; + + /* Read FAT in chunks to avoid issues with large reads */ + int success = 1; + uint8_t *cache_ptr = (uint8_t *)fs->fat_cache; + for (uint32_t s = 0; s < fat_sectors; s++) { + if (virtio_blk_read(fs->fat_start + s, 1, + cache_ptr + s * 512) < 0) { + success = 0; + break; + } + } + + if (!success) { + printk(KERN_WARNING "FAT32: Failed to cache FAT\n"); + kfree(fs->fat_cache); + fs->fat_cache = NULL; + fs->fat_cache_size = 0; + } else { + printk(KERN_INFO "FAT32: FAT cached (%u entries)\n", + fs->fat_cache_size); + /* Debug: show first few FAT entries */ + printk(KERN_DEBUG "FAT32: FAT[0]=0x%08x FAT[1]=0x%08x FAT[2]=0x%08x\n", + fs->fat_cache[0], fs->fat_cache[1], fs->fat_cache[2]); + } + } else { + printk(KERN_WARNING "FAT32: Could not allocate FAT cache\n"); + } + } else { + printk(KERN_INFO "FAT32: FAT too large for caching, using on-demand reads\n"); + } + + /* Create superblock */ + static struct super_block sb; + sb.s_blocksize = fs->cluster_size; + sb.s_type = fs_type; + sb.s_fs_info = fs; + + /* Create root file structure */ + struct fat32_file *root_file = kzalloc(sizeof(struct fat32_file), GFP_KERNEL); + if (!root_file) { + if (fs->fat_cache) kfree(fs->fat_cache); + kfree(fs); + return NULL; + } + + root_file->fs = fs; + root_file->start_cluster = fs->root_cluster; + root_file->current_cluster = fs->root_cluster; + root_file->file_size = 0; + root_file->attr = FAT_ATTR_DIRECTORY; + root_file->name[0] = '/'; + root_file->name[1] = '\0'; + + /* Create VFS root inode */ + static struct inode vfs_root_inode; + vfs_root_inode.i_ino = fs->root_cluster; + vfs_root_inode.i_mode = S_IFDIR | 0755; + vfs_root_inode.i_size = 0; + vfs_root_inode.i_sb = &sb; + vfs_root_inode.i_op = &fat32_inode_ops; + vfs_root_inode.i_fop = &fat32_dir_ops; + vfs_root_inode.i_private = root_file; + + /* Create root dentry */ + static struct dentry root_dentry; + root_dentry.d_name[0] = '/'; + root_dentry.d_name[1] = '\0'; + root_dentry.d_inode = &vfs_root_inode; + root_dentry.d_parent = &root_dentry; + root_dentry.d_child = NULL; + root_dentry.d_sibling = NULL; + root_dentry.d_sb = &sb; + + sb.s_root = &root_dentry; + mounted_fs = fs; + + printk(KERN_INFO "FAT32: Filesystem mounted successfully\n"); + + return &sb; +} + +static void fat32_kill_sb(struct super_block *sb) +{ + struct fat32_fs *fs = (struct fat32_fs *)sb->s_fs_info; + if (fs) { + if (fs->fat_cache) { + kfree(fs->fat_cache); + } + kfree(fs); + } + mounted_fs = NULL; + printk(KERN_INFO "FAT32: Filesystem unmounted\n"); +} + +/* ===================================================================== */ +/* Filesystem Type */ +/* ===================================================================== */ + +static struct file_system_type fat32_fs_type = { + .name = "fat32", + .fs_flags = 0, + .mount = fat32_mount_internal, + .kill_sb = fat32_kill_sb, + .next = NULL, +}; + +/* ===================================================================== */ +/* Initialization */ +/* ===================================================================== */ + +int fat32_init(void) +{ + printk(KERN_INFO "FAT32: Registering FAT32 filesystem\n"); + return register_filesystem(&fat32_fs_type); +} + +/* ===================================================================== */ +/* Public API */ +/* ===================================================================== */ + +struct fat32_fs *fat32_get_mounted(void) +{ + return mounted_fs; +} + +int fat32_sync(void) +{ + if (!mounted_fs) { + return -1; + } + + /* Flush block device */ + return virtio_blk_flush(); +} diff --git a/kernel/fs/vfs.c b/kernel/fs/vfs.c index 1523500..e7ee977 100644 --- a/kernel/fs/vfs.c +++ b/kernel/fs/vfs.c @@ -320,6 +320,13 @@ struct file *vfs_open(const char *path, int flags, mode_t mode) f->f_op->open(child->d_inode, f); } + /* Handle O_TRUNC - reset file position and size to 0 */ + if (flags & O_TRUNC) { + f->f_pos = 0; + child->d_inode->i_size = 0; + /* Truncate operation will be handled by filesystem on write */ + } + return f; } diff --git a/kernel/gui/window.c b/kernel/gui/window.c index 05c9bae..e5886c5 100644 --- a/kernel/gui/window.c +++ b/kernel/gui/window.c @@ -1765,18 +1765,12 @@ static void draw_dock(void) int size = DOCK_ICON_SIZE; /* Check hover */ - /* Simple check against the horizontal slot */ - int slot_w = DOCK_ICON_SIZE + DOCK_PADDING; - int is_hovered = 0; - if (mouse_y >= dock_y && mouse_y < dock_y + dock_h && mouse_x >= icon_x && mouse_x < icon_x + DOCK_ICON_SIZE) { - is_hovered = 1; hovered_idx = i; size = DOCK_ICON_SIZE + 16; /* Scale up */ } - int draw_size = size; int draw_x = icon_x - (size - DOCK_ICON_SIZE) / 2; int draw_y = center_y - size / 2; @@ -2164,8 +2158,6 @@ static int prev_buttons = 0; void gui_handle_mouse_event(int x, int y, int buttons) { - int prev_x = mouse_x; - int prev_y = mouse_y; mouse_x = x; mouse_y = y; @@ -2373,11 +2365,12 @@ void gui_handle_mouse_event(int x, int y, int buttons) case 2: /* Calculator */ gui_create_window("Calculator", spawn_x + 60, spawn_y + 40, 200, 280); break; - case 3: /* Notepad */ + case 3: /* Notepad */ { /* Call open with NULL to just open blank */ extern void gui_open_notepad(const char *path); gui_open_notepad(NULL); break; + } case 4: /* Settings */ gui_create_window("Settings", spawn_x + 20, spawn_y + 30, 380, 320); break; @@ -2497,30 +2490,41 @@ struct window *gui_create_file_manager(int x, int y) static void notepad_on_mouse(struct window *win, int x, int y, int buttons) { + (void)win; + (void)buttons; /* Already filtered for clicks by gui_handle_mouse_event */ + /* Check Save Button */ /* Toolbar area */ int content_y = BORDER_WIDTH + TITLEBAR_HEIGHT; if (y >= content_y && y < content_y + 30) { if (x >= BORDER_WIDTH + 10 && x < BORDER_WIDTH + 70) { /* Save clicked */ + printk("Notepad: Save button clicked\n"); + if (notepad_filepath[0]) { - /* Open for writing */ - struct file *f = vfs_open(notepad_filepath, O_RDWR | O_CREAT, 0644); + /* Open for writing - use O_TRUNC to clear file first */ + struct file *f = vfs_open(notepad_filepath, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (f) { - /* Determine length */ - int len = 0; while(notepad_text[len] && len < NOTEPAD_MAX_TEXT) len++; + /* Determine text length */ + int len = 0; + while (notepad_text[len] && len < NOTEPAD_MAX_TEXT) len++; /* Write content */ extern ssize_t vfs_write(struct file *file, const char *buf, size_t count); - vfs_write(f, notepad_text, len); - /* Reset file position if we want to ensure we wrote from start? vfs_open sets pos 0. */ - - /* Hack: Force truncation in ramfs? For now just overwrite. */ + ssize_t written = vfs_write(f, notepad_text, len); vfs_close(f); - printk("Notepad: Saved %d bytes to %s\n", len, notepad_filepath); + if (written >= 0) { + printk("Notepad: Saved %d bytes to %s\n", (int)written, notepad_filepath); + } else { + printk("Notepad: Write failed for %s\n", notepad_filepath); + } + } else { + printk("Notepad: Could not open %s for writing\n", notepad_filepath); } + } else { + printk("Notepad: No file path set\n"); } } } @@ -2561,6 +2565,8 @@ void gui_open_notepad(const char *path) static void rename_on_mouse(struct window *win, int x, int y, int buttons) { + (void)buttons; /* Already filtered for clicks by gui_handle_mouse_event */ + /* Check Save Button */ int content_y = BORDER_WIDTH + TITLEBAR_HEIGHT; if (y >= content_y && y < content_y + 30) { diff --git a/kernel/include/drivers/block.h b/kernel/include/drivers/block.h new file mode 100644 index 0000000..861ddd1 --- /dev/null +++ b/kernel/include/drivers/block.h @@ -0,0 +1,147 @@ +/* + * Vib-OS Kernel - Block Device Interface + * + * Abstraction layer for block devices (virtio-blk, NVMe, etc.) + * Provides a unified API for filesystem drivers. + */ + +#ifndef _DRIVERS_BLOCK_H +#define _DRIVERS_BLOCK_H + +#include "types.h" + +/* ===================================================================== */ +/* Constants */ +/* ===================================================================== */ + +#define BLOCK_SECTOR_SIZE 512 +#define BLOCK_MAX_DEVICES 8 +#define BLOCK_NAME_MAX 32 + +/* ===================================================================== */ +/* Block device structure */ +/* ===================================================================== */ + +struct block_device; + +/* Block device operations */ +struct block_ops { + /* Read sectors from device */ + int (*read)(struct block_device *dev, uint64_t sector, + uint32_t count, void *buffer); + + /* Write sectors to device */ + int (*write)(struct block_device *dev, uint64_t sector, + uint32_t count, const void *buffer); + + /* Flush cached writes */ + int (*flush)(struct block_device *dev); + + /* Get device info */ + int (*get_info)(struct block_device *dev, void *info); +}; + +/* Block device */ +struct block_device { + char name[BLOCK_NAME_MAX]; /* Device name (e.g., "vda") */ + uint64_t capacity; /* Total sectors */ + uint32_t sector_size; /* Bytes per sector */ + uint32_t flags; /* Device flags */ + const struct block_ops *ops; /* Operations */ + void *private; /* Driver-specific data */ +}; + +/* Device flags */ +#define BLOCK_FLAG_READONLY (1 << 0) +#define BLOCK_FLAG_REMOVABLE (1 << 1) + +/* ===================================================================== */ +/* Block device registration */ +/* ===================================================================== */ + +/** + * block_register - Register a block device + * @dev: Block device structure to register + * Returns: 0 on success, negative error code on failure + */ +int block_register(struct block_device *dev); + +/** + * block_unregister - Unregister a block device + * @dev: Block device to unregister + */ +void block_unregister(struct block_device *dev); + +/** + * block_get_device - Get a block device by name + * @name: Device name + * Returns: Pointer to device, or NULL if not found + */ +struct block_device *block_get_device(const char *name); + +/** + * block_get_device_by_index - Get a block device by index + * @index: Device index (0-based) + * Returns: Pointer to device, or NULL if invalid index + */ +struct block_device *block_get_device_by_index(int index); + +/* ===================================================================== */ +/* Block I/O functions */ +/* ===================================================================== */ + +/** + * block_read - Read sectors from a block device + * @dev: Block device + * @sector: Starting sector number + * @count: Number of sectors to read + * @buffer: Destination buffer (must be count * sector_size bytes) + * Returns: 0 on success, negative error code on failure + */ +int block_read(struct block_device *dev, uint64_t sector, + uint32_t count, void *buffer); + +/** + * block_write - Write sectors to a block device + * @dev: Block device + * @sector: Starting sector number + * @count: Number of sectors to write + * @buffer: Source buffer (must be count * sector_size bytes) + * Returns: 0 on success, negative error code on failure + */ +int block_write(struct block_device *dev, uint64_t sector, + uint32_t count, const void *buffer); + +/** + * block_flush - Flush cached data to device + * @dev: Block device + * Returns: 0 on success, negative error code on failure + */ +int block_flush(struct block_device *dev); + +/* ===================================================================== */ +/* VirtIO Block Driver API */ +/* ===================================================================== */ + +/* Initialize the VirtIO block driver */ +int virtio_blk_init(void); + +/* Read sectors */ +int virtio_blk_read(uint64_t sector, uint32_t count, void *buffer); + +/* Write sectors */ +int virtio_blk_write(uint64_t sector, uint32_t count, const void *buffer); + +/* Flush pending writes */ +int virtio_blk_flush(void); + +/* Get disk capacity in sectors */ +uint64_t virtio_blk_get_capacity(void); + +/* Get sector size in bytes */ +uint32_t virtio_blk_get_sector_size(void); + +/* Check if device is ready */ +bool virtio_blk_is_ready(void); + +#endif /* _DRIVERS_BLOCK_H */ diff --git a/scripts/create-disk-image.sh b/scripts/create-disk-image.sh new file mode 100755 index 0000000..bd83134 --- /dev/null +++ b/scripts/create-disk-image.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# +# Vib-OS - Create FAT32 Disk Image +# +# Creates a RAW FAT32 disk image (no partition table) for persistent storage. +# Usage: ./create-disk-image.sh [size_mb] [output_path] +# + +set -e + +SIZE_MB=${1:-64} +OUTPUT_PATH=${2:-image/disk.img} + +echo "===================================" +echo "Vib-OS Disk Image Creator" +echo "===================================" +echo "Size: ${SIZE_MB}MB" +echo "Output: ${OUTPUT_PATH}" +echo "" + +# Create output directory if needed +mkdir -p "$(dirname "$OUTPUT_PATH")" + +# Check if image already exists +if [ -f "$OUTPUT_PATH" ]; then + echo "Disk image already exists: $OUTPUT_PATH" + if [ -t 0 ]; then + read -p "Overwrite? [y/N] " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 0 + fi + fi + rm -f "$OUTPUT_PATH" +fi + +echo "Creating ${SIZE_MB}MB disk image..." +dd if=/dev/zero of="$OUTPUT_PATH" bs=1M count="$SIZE_MB" 2>/dev/null + +# Format as raw FAT32 (no partition table) using mtools +OS=$(uname -s) + +# Try mtools first (works on both macOS and Linux, creates raw FAT32) +if command -v mformat &> /dev/null; then + echo "Formatting as raw FAT32 with mtools..." + mformat -F -v VIBOS -i "$OUTPUT_PATH" :: + echo "FAT32 filesystem created" +elif [ "$OS" = "Linux" ] && command -v mkfs.vfat &> /dev/null; then + echo "Formatting as FAT32 with mkfs.vfat..." + mkfs.vfat -F 32 -n VIBOS "$OUTPUT_PATH" + echo "FAT32 filesystem created" +else + echo "Error: No FAT32 formatting tool found!" + echo "" + echo "Install mtools:" + if [ "$OS" = "Darwin" ]; then + echo " brew install mtools" + else + echo " sudo apt install mtools" + fi + exit 1 +fi + +# Create sample files if mtools is available +if command -v mcopy &> /dev/null; then + echo "" + echo "Adding sample files..." + + # Create temp directory with sample content + TMPDIR=$(mktemp -d) + echo "Welcome to Vib-OS!" > "$TMPDIR/readme.txt" + echo "This file is stored on a persistent FAT32 disk." >> "$TMPDIR/readme.txt" + echo "Data will survive reboots!" >> "$TMPDIR/readme.txt" + + # Copy to disk image + mcopy -i "$OUTPUT_PATH" "$TMPDIR/readme.txt" ::/README.TXT 2>/dev/null || true + mmd -i "$OUTPUT_PATH" ::/DOCUMENTS 2>/dev/null || true + + rm -rf "$TMPDIR" + echo "Sample files added" + + # Show contents + echo "" + echo "Disk contents:" + mdir -i "$OUTPUT_PATH" :: +fi + +echo "" +echo "===================================" +echo "Disk image created successfully!" +echo "===================================" +echo "" +echo "To use with QEMU:" +echo " make run-disk" +echo "" From 8eed12f85558e4710d31d10716958b5891880879 Mon Sep 17 00:00:00 2001 From: Garren Date: Sun, 18 Jan 2026 11:47:12 +0800 Subject: [PATCH 2/5] Update Makefile and clean up whitespace in FAT32 and VFS files - Changed CFLAGS in the Makefile to use the GNU11 standard for better compatibility. - Removed unnecessary whitespace in the fat32_file_open function in kernel/fs/fat32.c. - Cleaned up whitespace in the vfs_open function in kernel/fs/vfs.c for improved code readability. --- Makefile | 2 +- kernel/fs/fat32.c | 4 ++-- kernel/fs/vfs.c | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index b1227ca..689d8f7 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ CROSS_TARGET := --target=aarch64-unknown-none-elf # Compiler flags # CPU target: generic works on QEMU and most ARM64 hardware -CFLAGS_COMMON := -Wall -Wextra -Wno-unused-function -ffreestanding -fno-stack-protector \ +CFLAGS_COMMON := -std=gnu11 -Wall -Wextra -Wno-unused-function -ffreestanding -fno-stack-protector \ -fno-pic -mcpu=cortex-a72 -O2 -g CFLAGS_KERNEL := $(CFLAGS_COMMON) $(CROSS_TARGET) \ diff --git a/kernel/fs/fat32.c b/kernel/fs/fat32.c index 5cd5599..e46ae62 100644 --- a/kernel/fs/fat32.c +++ b/kernel/fs/fat32.c @@ -808,7 +808,7 @@ static ssize_t fat32_file_write(struct file *file, const char *buf, static int fat32_file_open(struct inode *vfs_inode, struct file *file) { file->private_data = vfs_inode->i_private; - + /* Handle O_TRUNC - reset file size to 0 */ if (file->f_flags & O_TRUNC) { struct fat32_file *ff = (struct fat32_file *)file->private_data; @@ -822,7 +822,7 @@ static int fat32_file_open(struct inode *vfs_inode, struct file *file) printk(KERN_DEBUG "FAT32: Truncated file to 0 bytes\n"); } } - + return 0; } diff --git a/kernel/fs/vfs.c b/kernel/fs/vfs.c index e7ee977..36b1996 100644 --- a/kernel/fs/vfs.c +++ b/kernel/fs/vfs.c @@ -308,25 +308,25 @@ struct file *vfs_open(const char *path, int flags, mode_t mode) struct file *f = kzalloc(sizeof(struct file), GFP_KERNEL); if (!f) return NULL; - + f->f_dentry = child; f->f_op = child->d_inode->i_fop; f->private_data = child->d_inode->i_private; f->f_mode = mode; f->f_flags = flags; f->f_count.counter = 1; - + if (f->f_op && f->f_op->open) { f->f_op->open(child->d_inode, f); } - + /* Handle O_TRUNC - reset file position and size to 0 */ if (flags & O_TRUNC) { f->f_pos = 0; child->d_inode->i_size = 0; /* Truncate operation will be handled by filesystem on write */ } - + return f; } From 0bbee0a78a15abdef490d4a91e0822738a9dbeb5 Mon Sep 17 00:00:00 2001 From: Garren Date: Sun, 18 Jan 2026 11:48:55 +0800 Subject: [PATCH 3/5] Remove unused global terminal pointer and clean up keyboard handler in main.c --- kernel/core/main.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/kernel/core/main.c b/kernel/core/main.c index 6708e5e..6139260 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -286,10 +286,6 @@ static void init_subsystems(void *dtb) * start_init_process - Start the first userspace process (PID 1) */ -/* Global terminal pointer for keyboard callback */ -static void *g_active_terminal = 0; - -/* Keyboard callback wrapper */ /* Keyboard callback wrapper */ static void keyboard_handler(int key) { From 05e0757f6f97bc5bf28166158603f9c00cfd30ea Mon Sep 17 00:00:00 2001 From: Garren Date: Tue, 20 Jan 2026 20:56:52 +0800 Subject: [PATCH 4/5] Remove unused draw_dock function from window.c to streamline GUI code and improve maintainability. --- kernel/gui/window.c | 111 -------------------------------------------- 1 file changed, 111 deletions(-) diff --git a/kernel/gui/window.c b/kernel/gui/window.c index dd05c17..1b11b81 100644 --- a/kernel/gui/window.c +++ b/kernel/gui/window.c @@ -2677,117 +2677,6 @@ static void draw_filled_circle(int cx, int cy, int r, uint32_t color) { } } -/* Draw dock with hover animations */ -static void draw_dock(void) -{ - int dock_content_w = NUM_DOCK_ICONS * (DOCK_ICON_SIZE + DOCK_PADDING) - DOCK_PADDING + 32; - int dock_x = (primary_display.width - dock_content_w) / 2; - int dock_y = primary_display.height - DOCK_HEIGHT + 6; - int dock_h = DOCK_HEIGHT - 12; - - /* Frosted glass dock background */ - uint32_t glass_base = 0x404050; - draw_rounded_rect(dock_x, dock_y, dock_content_w, dock_h, 16, glass_base); - - /* Top highlight */ - for (int i = dock_x + 16; i < dock_x + dock_content_w - 16; i++) { - draw_pixel(i, dock_y, 0x606070); - draw_pixel(i, dock_y + 1, 0x505060); - } - - /* Inner glow */ - for (int i = dock_x + 16; i < dock_x + dock_content_w - 16; i++) { - draw_pixel(i, dock_y + dock_h - 1, 0x303040); - draw_pixel(i, dock_y + dock_h - 2, 0x353545); - } - - /* Draw icons */ - int icon_x = dock_x + 16; - int center_y = dock_y + dock_h / 2; - - /* Checking for hover - if we find one, we'll draw it last so the label is on top */ - int hovered_idx = -1; - - for (int i = 0; i < NUM_DOCK_ICONS; i++) { - /* Standard size/pos */ - int size = DOCK_ICON_SIZE; - - /* Check hover */ - if (mouse_y >= dock_y && mouse_y < dock_y + dock_h && - mouse_x >= icon_x && mouse_x < icon_x + DOCK_ICON_SIZE) { - hovered_idx = i; - size = DOCK_ICON_SIZE + 16; /* Scale up */ - } - - int draw_x = icon_x - (size - DOCK_ICON_SIZE) / 2; - int draw_y = center_y - size / 2; - - /* Background */ - int icon_bg_x = draw_x - 2; - int icon_bg_y = draw_y - 2; - int icon_bg_size = size + 4; - int icon_r = 10; - - uint32_t bg_color = icon_colors[i]; - - /* Draw background shape */ - gui_draw_rect(icon_bg_x + icon_r, icon_bg_y, icon_bg_size - 2*icon_r, icon_bg_size, bg_color); - gui_draw_rect(icon_bg_x, icon_bg_y + icon_r, icon_bg_size, icon_bg_size - 2*icon_r, bg_color); - - /* Corners */ - for (int dy = -icon_r; dy <= icon_r; dy++) { - for (int dx = -icon_r; dx <= icon_r; dx++) { - if (dx*dx + dy*dy <= icon_r*icon_r) { - draw_pixel(icon_bg_x + icon_r + dx, icon_bg_y + icon_r + dy, bg_color); - draw_pixel(icon_bg_x + icon_bg_size - icon_r - 1 + dx, icon_bg_y + icon_r + dy, bg_color); - draw_pixel(icon_bg_x + icon_r + dx, icon_bg_y + icon_bg_size - icon_r - 1 + dy, bg_color); - draw_pixel(icon_bg_x + icon_bg_size - icon_r - 1 + dx, icon_bg_y + icon_bg_size - icon_r - 1 + dy, bg_color); - } - } - } - - /* Icon bitmap */ - draw_icon(draw_x, draw_y, size, dock_icons_bmp[i], 0xFFFFFF, bg_color); - - icon_x += DOCK_ICON_SIZE + DOCK_PADDING; - } - - /* Draw label for hovered item on top */ - if (hovered_idx >= 0) { - const char *label = dock_labels[hovered_idx]; - - /* Re-calculate position for this icon */ - int idx_x = dock_x + 16 + hovered_idx * (DOCK_ICON_SIZE + DOCK_PADDING); - int size = DOCK_ICON_SIZE + 16; - int draw_x = idx_x - (size - DOCK_ICON_SIZE) / 2; - int draw_y = center_y - size / 2; - - /* Label box above icon */ - int label_len = 0; - while (label[label_len]) label_len++; - int label_w = label_len * 8 + 16; - int label_h = 24; - int label_x = draw_x + (size - label_w) / 2; - int label_y = draw_y - 30; - - /* Draw label background */ - draw_rounded_rect(label_x, label_y, label_w, label_h, 6, 0x303040); - gui_draw_rect_outline(label_x, label_y, label_w, label_h, 0x505060, 1); - - /* Draw text */ - gui_draw_string(label_x + 8, label_y + 4, label, 0xFFFFFF, 0x303040); - - /* Little triangle pointing down */ - int tri_x = label_x + label_w / 2; - int tri_y = label_y + label_h; - for (int i = 0; i < 4; i++) { - for (int j = -i; j <= i; j++) { - draw_pixel(tri_x + j, tri_y + i, 0x303040); - } - } - } -} - /* Draw Terminal icon - simple bold >_ */ static void draw_icon_terminal(int x, int y, int size) { int m = size / 5; From da1810d32a7749fe1100d792bc24a6d40030e438 Mon Sep 17 00:00:00 2001 From: Garren Date: Tue, 20 Jan 2026 21:04:23 +0800 Subject: [PATCH 5/5] Refactor window.c: Comment out unused variables and add TODOs for terminal scrolling and wallpaper caching implementation. Adjust mouse event handling and button layout for notepad functionality. Clean up dock drawing logic and prepare for future tooltip implementation. --- kernel/gui/window.c | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/kernel/gui/window.c b/kernel/gui/window.c index 1b11b81..09f83f8 100644 --- a/kernel/gui/window.c +++ b/kernel/gui/window.c @@ -261,7 +261,7 @@ static char term_input[TERM_INPUT_MAX]; static int term_input_len = 0; static char term_history[TERM_HISTORY_LINES][80]; static int term_history_count = 0; -static int term_scroll = 0; +/* static int term_scroll = 0; */ /* TODO: implement terminal scrolling */ /* Snake game state */ #define SNAKE_MAX_LEN 100 @@ -2800,7 +2800,7 @@ static void draw_icon_web(int x, int y, int size) { /* Draw dock with hover animations - using vector icons */ static void draw_dock(void) { - int mouse_active = (mouse_y >= primary_display.height - DOCK_HEIGHT - 40); + int mouse_active = (mouse_y >= (int)(primary_display.height - DOCK_HEIGHT - 40)); /* 1. Calculate target sizes for all icons based on magnification */ int icon_sizes[NUM_DOCK_ICONS]; @@ -2858,7 +2858,7 @@ static void draw_dock(void) { if (i < NUM_DOCK_ICONS - 1) total_content_w += DOCK_PADDING; } - int dock_content_w = total_content_w; /* Used by old code too */ + (void)total_content_w; /* Was dock_content_w - kept for potential future use */ int dock_w = total_content_w + 32; /* Padding */ int dock_h = DOCK_HEIGHT - 12; int dock_x = (primary_display.width - dock_w) / 2; @@ -2992,7 +2992,7 @@ static void draw_dock(void) { } /* Cached wallpaper for performance - gradient is expensive to recalculate */ -static uint32_t *cached_wallpaper = NULL; +/* static uint32_t *cached_wallpaper = NULL; */ /* TODO: implement wallpaper caching */ static int wallpaper_cached = 0; static int wallpaper_cached_idx = -1; /* Which wallpaper is cached */ @@ -3410,8 +3410,6 @@ static int resize_start_win_x = 0, resize_start_win_y = 0; #define MIN_WINDOW_HEIGHT 100 void gui_handle_mouse_event(int x, int y, int buttons) { - int prev_x = mouse_x; - int prev_y = mouse_y; mouse_x = x; mouse_y = y; @@ -3432,7 +3430,7 @@ void gui_handle_mouse_event(int x, int y, int buttons) { /* Track for double-click detection */ static int last_click_x = 0, last_click_y = 0; - static uint64_t last_click_time = 0; + /* static uint64_t last_click_time = 0; */ /* TODO: implement double-click timing */ static int click_count = 0; /* Handle window dragging */ @@ -3938,10 +3936,11 @@ void gui_handle_mouse_event(int x, int y, int buttons) { case 2: /* Calculator */ gui_create_window("Calculator", spawn_x + 60, spawn_y + 40, 260, 380); break; - case 3: /* Notepad */ + case 3: /* Notepad */ { /* Call open with NULL to just open blank */ extern void gui_open_notepad(const char *path); gui_open_notepad(NULL); + } break; case 4: /* Settings */ gui_create_window("Settings", spawn_x + 20, spawn_y + 30, 380, 320); @@ -4184,15 +4183,27 @@ struct window *gui_create_file_manager_path(int x, int y, const char *path) { } static void notepad_on_mouse(struct window *win, int x, int y, int buttons) { - /* Check Save Button */ - /* Toolbar area */ + (void)win; + + /* Only process on left mouse button click */ + if (!(buttons & 1)) { + return; + } + + /* Toolbar button layout (matching draw code): + * bx starts at BORDER_WIDTH + 8, buttons are 50px wide with 4px spacing + * New: BORDER_WIDTH + 8 to BORDER_WIDTH + 58 + * Open: BORDER_WIDTH + 62 to BORDER_WIDTH + 112 + * Save: BORDER_WIDTH + 116 to BORDER_WIDTH + 166 + */ int content_y = BORDER_WIDTH + TITLEBAR_HEIGHT; if (y >= content_y && y < content_y + 30) { - if (x >= BORDER_WIDTH + 10 && x < BORDER_WIDTH + 70) { + /* Check Save Button (third button) */ + if (x >= BORDER_WIDTH + 116 && x < BORDER_WIDTH + 166) { /* Save clicked */ if (notepad_filepath[0]) { - /* Open for writing */ - struct file *f = vfs_open(notepad_filepath, O_RDWR | O_CREAT, 0644); + /* Open for writing with truncate to clear existing content */ + struct file *f = vfs_open(notepad_filepath, O_RDWR | O_CREAT | O_TRUNC, 0644); if (f) { /* Determine length */ int len = 0; @@ -4255,6 +4266,7 @@ void gui_open_notepad(const char *path) { } static void rename_on_mouse(struct window *win, int x, int y, int buttons) { + (void)buttons; /* Used for click detection via coordinate check */ /* Check Save Button */ int content_y = BORDER_WIDTH + TITLEBAR_HEIGHT; if (y >= content_y && y < content_y + 30) { @@ -4661,9 +4673,9 @@ static void image_viewer_on_draw(struct window *win) { int btn_x = tb_x + 16; /* Button icons (using ASCII for now) */ - const char *icons[] = {"<", ">", "R", "L", "+", "-", "F", "X"}; - const char *labels[] = {"Prev", "Next", "Rot R", "Rot L", - "Zoom+", "Zoom-", "Fit", "Full"}; + /* TODO: Use these for button tooltips */ + /* const char *icons[] = {"<", ">", "R", "L", "+", "-", "F", "X"}; */ + /* const char *labels[] = {"Prev", "Next", "Rot R", "Rot L", "Zoom+", "Zoom-", "Fit", "Full"}; */ uint32_t btn_bg = 0x374151; uint32_t btn_hover = 0x4B5563; uint32_t icon_color = 0xE5E7EB;