#include "ext2.h" #include "arch/x86_64/ata.h" #include "mm/memory.h" #include "stdio.h" #include #include "string.h" #include "mp/spinlock.h" #include "arch/x86_64/pit.h" // ── module state ───────────────────────────────────────────────────────────── static spinlock_t s_ext2_lock = SPINLOCK_INIT; static ext2_superblock_t sb; static ext2_group_desc_t* gdt; static uint32_t num_groups; static uint32_t block_size; // ── utilities ──────────────────────────────────────────────────────────────── static inline uint32_t align4(uint32_t n) { return (n + 3u) & ~3u; } const char* ext2_file_type_string(uint8_t type) { switch (type) { case EXT2_FT_REG_FILE: return "File"; case EXT2_FT_DIR: return "Directory"; case EXT2_FT_CHRDEV: return "Char Device"; case EXT2_FT_BLKDEV: return "Block Device"; case EXT2_FT_FIFO: return "FIFO"; case EXT2_FT_SOCK: return "Socket"; case EXT2_FT_SYMLINK: return "Symlink"; default: return "Unknown"; } } // ── low-level block I/O ────────────────────────────────────────────────────── // RAW — no lock. Called only from within _internal functions. static bool ext2_read_block_raw(uint32_t block_num, void* buf) { return ata_read_sectors(block_num * (block_size / 512), block_size / 512, buf); } static bool ext2_write_block_raw(uint32_t block_num, const void* buf) { return ata_write_sectors(block_num * (block_size / 512), block_size / 512, buf); } // PUBLIC — acquires lock. Called from outside the module. bool ext2_read_block(uint32_t block_num, void* buf) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool ok = ext2_read_block_raw(block_num, buf); spinlock_release_irqrestore(&s_ext2_lock, flags); return ok; } bool ext2_write_block(uint32_t block_num, const void* buf) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool ok = ext2_write_block_raw(block_num, buf); spinlock_release_irqrestore(&s_ext2_lock, flags); return ok; } // ── superblock / GDT ───────────────────────────────────────────────────────── bool ext2_read_superblock_internal(void) { uint8_t* tmp = kmalloc(1024); if (!tmp) return false; if (!ata_read_sectors(2, 2, tmp)) { printf("EXT2: failed to read superblock\n"); kfree(tmp); return false; } memcpy(&sb, tmp, sizeof(ext2_superblock_t)); kfree(tmp); if (sb.s_magic != EXT2_MAGIC) { printf("EXT2: bad magic 0x%04x\n", sb.s_magic); return false; } block_size = 1024u << sb.s_log_block_size; num_groups = (sb.s_blocks_count + sb.s_blocks_per_group - 1) / sb.s_blocks_per_group; printf("EXT2: OK block_size=%u groups=%u\n", block_size, num_groups); return true; } bool ext2_read_superblock(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_superblock_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } static bool ext2_write_superblock_internal(void) { uint8_t* tmp = kmalloc(1024); printf("test4"); if (!tmp) return false; memset(tmp, 0, 1024); memcpy(tmp, &sb, sizeof(ext2_superblock_t)); bool ok = ata_write_sectors(2, 2, tmp); kfree(tmp); return ok; } static bool ext2_write_superblock(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_write_superblock_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_read_group_desc_table_internal(void) { // GDT immediately follows the superblock block uint32_t gdt_block = (block_size == 1024) ? 2 : 1; uint32_t bytes = num_groups * sizeof(ext2_group_desc_t); uint32_t nblocks = (bytes + block_size - 1) / block_size; uint8_t* buf = kmalloc(nblocks * block_size); if (!buf) { printf("nah"); return false; } for (uint32_t i = 0; i < nblocks; i++) { if (!ext2_read_block_raw(gdt_block + i, buf + i * block_size)) { kfree(buf); return false; } } gdt = kmalloc(bytes); if (!gdt) { kfree(buf); return false; } memcpy(gdt, buf, bytes); kfree(buf); return true; } bool ext2_read_group_desc_table(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_group_desc_table_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } static bool ext2_write_gdt_internal(void) { uint32_t gdt_block = (block_size == 1024) ? 2 : 1; uint32_t bytes = num_groups * sizeof(ext2_group_desc_t); uint32_t nblocks = (bytes + block_size - 1) / block_size; uint8_t* buf = kmalloc(nblocks * block_size); if (!buf) return false; memset(buf, 0, nblocks * block_size); memcpy(buf, gdt, bytes); bool ok = true; for (uint32_t i = 0; i < nblocks && ok; i++) ok = ext2_write_block_raw(gdt_block + i, buf + i * block_size); kfree(buf); return ok; } static bool ext2_write_gdt(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_write_gdt_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } static void ext2_sync(void) { printf("test1"); ext2_write_superblock_internal(); printf("test2"); ext2_write_gdt_internal(); printf("test3"); } // ── bitmap allocators ───────────────────────────────────────────────────────── // Returns physical block number, or 0 on failure. // KEY FIX: block number = group * blocks_per_group + bit_index // NOTE: calls ext2_read_block_raw / ext2_write_block_raw — NOT the locking versions static uint32_t ext2_alloc_block_internal(void) { uint8_t* bm = kmalloc(block_size); if (!bm) return 0; for (uint32_t g = 0; g < num_groups; g++) { if (gdt[g].bg_free_blocks_count == 0) continue; ext2_read_block_raw(gdt[g].bg_block_bitmap, bm); // _raw, not locking uint32_t limit = sb.s_blocks_per_group; if (g == num_groups - 1 && sb.s_blocks_count % sb.s_blocks_per_group) limit = sb.s_blocks_count % sb.s_blocks_per_group; for (uint32_t i = 0; i < limit; i++) { if (bm[i / 8] & (1u << (i % 8))) continue; bm[i / 8] |= (1u << (i % 8)); ext2_write_block_raw(gdt[g].bg_block_bitmap, bm); // _raw, not locking gdt[g].bg_free_blocks_count--; sb.s_free_blocks_count--; kfree(bm); return g * sb.s_blocks_per_group + i; } } kfree(bm); return 0; } static uint32_t ext2_alloc_block(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); uint32_t resp = ext2_alloc_block_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } static void ext2_free_block_internal(uint32_t blk) { if (!blk) return; uint32_t g = blk / sb.s_blocks_per_group; uint32_t i = blk % sb.s_blocks_per_group; uint8_t* bm = kmalloc(block_size); if (!bm) return; ext2_read_block_raw(gdt[g].bg_block_bitmap, bm); bm[i / 8] &= ~(1u << (i % 8)); ext2_write_block_raw(gdt[g].bg_block_bitmap, bm); gdt[g].bg_free_blocks_count++; sb.s_free_blocks_count++; kfree(bm); } static void ext2_free_block(uint32_t blk) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); ext2_free_block_internal(blk); spinlock_release_irqrestore(&s_ext2_lock, flags); } static uint32_t alloc_zero_block_internal(void) { uint32_t b = ext2_alloc_block_internal(); // _internal, not locking if (!b) return 0; uint8_t* z = kmalloc(block_size); if (!z) { ext2_free_block_internal(b); return 0; } memset(z, 0, block_size); ext2_write_block_raw(b, z); // _raw kfree(z); return b; } static uint32_t alloc_zero_block(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); uint32_t resp = alloc_zero_block_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // Returns 1-based inode number, or 0 on failure. static uint32_t ext2_alloc_inode_internal(bool is_dir) { uint8_t* bm = kmalloc(block_size); if (!bm) return 0; for (uint32_t g = 0; g < num_groups; g++) { if (gdt[g].bg_free_inodes_count == 0) continue; ext2_read_block_raw(gdt[g].bg_inode_bitmap, bm); for (uint32_t i = 0; i < sb.s_inodes_per_group; i++) { if (bm[i / 8] & (1u << (i % 8))) continue; bm[i / 8] |= (1u << (i % 8)); ext2_write_block_raw(gdt[g].bg_inode_bitmap, bm); gdt[g].bg_free_inodes_count--; sb.s_free_inodes_count--; if (is_dir) gdt[g].bg_used_dirs_count++; kfree(bm); return g * sb.s_inodes_per_group + i + 1; // 1-based } } kfree(bm); return 0; } static uint32_t ext2_alloc_inode(bool is_dir) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); uint32_t resp = ext2_alloc_inode_internal(is_dir); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } static void ext2_free_inode_internal(uint32_t inum, bool is_dir) { if (!inum) return; inum--; uint32_t g = inum / sb.s_inodes_per_group; uint32_t i = inum % sb.s_inodes_per_group; uint8_t* bm = kmalloc(block_size); if (!bm) return; ext2_read_block_raw(gdt[g].bg_inode_bitmap, bm); bm[i / 8] &= ~(1u << (i % 8)); ext2_write_block_raw(gdt[g].bg_inode_bitmap, bm); gdt[g].bg_free_inodes_count++; sb.s_free_inodes_count++; if (is_dir && gdt[g].bg_used_dirs_count) gdt[g].bg_used_dirs_count--; kfree(bm); } static void ext2_free_inode(uint32_t inum, bool is_dir) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); ext2_free_inode_internal(inum, is_dir); spinlock_release_irqrestore(&s_ext2_lock, flags); } // ── inode I/O ──────────────────────────────────────────────────────────────── bool ext2_read_inode_internal(uint32_t inum, ext2_inode_t* out) { inum--; uint32_t g = inum / sb.s_inodes_per_group; uint32_t idx = inum % sb.s_inodes_per_group; uint32_t ipb = block_size / sb.s_inode_size; uint32_t block_off = idx / ipb; uint32_t inode_off = idx % ipb; uint8_t* buf = kmalloc(block_size); if (!buf) return false; if (!ext2_read_block_raw(gdt[g].bg_inode_table + block_off, buf)) { kfree(buf); return false; } memcpy(out, buf + inode_off * sb.s_inode_size, sizeof(ext2_inode_t)); kfree(buf); return true; } bool ext2_read_inode(uint32_t inum, ext2_inode_t* out) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_inode_internal(inum, out); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_write_inode_internal(uint32_t inum, ext2_inode_t* inode) { inum--; uint32_t g = inum / sb.s_inodes_per_group; uint32_t idx = inum % sb.s_inodes_per_group; uint32_t ipb = block_size / sb.s_inode_size; uint32_t block_off = idx / ipb; uint32_t inode_off = idx % ipb; uint8_t* buf = kmalloc(block_size); if (!buf) return false; if (!ext2_read_block_raw(gdt[g].bg_inode_table + block_off, buf)) { kfree(buf); return false; } memcpy(buf + inode_off * sb.s_inode_size, inode, sizeof(ext2_inode_t)); bool ok = ext2_write_block_raw(gdt[g].bg_inode_table + block_off, buf); kfree(buf); return ok; } bool ext2_write_inode(uint32_t inum, ext2_inode_t* inode) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_write_inode_internal(inum, inode); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── block map (handles all indirect levels, read or allocate) ──────────────── static uint32_t ext2_block_map_internal(ext2_inode_t* inode, uint32_t fb, bool create) { uint32_t ptrs = block_size / 4; // direct if (fb < 12) { if (create && !inode->i_block[fb]) inode->i_block[fb] = alloc_zero_block_internal(); return inode->i_block[fb]; } fb -= 12; // single indirect if (fb < ptrs) { if (create && !inode->i_block[12]) inode->i_block[12] = alloc_zero_block_internal(); if (!inode->i_block[12]) return 0; uint32_t* tbl = kmalloc(block_size); ext2_read_block_raw(inode->i_block[12], tbl); if (create && !tbl[fb]) tbl[fb] = alloc_zero_block_internal(); uint32_t r = tbl[fb]; if (create) ext2_write_block_raw(inode->i_block[12], tbl); kfree(tbl); return r; } fb -= ptrs; // double indirect if (fb < ptrs * ptrs) { if (create && !inode->i_block[13]) inode->i_block[13] = alloc_zero_block_internal(); if (!inode->i_block[13]) return 0; uint32_t* l1 = kmalloc(block_size); ext2_read_block_raw(inode->i_block[13], l1); uint32_t i1 = fb / ptrs, i2 = fb % ptrs; if (create && !l1[i1]) { l1[i1] = alloc_zero_block_internal(); ext2_write_block_raw(inode->i_block[13], l1); } if (!l1[i1]) { kfree(l1); return 0; } uint32_t* l2 = kmalloc(block_size); ext2_read_block_raw(l1[i1], l2); if (create && !l2[i2]) l2[i2] = alloc_zero_block_internal(); uint32_t r = l2[i2]; if (create) ext2_write_block_raw(l1[i1], l2); kfree(l1); kfree(l2); return r; } fb -= ptrs * ptrs; // triple indirect if (create && !inode->i_block[14]) inode->i_block[14] = alloc_zero_block_internal(); if (!inode->i_block[14]) return 0; uint32_t* l1 = kmalloc(block_size); ext2_read_block_raw(inode->i_block[14], l1); uint32_t i1 = fb / (ptrs * ptrs); uint32_t rem = fb % (ptrs * ptrs); uint32_t i2 = rem / ptrs, i3 = rem % ptrs; if (create && !l1[i1]) { l1[i1] = alloc_zero_block_internal(); ext2_write_block_raw(inode->i_block[14], l1); } if (!l1[i1]) { kfree(l1); return 0; } uint32_t* l2 = kmalloc(block_size); ext2_read_block_raw(l1[i1], l2); if (create && !l2[i2]) { l2[i2] = alloc_zero_block_internal(); ext2_write_block_raw(l1[i1], l2); } if (!l2[i2]) { kfree(l1); kfree(l2); return 0; } uint32_t* l3 = kmalloc(block_size); ext2_read_block_raw(l2[i2], l3); if (create && !l3[i3]) l3[i3] = alloc_zero_block_internal(); uint32_t r = l3[i3]; if (create) { ext2_write_block_raw(l2[i2], l3); ext2_write_block_raw(l1[i1], l2); ext2_write_block_raw(inode->i_block[14], l1); } kfree(l1); kfree(l2); kfree(l3); return r; } static uint32_t ext2_block_map(ext2_inode_t* inode, uint32_t fb, bool create) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); uint32_t resp = ext2_block_map_internal(inode, fb, create); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── free all data blocks belonging to an inode ──────────────────────────────── static void ext2_free_inode_blocks_internal(ext2_inode_t* inode) { uint32_t ptrs = block_size / 4; uint32_t total_data = (inode->i_size + block_size - 1) / block_size; for (uint32_t i = 0; i < total_data; i++) { uint32_t b = ext2_block_map_internal(inode, i, false); if (b) ext2_free_block_internal(b); } uint32_t* tbl = kmalloc(block_size); if (!tbl) return; // free single-indirect table block if (inode->i_block[12]) { ext2_free_block_internal(inode->i_block[12]); inode->i_block[12] = 0; } // free double-indirect and its L1 table blocks if (inode->i_block[13]) { ext2_read_block_raw(inode->i_block[13], tbl); for (uint32_t i = 0; i < ptrs; i++) if (tbl[i]) ext2_free_block_internal(tbl[i]); ext2_free_block_internal(inode->i_block[13]); inode->i_block[13] = 0; } // free triple-indirect and its L1+L2 table blocks if (inode->i_block[14]) { uint32_t* l2 = kmalloc(block_size); ext2_read_block_raw(inode->i_block[14], tbl); for (uint32_t i = 0; i < ptrs; i++) { if (!tbl[i]) continue; ext2_read_block_raw(tbl[i], l2); for (uint32_t j = 0; j < ptrs; j++) if (l2[j]) ext2_free_block_internal(l2[j]); ext2_free_block_internal(tbl[i]); } ext2_free_block_internal(inode->i_block[14]); inode->i_block[14] = 0; kfree(l2); } kfree(tbl); } static void ext2_free_inode_blocks(ext2_inode_t* inode) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); ext2_free_inode_blocks_internal(inode); spinlock_release_irqrestore(&s_ext2_lock, flags); } // ── file I/O ───────────────────────────────────────────────────────────────── bool ext2_read_file_internal(ext2_inode_t* inode, uint8_t* buf) { if ((inode->i_mode & 0xF000) != EXT2_S_IFREG) { printf("EXT2: not a regular file\n"); return false; } uint32_t size = inode->i_size; uint32_t done = 0; uint8_t* tmp = kmalloc(block_size); if (!tmp) return false; for (uint32_t fb = 0; done < size; fb++) { uint32_t phys = ext2_block_map_internal(inode, fb, false); if (!phys) break; ext2_read_block_raw(phys, tmp); uint32_t n = block_size; if (done + n > size) n = size - done; memcpy(buf + done, tmp, n); done += n; } kfree(tmp); return true; } bool ext2_read_file(ext2_inode_t* inode, uint8_t* buf) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_file_internal(inode, buf); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_write_file_internal(ext2_inode_t* inode, uint32_t inum, const uint8_t* data, uint32_t size, ext2_write_mode_t mode) { uint32_t write_at = 0; if (mode == EXT2_WRITE_APPEND) { write_at = inode->i_size; } else { // overwrite: tear down old blocks first ext2_free_inode_blocks_internal(inode); memset(inode->i_block, 0, sizeof(inode->i_block)); inode->i_size = 0; inode->i_blocks = 0; } uint32_t new_size = write_at + size; inode->i_size = new_size; uint8_t* tmp = kmalloc(block_size); if (!tmp) return false; for (uint32_t written = 0; written < size; ) { uint32_t abs = write_at + written; uint32_t fb = abs / block_size; uint32_t boff = abs % block_size; uint32_t phys = ext2_block_map_internal(inode, fb, true); if (!phys) { kfree(tmp); return false; } // read-modify-write for partial blocks if (boff || (size - written) < block_size) ext2_read_block_raw(phys, tmp); else memset(tmp, 0, block_size); uint32_t n = block_size - boff; if (n > size - written) n = size - written; memcpy(tmp + boff, data + written, n); ext2_write_block_raw(phys, tmp); written += n; } inode->i_blocks = ((new_size + block_size - 1) / block_size) * (block_size / 512); inode->i_mtime = (uint32_t)g_Unixseconds; inode->i_ctime = (uint32_t)g_Unixseconds; kfree(tmp); ext2_write_inode_internal(inum, inode); return true; } bool ext2_write_file(ext2_inode_t* inode, uint32_t inum, const uint8_t* data, uint32_t size, ext2_write_mode_t mode) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_write_file_internal(inode, inum, data, size, mode); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_truncate_internal(ext2_inode_t* inode, uint32_t inum, uint32_t new_size) { uint32_t old_size = inode->i_size; if (new_size == old_size) return true; if (new_size < old_size) { // free blocks past new_size uint32_t old_blks = (old_size + block_size - 1) / block_size; uint32_t new_blks = (new_size + block_size - 1) / block_size; for (uint32_t i = new_blks; i < old_blks; i++) { uint32_t b = ext2_block_map_internal(inode, i, false); if (b) ext2_free_block_internal(b); } // zero the tail of the last surviving block if (new_size % block_size && new_blks) { uint32_t b = ext2_block_map_internal(inode, new_blks - 1, false); if (b) { uint8_t* tmp = kmalloc(block_size); ext2_read_block_raw(b, tmp); uint32_t from = new_size % block_size; memset(tmp + from, 0, block_size - from); ext2_write_block_raw(b, tmp); kfree(tmp); } } inode->i_blocks = new_blks * (block_size / 512); } // extending: sparse — just update the size field inode->i_size = new_size; inode->i_mtime = (uint32_t)g_Unixseconds; inode->i_ctime = (uint32_t)g_Unixseconds; return ext2_write_inode_internal(inum, inode); } bool ext2_truncate(ext2_inode_t* inode, uint32_t inum, uint32_t new_size) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_truncate_internal(inode, inum, new_size); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── directory helpers ───────────────────────────────────────────────────────── // Scan one directory inode for a named entry, return its inode number or 0. static uint32_t dir_lookup_internal(ext2_inode_t* dir, const char* name) { uint32_t nlen = strlen(name); uint8_t* buf = kmalloc(block_size); if (!buf) return 0; uint32_t nblocks = (dir->i_size + block_size - 1) / block_size; for (uint32_t fb = 0; fb < nblocks; fb++) { uint32_t phys = ext2_block_map_internal(dir, fb, false); if (!phys) continue; ext2_read_block_raw(phys, buf); uint32_t off = 0; while (off + 8 <= block_size) { ext2_dir_entry_t* e = (ext2_dir_entry_t*)(buf + off); if (e->rec_len < 8 || off + e->rec_len > block_size) break; if (e->inode && e->name_len == nlen) { char tmp[256] = {0}; memcpy(tmp, e->name, nlen); if (strcmp(tmp, name) == 0) { uint32_t r = e->inode; kfree(buf); return r; } } off += e->rec_len; } } kfree(buf); return 0; } // Insert a new directory entry into dir, allocating a new block if needed. static bool dir_add_entry_internal(ext2_inode_t* dir, uint32_t dir_inum, uint32_t new_inum, const char* name, uint8_t ftype) { uint8_t nlen = (uint8_t)strlen(name); uint16_t needed = (uint16_t)align4(8 + nlen); uint8_t* buf = kmalloc(block_size); if (!buf) return false; uint32_t nblocks = (dir->i_size + block_size - 1) / block_size; for (uint32_t fb = 0; fb < nblocks; fb++) { uint32_t phys = ext2_block_map_internal(dir, fb, false); if (!phys) continue; ext2_read_block_raw(phys, buf); uint32_t off = 0; while (off + 8 <= block_size) { ext2_dir_entry_t* e = (ext2_dir_entry_t*)(buf + off); if (e->rec_len < 8 || off + e->rec_len > block_size) break; uint16_t real = (uint16_t)align4(8 + (e->inode ? e->name_len : 0)); uint16_t avail = e->rec_len - (e->inode ? real : 0); if (e->inode == 0 && e->rec_len >= needed) { // reuse deleted slot e->inode = new_inum; e->name_len = nlen; e->file_type = ftype; memcpy(e->name, name, nlen); ext2_write_block_raw(phys, buf); kfree(buf); return true; } else if (e->inode && avail >= needed) { // split: shrink existing, carve off the tail ext2_dir_entry_t* ne = (ext2_dir_entry_t*)(buf + off + real); ne->inode = new_inum; ne->name_len = nlen; ne->file_type = ftype; ne->rec_len = avail; memcpy(ne->name, name, nlen); e->rec_len = real; ext2_write_block_raw(phys, buf); kfree(buf); return true; } off += e->rec_len; } } // No room in existing blocks — grow the directory by one block uint32_t phys = ext2_block_map_internal(dir, nblocks, true); if (!phys) { kfree(buf); return false; } memset(buf, 0, block_size); ext2_dir_entry_t* e = (ext2_dir_entry_t*)buf; e->inode = new_inum; e->name_len = nlen; e->file_type = ftype; e->rec_len = (uint16_t)block_size; memcpy(e->name, name, nlen); ext2_write_block_raw(phys, buf); dir->i_size += block_size; dir->i_blocks = (dir->i_size / block_size) * (block_size / 512); dir->i_mtime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(dir_inum, dir); kfree(buf); return true; } // Remove an entry by name, merging its space back into the previous entry. static bool dir_remove_entry_internal(ext2_inode_t* dir, uint32_t dir_inum, const char* name) { uint32_t nlen = strlen(name); uint8_t* buf = kmalloc(block_size); if (!buf) return false; uint32_t nblocks = (dir->i_size + block_size - 1) / block_size; for (uint32_t fb = 0; fb < nblocks; fb++) { uint32_t phys = ext2_block_map_internal(dir, fb, false); if (!phys) continue; ext2_read_block_raw(phys, buf); uint32_t off = 0; ext2_dir_entry_t* prev = NULL; while (off + 8 <= block_size) { ext2_dir_entry_t* e = (ext2_dir_entry_t*)(buf + off); if (e->rec_len < 8 || off + e->rec_len > block_size) break; if (e->inode && e->name_len == nlen) { char tmp[256] = {0}; memcpy(tmp, e->name, nlen); if (strcmp(tmp, name) == 0) { if (prev) prev->rec_len += e->rec_len; // absorb into previous else e->inode = 0; // first entry: zero inode ext2_write_block_raw(phys, buf); dir->i_mtime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(dir_inum, dir); kfree(buf); return true; } } prev = e; off += e->rec_len; } } kfree(buf); return false; } static bool dir_is_empty_internal(ext2_inode_t* dir) { uint8_t* buf = kmalloc(block_size); if (!buf) return true; uint32_t nblocks = (dir->i_size + block_size - 1) / block_size; for (uint32_t fb = 0; fb < nblocks; fb++) { uint32_t phys = ext2_block_map_internal(dir, fb, false); if (!phys) continue; ext2_read_block_raw(phys, buf); uint32_t off = 0; while (off + 8 <= block_size) { ext2_dir_entry_t* e = (ext2_dir_entry_t*)(buf + off); if (e->rec_len < 8) break; if (e->inode) { char n[256] = {0}; memcpy(n, e->name, e->name_len); if (strcmp(n, ".") && strcmp(n, "..")) { kfree(buf); return false; } } off += e->rec_len; } } kfree(buf); return true; } // ── path resolution ─────────────────────────────────────────────────────────── uint32_t ext2_resolve_path_internal(const char* path) { uint32_t cur_inum = 2; // root ext2_inode_t cur; if (!ext2_read_inode_internal(cur_inum, &cur)) return 0; if (*path == '/') path++; if (!*path) return cur_inum; char comp[256]; while (*path) { uint32_t len = 0; while (path[len] && path[len] != '/') len++; if (!len || len >= 256) return 0; memcpy(comp, path, len); comp[len] = '\0'; path += len; if (*path == '/') path++; cur_inum = dir_lookup_internal(&cur, comp); if (!cur_inum) return 0; if (!ext2_read_inode_internal(cur_inum, &cur)) return 0; } return cur_inum; } uint32_t ext2_resolve_path(const char* path) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); uint32_t resp = ext2_resolve_path_internal(path); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── public directory API ────────────────────────────────────────────────────── bool ext2_find_in_dir_internal(ext2_inode_t* dir, const char* name, uint32_t* out) { *out = dir_lookup_internal(dir, name); return *out != 0; } bool ext2_find_in_dir(ext2_inode_t* dir, const char* name, uint32_t* out) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_find_in_dir_internal(dir, name, out); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_read_dir_internal(ext2_inode_t* dir) { uint8_t* buf = kmalloc(block_size); if (!buf) return false; uint32_t nblocks = (dir->i_size + block_size - 1) / block_size; for (uint32_t fb = 0; fb < nblocks; fb++) { uint32_t phys = ext2_block_map_internal(dir, fb, false); if (!phys) continue; ext2_read_block_raw(phys, buf); uint32_t off = 0; while (off + 8 <= block_size) { ext2_dir_entry_t* e = (ext2_dir_entry_t*)(buf + off); if (e->rec_len < 8 || off + e->rec_len > block_size) break; if (e->inode) { char name[256]; uint8_t nl = e->name_len > EXT2_NAME_LEN ? EXT2_NAME_LEN : e->name_len; memcpy(name, e->name, nl); name[nl] = '\0'; printf(" %s inode=%u %s\n", name, e->inode, ext2_file_type_string(e->file_type)); } off += e->rec_len; } } kfree(buf); return true; } bool ext2_read_dir(ext2_inode_t* dir) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_dir_internal(dir); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_read_root_dir_internal(void) { ext2_inode_t root; if (!ext2_read_inode_internal(2, &root)) return false; printf("Root directory:\n"); return ext2_read_dir_internal(&root); } bool ext2_read_root_dir(void) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_root_dir_internal(); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── high-level file ops ─────────────────────────────────────────────────────── bool ext2_read_file_from_root_internal(const char* name, uint8_t* buf, uint32_t* size) { ext2_inode_t root; if (!ext2_read_inode_internal(2, &root)) return false; uint32_t inum; if (!ext2_find_in_dir_internal(&root, name, &inum)) { printf("EXT2: not found: %s\n", name); return false; } ext2_inode_t fi; if (!ext2_read_inode_internal(inum, &fi)) return false; *size = fi.i_size; return ext2_read_file_internal(&fi, buf); } bool ext2_read_file_from_root(const char* name, uint8_t* buf, uint32_t* size) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_read_file_from_root_internal(name, buf, size); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_write_file_from_root_internal(const char* name, const uint8_t* data, uint32_t size, ext2_write_mode_t mode) { ext2_inode_t root; if (!ext2_read_inode_internal(2, &root)) { return false; } uint32_t inum; if (!ext2_find_in_dir_internal(&root, name, &inum)) { printf("EXT2: not found: %s\n", name); return false; } ext2_inode_t fi; if (!ext2_read_inode_internal(inum, &fi)) return false; bool ok = ext2_write_file_internal(&fi, inum, data, size, mode); if (ok) ext2_sync(); return ok; } bool ext2_write_file_from_root(const char* name, const uint8_t* data, uint32_t size, ext2_write_mode_t mode) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_write_file_from_root_internal(name, data, size, mode); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── create / delete ─────────────────────────────────────────────────────────── bool ext2_create_file_internal(ext2_inode_t* dir, uint32_t dir_inum, const char* name, uint32_t* out_inum) { if (dir_lookup_internal(dir, name)) { printf("EXT2: already exists: %s\n", name); return false; } uint32_t inum = ext2_alloc_inode_internal(false); if (!inum) return false; ext2_inode_t inode = {0}; inode.i_mode = EXT2_S_IFREG | 0644; inode.i_links_count = 1; inode.i_atime = inode.i_ctime = inode.i_mtime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(inum, &inode); if (!dir_add_entry_internal(dir, dir_inum, inum, name, EXT2_FT_REG_FILE)) { ext2_free_inode_internal(inum, false); return false; } if (out_inum) *out_inum = inum; ext2_sync(); return true; } bool ext2_create_file(ext2_inode_t* dir, uint32_t dir_inum, const char* name, uint32_t* out_inum) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_create_file_internal(dir, dir_inum, name, out_inum); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_mkdir_internal(ext2_inode_t* parent, uint32_t parent_inum, const char* name, uint32_t* out_inum) { if (dir_lookup_internal(parent, name)) { printf("EXT2: already exists: %s\n", name); return false; } uint32_t inum = ext2_alloc_inode_internal(true); if (!inum) return false; // Allocate the first block and write . and .. uint32_t phys = alloc_zero_block_internal(); if (!phys) { ext2_free_inode_internal(inum, true); return false; } ext2_inode_t dir = {0}; dir.i_mode = EXT2_S_IFDIR | 0755; dir.i_links_count = 2; // . + parent entry dir.i_size = block_size; dir.i_blocks = block_size / 512; dir.i_block[0] = phys; dir.i_atime = dir.i_ctime = dir.i_mtime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(inum, &dir); uint8_t* buf = kmalloc(block_size); memset(buf, 0, block_size); ext2_dir_entry_t* dot = (ext2_dir_entry_t*)buf; dot->inode = inum; dot->rec_len = 12; // align4(8+1) dot->name_len = 1; dot->file_type = EXT2_FT_DIR; dot->name[0] = '.'; ext2_dir_entry_t* dotdot = (ext2_dir_entry_t*)(buf + 12); dotdot->inode = parent_inum; dotdot->rec_len = (uint16_t)(block_size - 12); dotdot->name_len = 2; dotdot->file_type = EXT2_FT_DIR; dotdot->name[0] = '.'; dotdot->name[1] = '.'; ext2_write_block_raw(phys, buf); kfree(buf); if (!dir_add_entry_internal(parent, parent_inum, inum, name, EXT2_FT_DIR)) { ext2_free_block_internal(phys); ext2_free_inode_internal(inum, true); return false; } // each subdirectory increments the parent's hard link count (the .. link) parent->i_links_count++; ext2_write_inode_internal(parent_inum, parent); if (out_inum) *out_inum = inum; ext2_sync(); return true; } bool ext2_mkdir(ext2_inode_t* parent, uint32_t parent_inum, const char* name, uint32_t* out_inum) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_mkdir_internal(parent, parent_inum, name, out_inum); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_unlink_internal(ext2_inode_t* dir, uint32_t dir_inum, const char* name) { uint32_t inum = dir_lookup_internal(dir, name); if (!inum) { printf("EXT2: not found: %s\n", name); return false; } ext2_inode_t fi; if (!ext2_read_inode_internal(inum, &fi)) return false; if ((fi.i_mode & 0xF000) == EXT2_S_IFDIR) { printf("EXT2: is a directory, use rmdir\n"); return false; } if (!dir_remove_entry_internal(dir, dir_inum, name)) return false; fi.i_links_count--; if (fi.i_links_count == 0) { ext2_free_inode_blocks_internal(&fi); fi.i_dtime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(inum, &fi); ext2_free_inode_internal(inum, false); } else { fi.i_ctime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(inum, &fi); } ext2_sync(); return true; } bool ext2_unlink(ext2_inode_t* dir, uint32_t dir_inum, const char* name) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_unlink_internal(dir, dir_inum, name); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_rmdir_internal(ext2_inode_t* parent, uint32_t parent_inum, const char* name) { uint32_t inum = dir_lookup_internal(parent, name); if (!inum) { printf("EXT2: not found: %s\n", name); return false; } ext2_inode_t dir; if (!ext2_read_inode_internal(inum, &dir)) return false; if ((dir.i_mode & 0xF000) != EXT2_S_IFDIR) { printf("EXT2: not a directory: %s\n", name); return false; } if (!dir_is_empty_internal(&dir)) { printf("EXT2: directory not empty: %s\n", name); return false; } if (!dir_remove_entry_internal(parent, parent_inum, name)) return false; parent->i_links_count--; ext2_write_inode_internal(parent_inum, parent); ext2_free_inode_blocks_internal(&dir); dir.i_links_count = 0; dir.i_dtime = (uint32_t)g_Unixseconds; ext2_write_inode_internal(inum, &dir); ext2_free_inode_internal(inum, true); ext2_sync(); return true; } bool ext2_rmdir(ext2_inode_t* parent, uint32_t parent_inum, const char* name) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_rmdir_internal(parent, parent_inum, name); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_rename_internal(ext2_inode_t* src_dir, uint32_t src_inum, ext2_inode_t* dst_dir, uint32_t dst_inum, const char* old_name, const char* new_name) { uint32_t inum = dir_lookup_internal(src_dir, old_name); if (!inum) { printf("EXT2: not found: %s\n", old_name); return false; } ext2_inode_t fi; if (!ext2_read_inode_internal(inum, &fi)) return false; bool is_dir = (fi.i_mode & 0xF000) == EXT2_S_IFDIR; uint8_t ftype = is_dir ? EXT2_FT_DIR : EXT2_FT_REG_FILE; // If destination exists, remove it (POSIX rename semantics) uint32_t existing = dir_lookup_internal(dst_dir, new_name); if (existing) { ext2_inode_t ex; ext2_read_inode_internal(existing, &ex); if ((ex.i_mode & 0xF000) == EXT2_S_IFDIR) { if (!dir_is_empty_internal(&ex)) { printf("EXT2: rename: target dir not empty\n"); return false; } ext2_rmdir_internal(dst_dir, dst_inum, new_name); } else { ext2_unlink_internal(dst_dir, dst_inum, new_name); } } if (!dir_add_entry_internal(dst_dir, dst_inum, inum, new_name, ftype)) return false; if (!dir_remove_entry_internal(src_dir, src_inum, old_name)) return false; // If moving a directory, update its .. entry and adjust link counts if (is_dir && src_inum != dst_inum) { uint32_t phys = ext2_block_map_internal(&fi, 0, false); if (phys) { uint8_t* buf = kmalloc(block_size); ext2_read_block_raw(phys, buf); uint32_t off = 0; while (off + 8 <= block_size) { ext2_dir_entry_t* e = (ext2_dir_entry_t*)(buf + off); if (e->rec_len < 8) break; if (e->name_len == 2 && e->name[0] == '.' && e->name[1] == '.') { e->inode = dst_inum; break; } off += e->rec_len; } ext2_write_block_raw(phys, buf); kfree(buf); } src_dir->i_links_count--; ext2_write_inode_internal(src_inum, src_dir); dst_dir->i_links_count++; ext2_write_inode_internal(dst_inum, dst_dir); } ext2_sync(); return true; } bool ext2_rename(ext2_inode_t* src_dir, uint32_t src_inum, ext2_inode_t* dst_dir, uint32_t dst_inum, const char* old_name, const char* new_name) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_rename_internal(src_dir, src_inum, dst_dir, dst_inum, old_name, new_name); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── symlinks ────────────────────────────────────────────────────────────────── bool ext2_symlink_internal(ext2_inode_t* dir, uint32_t dir_inum, const char* name, const char* target) { if (dir_lookup_internal(dir, name)) { printf("EXT2: already exists: %s\n", name); return false; } uint32_t inum = ext2_alloc_inode_internal(false); if (!inum) return false; uint32_t tlen = strlen(target); ext2_inode_t inode = {0}; inode.i_mode = EXT2_S_IFLNK | 0777; inode.i_links_count = 1; inode.i_size = tlen; inode.i_atime = inode.i_ctime = inode.i_mtime = (uint32_t)g_Unixseconds; if (tlen < 60) { // fast symlink: target stored directly in i_block[] memcpy(inode.i_block, target, tlen); inode.i_blocks = 0; } else { uint32_t phys = alloc_zero_block_internal(); if (!phys) { ext2_free_inode_internal(inum, false); return false; } uint8_t* buf = kmalloc(block_size); memset(buf, 0, block_size); memcpy(buf, target, tlen); ext2_write_block_raw(phys, buf); kfree(buf); inode.i_block[0] = phys; inode.i_blocks = block_size / 512; } ext2_write_inode_internal(inum, &inode); if (!dir_add_entry_internal(dir, dir_inum, inum, name, EXT2_FT_SYMLINK)) { ext2_free_inode_internal(inum, false); return false; } ext2_sync(); return true; } bool ext2_symlink(ext2_inode_t* dir, uint32_t dir_inum, const char* name, const char* target) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_symlink_internal(dir, dir_inum, name, target); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_readlink_internal(uint32_t inum, char* buf, uint32_t buf_size) { ext2_inode_t inode; if (!ext2_read_inode_internal(inum, &inode)) return false; if ((inode.i_mode & 0xF000) != EXT2_S_IFLNK) return false; uint32_t tlen = inode.i_size; if (tlen >= buf_size) tlen = buf_size - 1; if (inode.i_size < 60) { memcpy(buf, inode.i_block, tlen); } else { uint8_t* tmp = kmalloc(block_size); ext2_read_block_raw(inode.i_block[0], tmp); memcpy(buf, tmp, tlen); kfree(tmp); } buf[tlen] = '\0'; return true; } bool ext2_readlink(uint32_t inum, char* buf, uint32_t buf_size) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_readlink_internal(inum, buf, buf_size); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } // ── metadata ───────────────────────────────────────────────────────────────── bool ext2_stat_internal(uint32_t inum, ext2_stat_t* st) { ext2_inode_t inode; if (!ext2_read_inode_internal(inum, &inode)) return false; st->ino = inum; st->mode = inode.i_mode; st->uid = inode.i_uid; st->gid = inode.i_gid; st->size = inode.i_size; st->atime = inode.i_atime; st->mtime = inode.i_mtime; st->ctime = inode.i_ctime; st->nlink = inode.i_links_count; st->blocks = inode.i_blocks; return true; } bool ext2_stat(uint32_t inum, ext2_stat_t* st) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_stat_internal(inum, st); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_chmod_internal(uint32_t inum, uint16_t mode) { ext2_inode_t inode; if (!ext2_read_inode_internal(inum, &inode)) return false; inode.i_mode = (inode.i_mode & 0xF000) | (mode & 0x0FFF); inode.i_ctime = (uint32_t)g_Unixseconds; return ext2_write_inode_internal(inum, &inode); } bool ext2_chown_internal(uint32_t inum, uint16_t uid, uint16_t gid) { ext2_inode_t inode; if (!ext2_read_inode_internal(inum, &inode)) return false; inode.i_uid = uid; inode.i_gid = gid; inode.i_ctime = (uint32_t)g_Unixseconds; return ext2_write_inode_internal(inum, &inode); } bool ext2_chmod(uint32_t inum, uint16_t mode) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_chmod_internal(inum, mode); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; } bool ext2_chown(uint32_t inum, uint16_t uid, uint16_t gid) { uint64_t flags; spinlock_acquire_irqsave(&s_ext2_lock, &flags); bool resp = ext2_chown_internal(inum, uid, gid); spinlock_release_irqrestore(&s_ext2_lock, flags); return resp; }