#include "ext2.h"
#include "arch/x86_64/ata.h"
#include "mm/memory.h"
#include "stdio.h"
#include <stdbool.h>
#include "string.h"
#include "mp/spinlock.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("  %-32s  inode=%-6u  %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;
}