016ee32987
Added back g_Unixseconds to the ext2 driver Signed-off-by: kaguya <vpshinomiya@protonmail.com>
1399 lines
47 KiB
C
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;
|
|
} |