Files
KirkOS/src/fs/ext2.c
T
kaguya 016ee32987 feat: update .gitignore and enhance ext2 file timestamps
Added back g_Unixseconds to the ext2 driver

Signed-off-by: kaguya <vpshinomiya@protonmail.com>
2026-04-16 00:37:30 -04:00

1399 lines
47 KiB
C

#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"
#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;
}