This commit is contained in:
alexvoste
2026-05-07 02:22:25 +03:00
commit 1a9fd27a31
226 changed files with 29188 additions and 0 deletions
+935
View File
@@ -0,0 +1,935 @@
#include "../../include/fs/ext2.h"
#include "../../include/fs/vfs.h"
#include "../../include/drivers/blkdev.h"
#include "../../include/memory/pmm.h"
#include "../../include/io/serial.h"
#include "../../include/syscall/errno.h"
#include <string.h>
#include <stdio.h>
static int block_read(ext2_t *fs, uint32_t block, void *buf) {
return blkdev_read(fs->dev, (uint64_t)block * fs->block_size, buf, fs->block_size);
}
static int block_write(ext2_t *fs, uint32_t block, const void *buf) {
return blkdev_write(fs->dev, (uint64_t)block * fs->block_size, buf, fs->block_size);
}
static int sb_flush(ext2_t *fs) {
return blkdev_write(fs->dev, EXT2_SUPER_OFFSET, &fs->sb, sizeof(fs->sb));
}
static int gdt_flush(ext2_t *fs) {
uint32_t gdt_block = (fs->block_size == 1024) ? 2 : 1;
return blkdev_write(fs->dev, (uint64_t)gdt_block * fs->block_size,
fs->gdt, fs->groups_count * sizeof(ext2_group_desc_t));
}
static int inode_read(ext2_t *fs, uint32_t ino, ext2_inode_t *out) {
if (ino == 0) return -EINVAL;
uint32_t idx = ino - 1;
uint32_t group = idx / fs->sb.s_inodes_per_group;
uint32_t local = idx % fs->sb.s_inodes_per_group;
if (group >= fs->groups_count) return -EINVAL;
uint64_t off = (uint64_t)fs->gdt[group].bg_inode_table * fs->block_size
+ (uint64_t)local * fs->inode_size;
return blkdev_read(fs->dev, off, out, sizeof(*out));
}
static int inode_write(ext2_t *fs, uint32_t ino, const ext2_inode_t *in) {
if (ino == 0) return -EINVAL;
uint32_t idx = ino - 1;
uint32_t group = idx / fs->sb.s_inodes_per_group;
uint32_t local = idx % fs->sb.s_inodes_per_group;
if (group >= fs->groups_count) return -EINVAL;
uint64_t off = (uint64_t)fs->gdt[group].bg_inode_table * fs->block_size
+ (uint64_t)local * fs->inode_size;
return blkdev_write(fs->dev, off, in, sizeof(*in));
}
static bool bmp_test(const uint8_t *bmp, uint32_t bit) {
return (bmp[bit / 8] >> (bit % 8)) & 1;
}
static void bmp_set(uint8_t *bmp, uint32_t bit) {
bmp[bit / 8] |= (uint8_t)(1 << (bit % 8));
}
static void bmp_clear(uint8_t *bmp, uint32_t bit) {
bmp[bit / 8] &= (uint8_t)~(1 << (bit % 8));
}
static int32_t alloc_inode(ext2_t *fs) {
uint8_t *bmp = kmalloc(fs->block_size);
if (!bmp) return -ENOMEM;
for (uint32_t g = 0; g < fs->groups_count; g++) {
if (fs->gdt[g].bg_free_inodes_count == 0) continue;
block_read(fs, fs->gdt[g].bg_inode_bitmap, bmp);
for (uint32_t i = 0; i < fs->sb.s_inodes_per_group; i++) {
if (!bmp_test(bmp, i)) {
bmp_set(bmp, i);
block_write(fs, fs->gdt[g].bg_inode_bitmap, bmp);
fs->gdt[g].bg_free_inodes_count--;
fs->sb.s_free_inodes_count--;
fs->dirty = true;
kfree(bmp);
return (int32_t)(g * fs->sb.s_inodes_per_group + i + 1);
}
}
}
kfree(bmp);
return -ENOSPC;
}
static void free_inode(ext2_t *fs, uint32_t ino) {
uint32_t idx = ino - 1;
uint32_t group = idx / fs->sb.s_inodes_per_group;
uint32_t local = idx % fs->sb.s_inodes_per_group;
uint8_t *bmp = kmalloc(fs->block_size);
if (!bmp) return;
block_read(fs, fs->gdt[group].bg_inode_bitmap, bmp);
bmp_clear(bmp, local);
block_write(fs, fs->gdt[group].bg_inode_bitmap, bmp);
fs->gdt[group].bg_free_inodes_count++;
fs->sb.s_free_inodes_count++;
fs->dirty = true;
kfree(bmp);
}
static int32_t alloc_block(ext2_t *fs) {
uint8_t *bmp = kmalloc(fs->block_size);
if (!bmp) return -ENOMEM;
for (uint32_t g = 0; g < fs->groups_count; g++) {
if (fs->gdt[g].bg_free_blocks_count == 0) continue;
block_read(fs, fs->gdt[g].bg_block_bitmap, bmp);
for (uint32_t i = 0; i < fs->sb.s_blocks_per_group; i++) {
uint32_t abs_block = g * fs->sb.s_blocks_per_group + i + fs->sb.s_first_data_block;
if (abs_block >= fs->sb.s_blocks_count) break;
if (!bmp_test(bmp, i)) {
bmp_set(bmp, i);
block_write(fs, fs->gdt[g].bg_block_bitmap, bmp);
fs->gdt[g].bg_free_blocks_count--;
fs->sb.s_free_blocks_count--;
fs->dirty = true;
kfree(bmp);
return (int32_t)abs_block;
}
}
}
kfree(bmp);
return -ENOSPC;
}
static void free_block(ext2_t *fs, uint32_t blk) {
if (blk < fs->sb.s_first_data_block) return;
uint32_t adj = blk - fs->sb.s_first_data_block;
uint32_t group = adj / fs->sb.s_blocks_per_group;
uint32_t local = adj % fs->sb.s_blocks_per_group;
if (group >= fs->groups_count) return;
uint8_t *bmp = kmalloc(fs->block_size);
if (!bmp) return;
block_read(fs, fs->gdt[group].bg_block_bitmap, bmp);
bmp_clear(bmp, local);
block_write(fs, fs->gdt[group].bg_block_bitmap, bmp);
fs->gdt[group].bg_free_blocks_count++;
fs->sb.s_free_blocks_count++;
fs->dirty = true;
kfree(bmp);
}
static int32_t get_block_num(ext2_t *fs, ext2_inode_t *di, uint32_t file_block) {
if (file_block < EXT2_NDIR_BLOCKS)
return (int32_t)di->i_block[file_block];
uint32_t ppb = fs->ptrs_per_block;
file_block -= EXT2_NDIR_BLOCKS;
if (file_block < ppb) {
if (di->i_block[EXT2_IND_BLOCK] == 0) return 0;
uint32_t *ind = kmalloc(fs->block_size);
if (!ind) return -ENOMEM;
block_read(fs, di->i_block[EXT2_IND_BLOCK], ind);
int32_t ret = (int32_t)ind[file_block];
kfree(ind);
return ret;
}
file_block -= ppb;
if (file_block < ppb * ppb) {
if (di->i_block[EXT2_DIND_BLOCK] == 0) return 0;
uint32_t *dind = kmalloc(fs->block_size);
if (!dind) return -ENOMEM;
block_read(fs, di->i_block[EXT2_DIND_BLOCK], dind);
uint32_t i1 = file_block / ppb;
uint32_t i2 = file_block % ppb;
if (dind[i1] == 0) { kfree(dind); return 0; }
uint32_t ib = dind[i1];
kfree(dind);
uint32_t *ind = kmalloc(fs->block_size);
if (!ind) return -ENOMEM;
block_read(fs, ib, ind);
int32_t ret = (int32_t)ind[i2];
kfree(ind);
return ret;
}
return -EFBIG;
}
static int set_block_num(ext2_t *fs, ext2_inode_t *di, uint32_t file_block, uint32_t disk_block) {
if (file_block < EXT2_NDIR_BLOCKS) {
di->i_block[file_block] = disk_block;
return 0;
}
uint32_t ppb = fs->ptrs_per_block;
file_block -= EXT2_NDIR_BLOCKS;
if (file_block < ppb) {
if (di->i_block[EXT2_IND_BLOCK] == 0) {
int32_t nb = alloc_block(fs);
if (nb < 0) return nb;
di->i_block[EXT2_IND_BLOCK] = (uint32_t)nb;
uint8_t *z = kzalloc(fs->block_size);
block_write(fs, (uint32_t)nb, z);
kfree(z);
}
uint32_t *ind = kmalloc(fs->block_size);
if (!ind) return -ENOMEM;
block_read(fs, di->i_block[EXT2_IND_BLOCK], ind);
ind[file_block] = disk_block;
block_write(fs, di->i_block[EXT2_IND_BLOCK], ind);
kfree(ind);
return 0;
}
file_block -= ppb;
if (file_block < ppb * ppb) {
if (di->i_block[EXT2_DIND_BLOCK] == 0) {
int32_t nb = alloc_block(fs);
if (nb < 0) return nb;
di->i_block[EXT2_DIND_BLOCK] = (uint32_t)nb;
uint8_t *z = kzalloc(fs->block_size);
block_write(fs, (uint32_t)nb, z);
kfree(z);
}
uint32_t *dind = kmalloc(fs->block_size);
if (!dind) return -ENOMEM;
block_read(fs, di->i_block[EXT2_DIND_BLOCK], dind);
uint32_t i1 = file_block / ppb;
uint32_t i2 = file_block % ppb;
if (dind[i1] == 0) {
int32_t nb = alloc_block(fs);
if (nb < 0) { kfree(dind); return nb; }
dind[i1] = (uint32_t)nb;
block_write(fs, di->i_block[EXT2_DIND_BLOCK], dind);
uint8_t *z = kzalloc(fs->block_size);
block_write(fs, (uint32_t)nb, z);
kfree(z);
}
uint32_t ib = dind[i1];
kfree(dind);
uint32_t *ind = kmalloc(fs->block_size);
if (!ind) return -ENOMEM;
block_read(fs, ib, ind);
ind[i2] = disk_block;
block_write(fs, ib, ind);
kfree(ind);
return 0;
}
return -EFBIG;
}
static const vnode_ops_t ext2_file_ops;
static const vnode_ops_t ext2_dir_ops;
static vnode_t *ext2_make_vnode(ext2_t *fs, uint32_t ino, ext2_inode_t *di) {
vnode_t *v = kzalloc(sizeof(vnode_t));
if (!v) return NULL;
ext2_vdata_t *vd = kzalloc(sizeof(ext2_vdata_t));
if (!vd) { kfree(v); return NULL; }
vd->fs = fs;
vd->ino = ino;
if ((di->i_mode & 0xF000) == EXT2_S_IFDIR) {
v->type = VFS_NODE_DIR;
v->ops = &ext2_dir_ops;
} else {
v->type = VFS_NODE_FILE;
v->ops = &ext2_file_ops;
}
v->mode = di->i_mode & 0x0FFF;
v->uid = di->i_uid;
v->gid = di->i_gid;
v->ino = ino;
v->size = di->i_size;
v->fs_data = vd;
v->refcount = 1;
return v;
}
static int64_t ext2_file_read(vnode_t *node, void *buf, size_t len, uint64_t offset) {
ext2_vdata_t *vd = node->fs_data;
ext2_t *fs = vd->fs;
ext2_inode_t di;
int r = inode_read(fs, vd->ino, &di);
if (r < 0) return r;
if (offset >= di.i_size) return 0;
if (offset + len > di.i_size) len = di.i_size - (size_t)offset;
if (len == 0) return 0;
uint8_t *dst = (uint8_t *)buf;
size_t done = 0;
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
while (done < len) {
uint32_t co = (uint32_t)(offset + done);
uint32_t fb = co / fs->block_size;
uint32_t bo = co % fs->block_size;
int32_t db = get_block_num(fs, &di, fb);
if (db <= 0) {
if (db < 0) { kfree(bb); return db; }
memset(bb, 0, fs->block_size);
} else {
r = block_read(fs, (uint32_t)db, bb);
if (r < 0) { kfree(bb); return r; }
}
size_t ch = fs->block_size - bo;
if (ch > len - done) ch = len - done;
memcpy(dst + done, bb + bo, ch);
done += ch;
}
kfree(bb);
return (int64_t)done;
}
static int64_t ext2_file_write(vnode_t *node, const void *buf, size_t len, uint64_t offset) {
ext2_vdata_t *vd = node->fs_data;
ext2_t *fs = vd->fs;
ext2_inode_t di;
int r = inode_read(fs, vd->ino, &di);
if (r < 0) return r;
const uint8_t *src = (const uint8_t *)buf;
size_t done = 0;
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
while (done < len) {
uint32_t co = (uint32_t)(offset + done);
uint32_t fb = co / fs->block_size;
uint32_t bo = co % fs->block_size;
int32_t db = get_block_num(fs, &di, fb);
if (db == 0) {
int32_t nb = alloc_block(fs);
if (nb < 0) { kfree(bb); return (done > 0) ? (int64_t)done : nb; }
set_block_num(fs, &di, fb, (uint32_t)nb);
db = nb;
memset(bb, 0, fs->block_size);
di.i_blocks += fs->block_size / 512;
} else if (db < 0) {
kfree(bb); return db;
} else {
if (bo != 0 || (len - done) < fs->block_size)
block_read(fs, (uint32_t)db, bb);
}
size_t ch = fs->block_size - bo;
if (ch > len - done) ch = len - done;
memcpy(bb + bo, src + done, ch);
block_write(fs, (uint32_t)db, bb);
done += ch;
}
uint32_t ne = (uint32_t)(offset + done);
if (ne > di.i_size) { di.i_size = ne; node->size = ne; }
inode_write(fs, vd->ino, &di);
fs->dirty = true;
kfree(bb);
return (int64_t)done;
}
static void free_all_blocks(ext2_t *fs, ext2_inode_t *di) {
for (int i = 0; i < EXT2_NDIR_BLOCKS; i++) {
if (di->i_block[i]) { free_block(fs, di->i_block[i]); di->i_block[i] = 0; }
}
if (di->i_block[EXT2_IND_BLOCK]) {
uint32_t *ind = kmalloc(fs->block_size);
if (ind) {
block_read(fs, di->i_block[EXT2_IND_BLOCK], ind);
for (uint32_t i = 0; i < fs->ptrs_per_block; i++)
if (ind[i]) free_block(fs, ind[i]);
kfree(ind);
}
free_block(fs, di->i_block[EXT2_IND_BLOCK]);
di->i_block[EXT2_IND_BLOCK] = 0;
}
if (di->i_block[EXT2_DIND_BLOCK]) {
uint32_t *dind = kmalloc(fs->block_size);
if (dind) {
block_read(fs, di->i_block[EXT2_DIND_BLOCK], dind);
for (uint32_t i = 0; i < fs->ptrs_per_block; i++) {
if (dind[i]) {
uint32_t *ind = kmalloc(fs->block_size);
if (ind) {
block_read(fs, dind[i], ind);
for (uint32_t j = 0; j < fs->ptrs_per_block; j++)
if (ind[j]) free_block(fs, ind[j]);
kfree(ind);
}
free_block(fs, dind[i]);
}
}
kfree(dind);
}
free_block(fs, di->i_block[EXT2_DIND_BLOCK]);
di->i_block[EXT2_DIND_BLOCK] = 0;
}
di->i_blocks = 0;
}
static int ext2_file_truncate(vnode_t *node, uint64_t new_size) {
ext2_vdata_t *vd = node->fs_data;
ext2_t *fs = vd->fs;
ext2_inode_t di;
int r = inode_read(fs, vd->ino, &di);
if (r < 0) return r;
if (new_size == 0) free_all_blocks(fs, &di);
di.i_size = (uint32_t)new_size;
node->size = new_size;
inode_write(fs, vd->ino, &di);
fs->dirty = true;
return 0;
}
static int ext2_stat(vnode_t *node, vfs_stat_t *out) {
memset(out, 0, sizeof(*out));
out->st_ino = node->ino;
out->st_type = node->type;
out->st_mode = node->mode;
out->st_uid = node->uid;
out->st_gid = node->gid;
out->st_size = node->size;
out->st_blocks = (node->size + 511) / 512;
return 0;
}
static void ext2_vnode_ref(vnode_t *node) { (void)node; }
static void ext2_vnode_unref(vnode_t *node) {
if (node->fs_data) kfree(node->fs_data);
kfree(node);
}
static const vnode_ops_t ext2_file_ops = {
.read = ext2_file_read,
.write = ext2_file_write,
.truncate = ext2_file_truncate,
.stat = ext2_stat,
.ref = ext2_vnode_ref,
.unref = ext2_vnode_unref,
};
static int ext2_dir_lookup(vnode_t *dir, const char *name, vnode_t **out) {
ext2_vdata_t *vd = dir->fs_data;
ext2_t *fs = vd->fs;
ext2_inode_t di;
int r = inode_read(fs, vd->ino, &di);
if (r < 0) return r;
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
size_t nl = strlen(name);
uint32_t ds = di.i_size;
uint32_t pos = 0, fb = 0;
while (pos < ds) {
int32_t db = get_block_num(fs, &di, fb);
if (db <= 0) { fb++; pos += fs->block_size; continue; }
block_read(fs, (uint32_t)db, bb);
uint32_t off = 0;
while (off < fs->block_size && (pos + off) < ds) {
ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off);
if (de->rec_len == 0) break;
if (de->inode != 0 && de->name_len == nl && memcmp(de->name, name, nl) == 0) {
ext2_inode_t ci;
inode_read(fs, de->inode, &ci);
*out = ext2_make_vnode(fs, de->inode, &ci);
kfree(bb);
return *out ? 0 : -ENOMEM;
}
off += de->rec_len;
}
fb++; pos += fs->block_size;
}
kfree(bb);
return -ENOENT;
}
static int ext2_dir_readdir(vnode_t *dir, uint64_t index, vfs_dirent_t *out) {
ext2_vdata_t *vd = dir->fs_data;
ext2_t *fs = vd->fs;
ext2_inode_t di;
int r = inode_read(fs, vd->ino, &di);
if (r < 0) return r;
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
uint32_t ds = di.i_size, pos = 0, fb = 0;
uint64_t cur = 0;
while (pos < ds) {
int32_t db = get_block_num(fs, &di, fb);
if (db <= 0) { fb++; pos += fs->block_size; continue; }
block_read(fs, (uint32_t)db, bb);
uint32_t off = 0;
while (off < fs->block_size && (pos + off) < ds) {
ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off);
if (de->rec_len == 0) break;
if (de->inode != 0 && de->name_len > 0) {
bool skip = (de->name_len == 1 && de->name[0] == '.') ||
(de->name_len == 2 && de->name[0] == '.' && de->name[1] == '.');
if (!skip) {
if (cur == index) {
out->d_ino = de->inode;
out->d_type = (de->file_type == EXT2_FT_DIR) ? VFS_NODE_DIR : VFS_NODE_FILE;
size_t n = de->name_len;
if (n >= VFS_MAX_NAME) n = VFS_MAX_NAME - 1;
memcpy(out->d_name, de->name, n);
out->d_name[n] = '\0';
kfree(bb);
return 0;
}
cur++;
}
}
off += de->rec_len;
}
fb++; pos += fs->block_size;
}
kfree(bb);
return -ENOENT;
}
static int ext2_dir_add_entry(ext2_t *fs, uint32_t dir_ino, uint32_t child_ino,
uint8_t file_type, const char *name) {
if (!name || !name[0]) return -EINVAL;
ext2_inode_t di;
int r = inode_read(fs, dir_ino, &di);
if (r < 0) return r;
uint8_t nl = (uint8_t)strlen(name);
uint16_t need = (uint16_t)((8 + nl + 3) & ~3);
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
uint32_t nb = (di.i_size + fs->block_size - 1) / fs->block_size;
for (uint32_t fb = 0; fb < nb; fb++) {
int32_t db = get_block_num(fs, &di, fb);
if (db <= 0) continue;
block_read(fs, (uint32_t)db, bb);
uint32_t off = 0;
while (off < fs->block_size) {
ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off);
if (de->rec_len == 0) break;
if (de->inode == 0 && de->rec_len >= need) {
de->inode = child_ino;
de->name_len = nl;
de->file_type = file_type;
memcpy(de->name, name, nl);
block_write(fs, (uint32_t)db, bb);
kfree(bb);
return 0;
}
uint16_t actual = (uint16_t)((8 + de->name_len + 3) & ~3);
uint16_t slack = de->rec_len - actual;
if (slack >= need) {
de->rec_len = actual;
ext2_dir_entry_t *ne = (ext2_dir_entry_t *)(bb + off + actual);
ne->inode = child_ino;
ne->rec_len = slack;
ne->name_len = nl;
ne->file_type = file_type;
memcpy(ne->name, name, nl);
block_write(fs, (uint32_t)db, bb);
kfree(bb);
return 0;
}
off += de->rec_len;
}
}
int32_t new_blk = alloc_block(fs);
if (new_blk < 0) { kfree(bb); return (int)new_blk; }
set_block_num(fs, &di, nb, (uint32_t)new_blk);
di.i_size += fs->block_size;
di.i_blocks += fs->block_size / 512;
memset(bb, 0, fs->block_size);
ext2_dir_entry_t *de = (ext2_dir_entry_t *)bb;
de->inode = child_ino;
de->rec_len = (uint16_t)fs->block_size;
de->name_len = nl;
de->file_type = file_type;
memcpy(de->name, name, nl);
block_write(fs, (uint32_t)new_blk, bb);
inode_write(fs, dir_ino, &di);
kfree(bb);
return 0;
}
static int ext2_dir_mkdir(vnode_t *dir, const char *name, uint32_t mode) {
if (!name || !name[0]) return -EINVAL;
ext2_vdata_t *vd = dir->fs_data;
ext2_t *fs = vd->fs;
vnode_t *ex = NULL;
if (ext2_dir_lookup(dir, name, &ex) == 0) { vnode_unref(ex); return -EEXIST; }
int32_t ino = alloc_inode(fs);
if (ino < 0) return (int)ino;
ext2_inode_t ndi;
memset(&ndi, 0, sizeof(ndi));
ndi.i_mode = EXT2_S_IFDIR | (uint16_t)(mode ? mode : 0755);
ndi.i_links_count = 2;
int32_t blk = alloc_block(fs);
if (blk < 0) { free_inode(fs, (uint32_t)ino); return (int)blk; }
ndi.i_block[0] = (uint32_t)blk;
ndi.i_size = fs->block_size;
ndi.i_blocks = fs->block_size / 512;
uint8_t *bb = kzalloc(fs->block_size);
if (!bb) { free_block(fs, (uint32_t)blk); free_inode(fs, (uint32_t)ino); return -ENOMEM; }
ext2_dir_entry_t *dot = (ext2_dir_entry_t *)bb;
dot->inode = (uint32_t)ino; dot->rec_len = 12; dot->name_len = 1;
dot->file_type = EXT2_FT_DIR; dot->name[0] = '.';
ext2_dir_entry_t *dotdot = (ext2_dir_entry_t *)(bb + 12);
dotdot->inode = vd->ino; dotdot->rec_len = (uint16_t)(fs->block_size - 12);
dotdot->name_len = 2; dotdot->file_type = EXT2_FT_DIR;
dotdot->name[0] = '.'; dotdot->name[1] = '.';
block_write(fs, (uint32_t)blk, bb);
kfree(bb);
inode_write(fs, (uint32_t)ino, &ndi);
int r = ext2_dir_add_entry(fs, vd->ino, (uint32_t)ino, EXT2_FT_DIR, name);
if (r < 0) { free_block(fs, (uint32_t)blk); free_inode(fs, (uint32_t)ino); return r; }
ext2_inode_t pdi;
inode_read(fs, vd->ino, &pdi);
pdi.i_links_count++;
inode_write(fs, vd->ino, &pdi);
uint32_t grp = ((uint32_t)ino - 1) / fs->sb.s_inodes_per_group;
fs->gdt[grp].bg_used_dirs_count++;
fs->dirty = true;
dir->size++;
return 0;
}
static int ext2_dir_create(vnode_t *dir, const char *name, uint32_t mode, vnode_t **out) {
if (!name || !name[0]) return -EINVAL;
ext2_vdata_t *vd = dir->fs_data;
ext2_t *fs = vd->fs;
if (ext2_dir_lookup(dir, name, out) == 0) return 0;
int32_t ino = alloc_inode(fs);
if (ino < 0) return (int)ino;
ext2_inode_t ndi;
memset(&ndi, 0, sizeof(ndi));
ndi.i_mode = EXT2_S_IFREG | (uint16_t)(mode ? mode : 0644);
ndi.i_links_count = 1;
inode_write(fs, (uint32_t)ino, &ndi);
int r = ext2_dir_add_entry(fs, vd->ino, (uint32_t)ino, EXT2_FT_REG_FILE, name);
if (r < 0) { free_inode(fs, (uint32_t)ino); return r; }
*out = ext2_make_vnode(fs, (uint32_t)ino, &ndi);
if (!*out) return -ENOMEM;
fs->dirty = true;
dir->size++;
return 0;
}
static int ext2_dir_unlink(vnode_t *dir, const char *name) {
ext2_vdata_t *vd = dir->fs_data;
ext2_t *fs = vd->fs;
ext2_inode_t di;
int r = inode_read(fs, vd->ino, &di);
if (r < 0) return r;
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
size_t nl = strlen(name);
uint32_t nblocks = (di.i_size + fs->block_size - 1) / fs->block_size;
for (uint32_t fb = 0; fb < nblocks; fb++) {
int32_t db = get_block_num(fs, &di, fb);
if (db <= 0) continue;
block_read(fs, (uint32_t)db, bb);
uint32_t off = 0;
ext2_dir_entry_t *prev = NULL;
while (off < fs->block_size) {
ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off);
if (de->rec_len == 0) break;
if (de->inode != 0 && de->name_len == nl && memcmp(de->name, name, nl) == 0) {
uint32_t cino = de->inode;
ext2_inode_t ci;
inode_read(fs, cino, &ci);
bool is_dir = ((ci.i_mode & 0xF000) == EXT2_S_IFDIR);
free_all_blocks(fs, &ci);
free_inode(fs, cino);
if (prev) prev->rec_len += de->rec_len;
else de->inode = 0;
block_write(fs, (uint32_t)db, bb);
if (is_dir) {
ext2_inode_t pdi;
inode_read(fs, vd->ino, &pdi);
if (pdi.i_links_count > 1) pdi.i_links_count--;
inode_write(fs, vd->ino, &pdi);
uint32_t grp = (cino - 1) / fs->sb.s_inodes_per_group;
if (fs->gdt[grp].bg_used_dirs_count > 0)
fs->gdt[grp].bg_used_dirs_count--;
}
dir->size--;
fs->dirty = true;
kfree(bb);
return 0;
}
prev = de;
off += de->rec_len;
}
}
kfree(bb);
return -ENOENT;
}
static int ext2_dir_rename(vnode_t *src_dir, const char *src_name,
vnode_t *dst_dir, const char *dst_name) {
ext2_vdata_t *svd = src_dir->fs_data;
ext2_t *fs = svd->fs;
vnode_t *child = NULL;
int r = ext2_dir_lookup(src_dir, src_name, &child);
if (r < 0) return r;
uint8_t ft = (child->type == VFS_NODE_DIR) ? EXT2_FT_DIR : EXT2_FT_REG_FILE;
uint32_t cino = (uint32_t)child->ino;
vnode_unref(child);
vnode_t *ex = NULL;
if (ext2_dir_lookup(dst_dir, dst_name, &ex) == 0) {
vnode_unref(ex);
ext2_dir_unlink(dst_dir, dst_name);
}
ext2_vdata_t *dvd = dst_dir->fs_data;
r = ext2_dir_add_entry(fs, dvd->ino, cino, ft, dst_name);
if (r < 0) return r;
ext2_inode_t sdi;
inode_read(fs, svd->ino, &sdi);
uint8_t *bb = kmalloc(fs->block_size);
if (!bb) return -ENOMEM;
size_t snl = strlen(src_name);
uint32_t nblocks = (sdi.i_size + fs->block_size - 1) / fs->block_size;
for (uint32_t fb = 0; fb < nblocks; fb++) {
int32_t db = get_block_num(fs, &sdi, fb);
if (db <= 0) continue;
block_read(fs, (uint32_t)db, bb);
uint32_t off = 0;
ext2_dir_entry_t *prev = NULL;
while (off < fs->block_size) {
ext2_dir_entry_t *de = (ext2_dir_entry_t *)(bb + off);
if (de->rec_len == 0) break;
if (de->inode == cino && de->name_len == snl &&
memcmp(de->name, src_name, snl) == 0) {
if (prev) prev->rec_len += de->rec_len;
else de->inode = 0;
block_write(fs, (uint32_t)db, bb);
kfree(bb);
fs->dirty = true;
return 0;
}
prev = de;
off += de->rec_len;
}
}
kfree(bb);
fs->dirty = true;
return 0;
}
static const vnode_ops_t ext2_dir_ops = {
.lookup = ext2_dir_lookup,
.readdir = ext2_dir_readdir,
.mkdir = ext2_dir_mkdir,
.create = ext2_dir_create,
.unlink = ext2_dir_unlink,
.rename = ext2_dir_rename,
.stat = ext2_stat,
.ref = ext2_vnode_ref,
.unref = ext2_vnode_unref,
};
int ext2_format(blkdev_t *dev, const char *label) {
if (!dev) return -EINVAL;
uint64_t disk_bytes = dev->size_bytes;
if (disk_bytes < 64 * 1024) return -ENOSPC;
uint32_t block_size = 1024;
uint32_t total_blocks = (uint32_t)(disk_bytes / block_size);
uint32_t blocks_per_group = block_size * 8;
uint32_t inodes_per_group = (total_blocks < 8192) ? 128 : 256;
uint32_t groups_count = (total_blocks + blocks_per_group - 1) / blocks_per_group;
if (groups_count == 0) groups_count = 1;
uint32_t total_inodes = inodes_per_group * groups_count;
uint32_t inode_size = 128;
uint32_t it_blocks_pg = (inodes_per_group * inode_size + block_size - 1) / block_size;
uint32_t first_data_block = 1;
uint8_t *zb = kzalloc(block_size);
if (!zb) return -ENOMEM;
for (uint32_t b = 0; b < total_blocks && b < 16; b++)
blkdev_write(dev, (uint64_t)b * block_size, zb, block_size);
ext2_superblock_t sb;
memset(&sb, 0, sizeof(sb));
sb.s_inodes_count = total_inodes;
sb.s_blocks_count = total_blocks;
sb.s_r_blocks_count = total_blocks / 20;
sb.s_free_blocks_count = total_blocks;
sb.s_free_inodes_count = total_inodes;
sb.s_first_data_block = first_data_block;
sb.s_log_block_size = 0;
sb.s_blocks_per_group = blocks_per_group;
sb.s_frags_per_group = blocks_per_group;
sb.s_inodes_per_group = inodes_per_group;
sb.s_magic = EXT2_SUPER_MAGIC;
sb.s_state = EXT2_VALID_FS;
sb.s_errors = 1;
sb.s_rev_level = EXT2_DYNAMIC_REV;
sb.s_first_ino = 11;
sb.s_inode_size = (uint16_t)inode_size;
sb.s_max_mnt_count = 20;
if (label) strncpy(sb.s_volume_name, label, 15);
uint32_t gdt_block = first_data_block + 1;
uint32_t gdt_blocks = (groups_count * sizeof(ext2_group_desc_t) + block_size - 1) / block_size;
ext2_group_desc_t *gdt = kzalloc(gdt_blocks * block_size);
if (!gdt) { kfree(zb); return -ENOMEM; }
uint32_t used_total = first_data_block;
printf(" ext2: ");
uint32_t last_pct_ext2 = 999;
uint32_t spinner_ext2 = 0;
for (uint32_t g = 0; g < groups_count; g++) {
uint32_t gs = g * blocks_per_group + first_data_block;
uint32_t cb = (g == 0) ? (gdt_block + gdt_blocks) : gs;
gdt[g].bg_block_bitmap = cb++;
gdt[g].bg_inode_bitmap = cb++;
gdt[g].bg_inode_table = cb;
cb += it_blocks_pg;
uint32_t meta = cb - gs;
if (g == 0) meta = cb - first_data_block;
uint32_t gb = blocks_per_group;
if (g == groups_count - 1) gb = total_blocks - gs;
gdt[g].bg_free_blocks_count = (gb > meta) ? (uint16_t)(gb - meta) : 0;
gdt[g].bg_free_inodes_count = (uint16_t)inodes_per_group;
used_total += meta;
for (uint32_t t = 0; t < it_blocks_pg; t++)
blkdev_write(dev, (uint64_t)(gdt[g].bg_inode_table + t) * block_size, zb, block_size);
uint8_t *bbmp = kzalloc(block_size);
for (uint32_t i = 0; i < meta; i++) bmp_set(bbmp, i);
if (g == 0)
for (uint32_t i = 0; i < (gdt_block + gdt_blocks - first_data_block); i++)
bmp_set(bbmp, i);
for (uint32_t i = gb; i < blocks_per_group; i++) bmp_set(bbmp, i);
blkdev_write(dev, (uint64_t)gdt[g].bg_block_bitmap * block_size, bbmp, block_size);
kfree(bbmp);
uint8_t *ibmp = kzalloc(block_size);
if (g == 0) {
for (uint32_t i = 0; i < 11; i++) { bmp_set(ibmp, i); gdt[g].bg_free_inodes_count--; }
}
for (uint32_t i = inodes_per_group; i < block_size * 8; i++) bmp_set(ibmp, i);
blkdev_write(dev, (uint64_t)gdt[g].bg_inode_bitmap * block_size, ibmp, block_size);
kfree(ibmp);
uint32_t pct = ((g + 1) * 100) / groups_count;
if (pct != last_pct_ext2) {
static const char glyphs[4] = { '|', '/', '-', '\\' };
printf("\r \r %c ext2: %u%% (group %u/%u)",
glyphs[spinner_ext2 & 3], pct, g + 1, groups_count);
spinner_ext2++;
last_pct_ext2 = pct;
}
}
printf("\r \r ext2: done\n");
sb.s_free_blocks_count = total_blocks - used_total;
sb.s_free_inodes_count = total_inodes - 11;
int32_t root_blk = -1;
{
uint8_t *bbmp = kmalloc(block_size);
blkdev_read(dev, (uint64_t)gdt[0].bg_block_bitmap * block_size, bbmp, block_size);
for (uint32_t i = 0; i < blocks_per_group; i++) {
if (!bmp_test(bbmp, i)) {
bmp_set(bbmp, i);
blkdev_write(dev, (uint64_t)gdt[0].bg_block_bitmap * block_size, bbmp, block_size);
root_blk = (int32_t)(i + first_data_block);
gdt[0].bg_free_blocks_count--;
sb.s_free_blocks_count--;
break;
}
}
kfree(bbmp);
}
if (root_blk < 0) { kfree(gdt); kfree(zb); return -ENOSPC; }
ext2_inode_t ri;
memset(&ri, 0, sizeof(ri));
ri.i_mode = EXT2_S_IFDIR | 0755;
ri.i_links_count = 2;
ri.i_block[0] = (uint32_t)root_blk;
ri.i_size = block_size;
ri.i_blocks = block_size / 512;
uint64_t roff = (uint64_t)gdt[0].bg_inode_table * block_size + (uint64_t)(EXT2_ROOT_INO - 1) * inode_size;
blkdev_write(dev, roff, &ri, sizeof(ri));
uint8_t *rdb = kzalloc(block_size);
ext2_dir_entry_t *dot = (ext2_dir_entry_t *)rdb;
dot->inode = EXT2_ROOT_INO; dot->rec_len = 12; dot->name_len = 1;
dot->file_type = EXT2_FT_DIR; dot->name[0] = '.';
ext2_dir_entry_t *dotdot = (ext2_dir_entry_t *)(rdb + 12);
dotdot->inode = EXT2_ROOT_INO; dotdot->rec_len = (uint16_t)(block_size - 12);
dotdot->name_len = 2; dotdot->file_type = EXT2_FT_DIR;
dotdot->name[0] = '.'; dotdot->name[1] = '.';
blkdev_write(dev, (uint64_t)root_blk * block_size, rdb, block_size);
kfree(rdb);
gdt[0].bg_used_dirs_count = 1;
blkdev_write(dev, EXT2_SUPER_OFFSET, &sb, sizeof(sb));
blkdev_write(dev, (uint64_t)gdt_block * block_size, gdt, gdt_blocks * block_size);
kfree(gdt);
kfree(zb);
serial_printf("[ext2] formatted: %u blocks (%u KiB), %u inodes, bs=%u, label='%s'\n",
total_blocks, total_blocks * block_size / 1024, total_inodes, block_size,
label ? label : "");
return 0;
}
vnode_t *ext2_mount(blkdev_t *dev) {
if (!dev) return NULL;
ext2_t *fs = kzalloc(sizeof(ext2_t));
if (!fs) return NULL;
fs->dev = dev;
if (blkdev_read(dev, EXT2_SUPER_OFFSET, &fs->sb, sizeof(fs->sb)) < 0) { kfree(fs); return NULL; }
if (fs->sb.s_magic != EXT2_SUPER_MAGIC) {
serial_printf("[ext2] bad magic: 0x%x (expected 0x%x)\n", fs->sb.s_magic, EXT2_SUPER_MAGIC);
kfree(fs);
return NULL;
}
fs->block_size = 1024 << fs->sb.s_log_block_size;
fs->inode_size = (fs->sb.s_rev_level >= EXT2_DYNAMIC_REV) ? fs->sb.s_inode_size : 128;
fs->groups_count = (fs->sb.s_blocks_count + fs->sb.s_blocks_per_group - 1) / fs->sb.s_blocks_per_group;
fs->inodes_per_block = fs->block_size / fs->inode_size;
fs->ptrs_per_block = fs->block_size / 4;
uint32_t gdt_block = (fs->block_size == 1024) ? 2 : 1;
uint32_t gdt_sz = fs->groups_count * sizeof(ext2_group_desc_t);
uint32_t gdt_blocks = (gdt_sz + fs->block_size - 1) / fs->block_size;
fs->gdt = kmalloc(gdt_blocks * fs->block_size);
if (!fs->gdt) { kfree(fs); return NULL; }
if (blkdev_read(dev, (uint64_t)gdt_block * fs->block_size, fs->gdt, gdt_blocks * fs->block_size) < 0) {
kfree(fs->gdt); kfree(fs); return NULL;
}
ext2_inode_t root_di;
if (inode_read(fs, EXT2_ROOT_INO, &root_di) < 0) { kfree(fs->gdt); kfree(fs); return NULL; }
vnode_t *root = ext2_make_vnode(fs, EXT2_ROOT_INO, &root_di);
if (!root) { kfree(fs->gdt); kfree(fs); return NULL; }
serial_printf("[ext2] mounted '%s': %u blocks (%u free), %u inodes (%u free), bs=%u\n",
fs->sb.s_volume_name, fs->sb.s_blocks_count, fs->sb.s_free_blocks_count,
fs->sb.s_inodes_count, fs->sb.s_free_inodes_count, fs->block_size);
return root;
}
void ext2_sync(ext2_t *fs) {
if (!fs || !fs->dirty) return;
sb_flush(fs);
gdt_flush(fs);
if (fs->dev->ops && fs->dev->ops->flush) fs->dev->ops->flush(fs->dev);
fs->dirty = false;
}
void ext2_unmount(ext2_t *fs) {
if (!fs) return;
ext2_sync(fs);
if (fs->gdt) kfree(fs->gdt);
kfree(fs);
}
int ext2_statvfs(vnode_t *root, vfs_statvfs_t *out) {
if (!root || !root->fs_data || !out) return -EINVAL;
ext2_vdata_t *vd = (ext2_vdata_t *)root->fs_data;
ext2_t *fs = vd->fs;
if (!fs) return -EINVAL;
out->f_bsize = fs->block_size;
out->f_blocks = fs->sb.s_blocks_count;
out->f_bfree = fs->sb.s_free_blocks_count;
out->f_bavail = fs->sb.s_free_blocks_count;
out->f_files = fs->sb.s_inodes_count;
out->f_ffree = fs->sb.s_free_inodes_count;
out->f_flag = 0;
out->f_namemax = 255;
return 0;
}