#include "diskfs.h"
#include "ata_pio.h"
#include "errno.h"
#include "heap.h"
#include "utils.h"
#include <stddef.h>
#include <stdint.h>
static int g_diskfs_drive = 0;
// Very small on-disk FS stored starting at LBA2.
// - LBA0 reserved
// - LBA1 used by legacy persist counter storage
// - LBA2..LBA3 superblock (2 sectors)
// - data blocks allocated linearly from DISKFS_LBA_DATA_START upward
//
// Not a full POSIX FS; goal is persistent files with minimal directory hierarchy.
#define DISKFS_LBA_SUPER 2U
#define DISKFS_LBA_SUPER2 3U
#define DISKFS_LBA_DATA_START 4U
#define DISKFS_MAGIC 0x44465331U /* 'DFS1' */
#define DISKFS_VERSION 3U
#define DISKFS_MAX_INODES 24
#define DISKFS_NAME_MAX 24
#define DISKFS_SECTOR 512U
#define DISKFS_DEFAULT_CAP_SECTORS 8U /* 4KB */
enum {
DISKFS_INODE_FREE = 0,
DISKFS_INODE_FILE = 1,
DISKFS_INODE_DIR = 2,
};
struct diskfs_inode {
uint8_t type;
uint8_t nlink; /* hard link count (0 or 1 = single link) */
uint16_t parent;
char name[DISKFS_NAME_MAX];
uint32_t start_lba;
uint32_t size_bytes;
uint32_t cap_sectors;
};
struct diskfs_super {
uint32_t magic;
uint32_t version;
uint32_t next_free_lba;
struct diskfs_inode inodes[DISKFS_MAX_INODES];
};
// v2 on-disk format (flat dirents) for migration.
struct diskfs_super_v2 {
uint32_t magic;
uint32_t version;
uint32_t file_count;
uint32_t next_free_lba;
struct {
char name[32];
uint32_t start_lba;
uint32_t size_bytes;
uint32_t cap_sectors;
} files[12];
};
struct diskfs_node {
fs_node_t vfs;
uint16_t ino;
};
static struct diskfs_node g_root;
static uint32_t g_ready = 0;
static int diskfs_super_store(const struct diskfs_super* sb);
static void diskfs_strlcpy(char* dst, const char* src, size_t dst_sz) {
if (!dst || dst_sz == 0) return;
if (!src) {
dst[0] = 0;
return;
}
size_t i = 0;
for (; src[i] != 0 && i + 1 < dst_sz; i++) {
dst[i] = src[i];
}
dst[i] = 0;
}
static void diskfs_close_impl(fs_node_t* node) {
if (!node) return;
struct diskfs_node* dn = (struct diskfs_node*)node;
if (dn == &g_root) return;
kfree(dn);
}
static int diskfs_super_load(struct diskfs_super* sb) {
if (!sb) return -EINVAL;
uint8_t sec0[DISKFS_SECTOR];
uint8_t sec1[DISKFS_SECTOR];
if (ata_pio_read28(g_diskfs_drive, DISKFS_LBA_SUPER, sec0) < 0) return -EIO;
if (ata_pio_read28(g_diskfs_drive, DISKFS_LBA_SUPER2, sec1) < 0) return -EIO;
if (sizeof(*sb) > (size_t)(DISKFS_SECTOR * 2U)) return -EIO;
memcpy(sb, sec0, DISKFS_SECTOR);
if (sizeof(*sb) > DISKFS_SECTOR) {
memcpy(((uint8_t*)sb) + DISKFS_SECTOR, sec1, sizeof(*sb) - DISKFS_SECTOR);
}
if (sb->magic != DISKFS_MAGIC) {
memset(sb, 0, sizeof(*sb));
sb->magic = DISKFS_MAGIC;
sb->version = DISKFS_VERSION;
sb->next_free_lba = DISKFS_LBA_DATA_START;
// Root inode
sb->inodes[0].type = DISKFS_INODE_DIR;
sb->inodes[0].parent = 0;
sb->inodes[0].name[0] = 0;
return diskfs_super_store(sb);
}
if (sb->version == DISKFS_VERSION) {
if (sb->next_free_lba < DISKFS_LBA_DATA_START) sb->next_free_lba = DISKFS_LBA_DATA_START;
if (sb->inodes[0].type != DISKFS_INODE_DIR) {
sb->inodes[0].type = DISKFS_INODE_DIR;
sb->inodes[0].parent = 0;
sb->inodes[0].name[0] = 0;
(void)diskfs_super_store(sb);
}
return 0;
}
// Migration path: v2 -> v3
if (sb->version == 2U) {
struct diskfs_super_v2 old;
memset(&old, 0, sizeof(old));
if (sizeof(old) > (size_t)(DISKFS_SECTOR * 2U)) return -EIO;
memcpy(&old, sec0, DISKFS_SECTOR);
if (sizeof(old) > DISKFS_SECTOR) {
memcpy(((uint8_t*)&old) + DISKFS_SECTOR, sec1, sizeof(old) - DISKFS_SECTOR);
}
if (old.magic != DISKFS_MAGIC || old.version != 2U) return -EIO;
memset(sb, 0, sizeof(*sb));
sb->magic = DISKFS_MAGIC;
sb->version = DISKFS_VERSION;
sb->next_free_lba = old.next_free_lba;
if (sb->next_free_lba < DISKFS_LBA_DATA_START) sb->next_free_lba = DISKFS_LBA_DATA_START;
sb->inodes[0].type = DISKFS_INODE_DIR;
sb->inodes[0].parent = 0;
sb->inodes[0].name[0] = 0;
uint32_t n = old.file_count;
if (n > 12U) n = 12U;
uint16_t ino = 1;
for (uint32_t i = 0; i < n && ino < DISKFS_MAX_INODES; i++) {
if (old.files[i].name[0] == 0) continue;
sb->inodes[ino].type = DISKFS_INODE_FILE;
sb->inodes[ino].parent = 0;
diskfs_strlcpy(sb->inodes[ino].name, old.files[i].name, sizeof(sb->inodes[ino].name));
sb->inodes[ino].start_lba = old.files[i].start_lba;
sb->inodes[ino].size_bytes = old.files[i].size_bytes;
sb->inodes[ino].cap_sectors = old.files[i].cap_sectors;
ino++;
}
return diskfs_super_store(sb);
}
// Unknown version -> re-init (best-effort)
memset(sb, 0, sizeof(*sb));
sb->magic = DISKFS_MAGIC;
sb->version = DISKFS_VERSION;
sb->next_free_lba = DISKFS_LBA_DATA_START;
sb->inodes[0].type = DISKFS_INODE_DIR;
sb->inodes[0].parent = 0;
sb->inodes[0].name[0] = 0;
return diskfs_super_store(sb);
}
static int diskfs_super_store(const struct diskfs_super* sb) {
if (!sb) return -EINVAL;
uint8_t sec0[DISKFS_SECTOR];
uint8_t sec1[DISKFS_SECTOR];
if (sizeof(*sb) > (size_t)(DISKFS_SECTOR * 2U)) return -EIO;
memset(sec0, 0, sizeof(sec0));
memset(sec1, 0, sizeof(sec1));
memcpy(sec0, sb, DISKFS_SECTOR);
if (sizeof(*sb) > DISKFS_SECTOR) {
memcpy(sec1, ((const uint8_t*)sb) + DISKFS_SECTOR, sizeof(*sb) - DISKFS_SECTOR);
}
if (ata_pio_write28(g_diskfs_drive, DISKFS_LBA_SUPER, sec0) < 0) return -EIO;
if (ata_pio_write28(g_diskfs_drive, DISKFS_LBA_SUPER2, sec1) < 0) return -EIO;
return 0;
}
static int diskfs_segment_valid(const char* name) {
if (!name || name[0] == 0) return 0;
for (uint32_t i = 0; name[i] != 0; i++) {
if (i + 1 >= DISKFS_NAME_MAX) return 0;
}
return 1;
}
static int diskfs_find_child(const struct diskfs_super* sb, uint16_t parent, const char* name) {
if (!sb || !name) return -1;
for (uint16_t i = 0; i < DISKFS_MAX_INODES; i++) {
if (sb->inodes[i].type == DISKFS_INODE_FREE) continue;
if (sb->inodes[i].parent != parent) continue;
if (sb->inodes[i].name[0] == 0) continue;
if (strcmp(sb->inodes[i].name, name) == 0) return (int)i;
}
return -1;
}
static int diskfs_alloc_inode_file(struct diskfs_super* sb, uint16_t parent, const char* name, uint32_t cap_sectors, uint16_t* out_ino) {
if (!sb || !name || !out_ino) return -EINVAL;
if (!diskfs_segment_valid(name)) return -EINVAL;
for (uint16_t i = 1; i < DISKFS_MAX_INODES; i++) {
if (sb->inodes[i].type != DISKFS_INODE_FREE) continue;
sb->inodes[i].type = DISKFS_INODE_FILE;
sb->inodes[i].parent = parent;
memset(sb->inodes[i].name, 0, sizeof(sb->inodes[i].name));
strcpy(sb->inodes[i].name, name);
sb->inodes[i].start_lba = sb->next_free_lba;
sb->inodes[i].size_bytes = 0;
sb->inodes[i].cap_sectors = cap_sectors ? cap_sectors : DISKFS_DEFAULT_CAP_SECTORS;
sb->next_free_lba += sb->inodes[i].cap_sectors;
uint8_t zero[DISKFS_SECTOR];
memset(zero, 0, sizeof(zero));
for (uint32_t s = 0; s < sb->inodes[i].cap_sectors; s++) {
(void)ata_pio_write28(g_diskfs_drive, sb->inodes[i].start_lba + s, zero);
}
*out_ino = i;
return 0;
}
return -ENOSPC;
}
static int diskfs_split_next(const char** p_inout, char* out, size_t out_sz) {
if (!p_inout || !*p_inout || !out || out_sz == 0) return 0;
const char* p = *p_inout;
while (*p == '/') p++;
if (*p == 0) {
*p_inout = p;
out[0] = 0;
return 0;
}
size_t i = 0;
while (*p != 0 && *p != '/') {
if (i + 1 < out_sz) out[i++] = *p;
p++;
}
out[i] = 0;
while (*p == '/') p++;
*p_inout = p;
return out[0] != 0;
}
static int diskfs_lookup_path(struct diskfs_super* sb, const char* path, uint16_t* out_ino, uint16_t* out_parent, char* out_last, size_t out_last_sz) {
if (!sb || !path || !out_ino) return -EINVAL;
const char* p = path;
uint16_t cur = 0;
uint16_t parent = 0;
char part[DISKFS_NAME_MAX];
char last[DISKFS_NAME_MAX];
last[0] = 0;
while (diskfs_split_next(&p, part, sizeof(part))) {
if (!diskfs_segment_valid(part)) return -EINVAL;
parent = cur;
strcpy(last, part);
int c = diskfs_find_child(sb, cur, part);
if (c < 0) {
if (out_parent) *out_parent = parent;
if (out_last && out_last_sz) {
diskfs_strlcpy(out_last, last, out_last_sz);
}
return -ENOENT;
}
cur = (uint16_t)c;
if (sb->inodes[cur].type != DISKFS_INODE_DIR && *p != 0) {
if (out_parent) *out_parent = parent;
if (out_last && out_last_sz) diskfs_strlcpy(out_last, last, out_last_sz);
return -ENOTDIR;
}
}
if (out_parent) *out_parent = parent;
if (out_last && out_last_sz) diskfs_strlcpy(out_last, last, out_last_sz);
*out_ino = cur;
return 0;
}
struct diskfs_kdirent {
uint32_t d_ino;
uint16_t d_reclen;
uint8_t d_type;
char d_name[DISKFS_NAME_MAX];
};
static uint32_t diskfs_read_impl(fs_node_t* node, uint32_t offset, uint32_t size, uint8_t* buffer) {
if (!node || !buffer) return 0;
if (node->flags != FS_FILE) return 0;
if (!g_ready) return 0;
struct diskfs_node* dn = (struct diskfs_node*)node;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return 0;
if (dn->ino >= DISKFS_MAX_INODES) return 0;
if (sb.inodes[dn->ino].type != DISKFS_INODE_FILE) return 0;
struct diskfs_inode* de = &sb.inodes[dn->ino];
if (offset >= de->size_bytes) return 0;
if (offset + size > de->size_bytes) size = de->size_bytes - offset;
if (size == 0) return 0;
uint32_t total = 0;
while (total < size) {
uint32_t pos = offset + total;
uint32_t lba_off = pos / DISKFS_SECTOR;
uint32_t sec_off = pos % DISKFS_SECTOR;
uint32_t chunk = size - total;
if (chunk > (DISKFS_SECTOR - sec_off)) chunk = DISKFS_SECTOR - sec_off;
if (lba_off >= de->cap_sectors) break;
uint8_t sec[DISKFS_SECTOR];
if (ata_pio_read28(g_diskfs_drive, de->start_lba + lba_off, sec) < 0) break;
memcpy(buffer + total, sec + sec_off, chunk);
total += chunk;
}
return total;
}
static uint32_t diskfs_write_impl(fs_node_t* node, uint32_t offset, uint32_t size, const uint8_t* buffer) {
if (!node || !buffer) return 0;
if (node->flags != FS_FILE) return 0;
if (!g_ready) return 0;
struct diskfs_node* dn = (struct diskfs_node*)node;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return 0;
if (dn->ino >= DISKFS_MAX_INODES) return 0;
if (sb.inodes[dn->ino].type != DISKFS_INODE_FILE) return 0;
struct diskfs_inode* de = &sb.inodes[dn->ino];
uint64_t end = (uint64_t)offset + (uint64_t)size;
if (end > 0xFFFFFFFFULL) return 0;
uint32_t need_bytes = (uint32_t)end;
uint32_t need_sectors = (need_bytes + DISKFS_SECTOR - 1U) / DISKFS_SECTOR;
if (need_sectors > de->cap_sectors) {
// Grow by allocating a new extent at end, copy old contents.
uint32_t new_cap = de->cap_sectors;
while (new_cap < need_sectors) {
new_cap *= 2U;
if (new_cap == 0) return 0;
}
uint32_t new_start = sb.next_free_lba;
sb.next_free_lba += new_cap;
uint8_t sec[DISKFS_SECTOR];
for (uint32_t s = 0; s < new_cap; s++) {
memset(sec, 0, sizeof(sec));
if (s < de->cap_sectors) {
if (ata_pio_read28(g_diskfs_drive, de->start_lba + s, sec) < 0) {
return 0;
}
}
(void)ata_pio_write28(g_diskfs_drive, new_start + s, sec);
}
de->start_lba = new_start;
de->cap_sectors = new_cap;
}
uint32_t total = 0;
while (total < size) {
uint32_t pos = offset + total;
uint32_t lba_off = pos / DISKFS_SECTOR;
uint32_t sec_off = pos % DISKFS_SECTOR;
uint32_t chunk = size - total;
if (chunk > (DISKFS_SECTOR - sec_off)) chunk = DISKFS_SECTOR - sec_off;
if (lba_off >= de->cap_sectors) break;
uint8_t sec[DISKFS_SECTOR];
if (sec_off != 0 || chunk != DISKFS_SECTOR) {
if (ata_pio_read28(g_diskfs_drive, de->start_lba + lba_off, sec) < 0) break;
} else {
memset(sec, 0, sizeof(sec));
}
memcpy(sec + sec_off, buffer + total, chunk);
if (ata_pio_write28(g_diskfs_drive, de->start_lba + lba_off, sec) < 0) break;
total += chunk;
}
if (offset + total > de->size_bytes) {
de->size_bytes = offset + total;
}
if (diskfs_super_store(&sb) < 0) return total;
node->length = de->size_bytes;
return total;
}
static int diskfs_readdir_impl(struct fs_node* node, uint32_t* inout_index, void* buf, uint32_t buf_len) {
if (!node || !inout_index || !buf) return -1;
if (node->flags != FS_DIRECTORY) return -1;
if (buf_len < sizeof(struct vfs_dirent)) return -1;
struct diskfs_node* dn = (struct diskfs_node*)node;
uint16_t dir_ino = dn->ino;
// Use diskfs_getdents which fills diskfs_kdirent; convert to vfs_dirent.
struct diskfs_kdirent kbuf[8];
uint32_t klen = sizeof(kbuf);
if (klen > buf_len) klen = buf_len;
uint32_t idx = *inout_index;
int rc = diskfs_getdents(dir_ino, &idx, kbuf, klen);
if (rc <= 0) return rc;
uint32_t nents = (uint32_t)rc / (uint32_t)sizeof(struct diskfs_kdirent);
uint32_t cap = buf_len / (uint32_t)sizeof(struct vfs_dirent);
if (nents > cap) nents = cap;
struct vfs_dirent* out = (struct vfs_dirent*)buf;
for (uint32_t i = 0; i < nents; i++) {
memset(&out[i], 0, sizeof(out[i]));
out[i].d_ino = kbuf[i].d_ino;
out[i].d_reclen = (uint16_t)sizeof(struct vfs_dirent);
out[i].d_type = kbuf[i].d_type;
diskfs_strlcpy(out[i].d_name, kbuf[i].d_name, sizeof(out[i].d_name));
}
*inout_index = idx;
return (int)(nents * (uint32_t)sizeof(struct vfs_dirent));
}
static int diskfs_vfs_truncate(struct fs_node* node, uint32_t length);
static struct fs_node* diskfs_root_finddir(struct fs_node* node, const char* name);
static int diskfs_vfs_create(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out);
static int diskfs_vfs_mkdir(struct fs_node* dir, const char* name);
static int diskfs_vfs_unlink(struct fs_node* dir, const char* name);
static int diskfs_vfs_rmdir(struct fs_node* dir, const char* name);
static int diskfs_vfs_rename(struct fs_node* old_dir, const char* old_name,
struct fs_node* new_dir, const char* new_name);
static int diskfs_vfs_link(struct fs_node* dir, const char* name, struct fs_node* target);
static const struct file_operations diskfs_file_fops = {
.read = diskfs_read_impl,
.write = diskfs_write_impl,
.close = diskfs_close_impl,
};
static const struct inode_operations diskfs_file_iops = {
.truncate = diskfs_vfs_truncate,
};
static const struct file_operations diskfs_dir_fops = {
.close = diskfs_close_impl,
};
static const struct inode_operations diskfs_dir_iops = {
.lookup = diskfs_root_finddir,
.readdir = diskfs_readdir_impl,
.create = diskfs_vfs_create,
.mkdir = diskfs_vfs_mkdir,
.unlink = diskfs_vfs_unlink,
.rmdir = diskfs_vfs_rmdir,
.rename = diskfs_vfs_rename,
.link = diskfs_vfs_link,
};
static struct fs_node* diskfs_root_finddir(struct fs_node* node, const char* name) {
struct diskfs_node* parent = (struct diskfs_node*)node;
if (!g_ready) return 0;
if (!name || name[0] == 0) return 0;
if (!diskfs_segment_valid(name)) return 0;
uint16_t parent_ino = parent ? parent->ino : 0;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return 0;
if (parent_ino >= DISKFS_MAX_INODES) return 0;
if (sb.inodes[parent_ino].type != DISKFS_INODE_DIR) return 0;
int child = diskfs_find_child(&sb, parent_ino, name);
if (child < 0) return 0;
uint16_t cino = (uint16_t)child;
struct diskfs_node* dn = (struct diskfs_node*)kmalloc(sizeof(*dn));
if (!dn) return 0;
memset(dn, 0, sizeof(*dn));
strcpy(dn->vfs.name, name);
dn->vfs.inode = 100 + (uint32_t)cino;
dn->ino = cino;
if (sb.inodes[cino].type == DISKFS_INODE_DIR) {
dn->vfs.flags = FS_DIRECTORY;
dn->vfs.length = 0;
dn->vfs.f_ops = &diskfs_dir_fops;
dn->vfs.i_ops = &diskfs_dir_iops;
} else {
dn->vfs.flags = FS_FILE;
dn->vfs.length = sb.inodes[cino].size_bytes;
dn->vfs.f_ops = &diskfs_file_fops;
dn->vfs.i_ops = &diskfs_file_iops;
}
return &dn->vfs;
}
int diskfs_open_file(const char* rel_path, uint32_t flags, fs_node_t** out_node) {
if (!out_node) return -EINVAL;
*out_node = 0;
if (!g_ready) return -ENODEV;
if (!rel_path || rel_path[0] == 0) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t ino = 0;
uint16_t parent = 0;
char last[DISKFS_NAME_MAX];
last[0] = 0;
int rc = diskfs_lookup_path(&sb, rel_path, &ino, &parent, last, sizeof(last));
if (rc == -ENOENT) {
if ((flags & 0x40U) == 0U) return -ENOENT; // O_CREAT
if (last[0] == 0) return -EINVAL;
// Ensure intermediate dirs exist: lookup again but stop before last segment.
// We already have parent inode from lookup_path failure.
if (parent >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[parent].type != DISKFS_INODE_DIR) return -ENOTDIR;
uint16_t new_ino = 0;
rc = diskfs_alloc_inode_file(&sb, parent, last, DISKFS_DEFAULT_CAP_SECTORS, &new_ino);
if (rc < 0) return rc;
if (diskfs_super_store(&sb) < 0) return -EIO;
ino = new_ino;
} else if (rc < 0) {
return rc;
}
if (ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[ino].type != DISKFS_INODE_FILE) return -EISDIR;
if ((flags & 0x200U) != 0U) { // O_TRUNC
sb.inodes[ino].size_bytes = 0;
if (diskfs_super_store(&sb) < 0) return -EIO;
}
// Build a transient vfs node for this inode.
struct diskfs_node* dn = (struct diskfs_node*)kmalloc(sizeof(*dn));
if (!dn) return -ENOMEM;
memset(dn, 0, sizeof(*dn));
diskfs_strlcpy(dn->vfs.name, last, sizeof(dn->vfs.name));
dn->vfs.flags = FS_FILE;
dn->vfs.inode = 100 + (uint32_t)ino;
dn->vfs.length = sb.inodes[ino].size_bytes;
dn->vfs.f_ops = &diskfs_file_fops;
dn->vfs.i_ops = &diskfs_file_iops;
dn->ino = ino;
*out_node = &dn->vfs;
return 0;
}
int diskfs_mkdir(const char* rel_path) {
if (!g_ready) return -ENODEV;
if (!rel_path || rel_path[0] == 0) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t ino = 0;
uint16_t parent = 0;
char last[DISKFS_NAME_MAX];
last[0] = 0;
int rc = diskfs_lookup_path(&sb, rel_path, &ino, &parent, last, sizeof(last));
if (rc == 0) {
return -EEXIST;
}
if (rc != -ENOENT) return rc;
if (last[0] == 0) return -EINVAL;
if (parent >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[parent].type != DISKFS_INODE_DIR) return -ENOTDIR;
for (uint16_t i = 1; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type != DISKFS_INODE_FREE) continue;
sb.inodes[i].type = DISKFS_INODE_DIR;
sb.inodes[i].parent = parent;
memset(sb.inodes[i].name, 0, sizeof(sb.inodes[i].name));
diskfs_strlcpy(sb.inodes[i].name, last, sizeof(sb.inodes[i].name));
sb.inodes[i].start_lba = 0;
sb.inodes[i].size_bytes = 0;
sb.inodes[i].cap_sectors = 0;
return diskfs_super_store(&sb);
}
return -ENOSPC;
}
int diskfs_unlink(const char* rel_path) {
if (!g_ready) return -ENODEV;
if (!rel_path || rel_path[0] == 0) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t ino = 0;
int rc = diskfs_lookup_path(&sb, rel_path, &ino, 0, 0, 0);
if (rc < 0) return rc;
if (ino == 0) return -EPERM;
if (ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[ino].type == DISKFS_INODE_DIR) return -EISDIR;
if (sb.inodes[ino].type != DISKFS_INODE_FILE) return -ENOENT;
uint32_t shared_lba = sb.inodes[ino].start_lba;
memset(&sb.inodes[ino], 0, sizeof(sb.inodes[ino]));
/* Check if any other inode still references the same data blocks */
int still_referenced = 0;
if (shared_lba != 0) {
for (uint16_t i = 0; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type == DISKFS_INODE_FILE &&
sb.inodes[i].start_lba == shared_lba) {
still_referenced = 1;
break;
}
}
}
(void)still_referenced; /* data blocks are never reclaimed in current impl */
return diskfs_super_store(&sb);
}
int diskfs_link(const char* old_rel, const char* new_rel) {
if (!g_ready) return -ENODEV;
if (!old_rel || old_rel[0] == 0) return -EINVAL;
if (!new_rel || new_rel[0] == 0) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t old_ino = 0;
int rc = diskfs_lookup_path(&sb, old_rel, &old_ino, 0, 0, 0);
if (rc < 0) return rc;
if (old_ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[old_ino].type != DISKFS_INODE_FILE) return -EPERM;
/* Find the new name's parent directory and base name */
const char* new_base = new_rel;
uint16_t new_parent = 0;
for (const char* p = new_rel; *p; p++) {
if (*p == '/') new_base = p + 1;
}
/* For simplicity, new link must be in root directory (parent=0) */
/* Check new name doesn't already exist */
uint16_t dummy = 0;
if (diskfs_lookup_path(&sb, new_rel, &dummy, 0, 0, 0) == 0) return -EEXIST;
/* Find a free inode slot */
uint16_t new_ino = 0;
for (uint16_t i = 1; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type == DISKFS_INODE_FREE) { new_ino = i; break; }
}
if (new_ino == 0) return -ENOSPC;
/* Create new inode sharing same data blocks */
sb.inodes[new_ino].type = DISKFS_INODE_FILE;
sb.inodes[new_ino].nlink = 2;
sb.inodes[new_ino].parent = new_parent;
diskfs_strlcpy(sb.inodes[new_ino].name, new_base, DISKFS_NAME_MAX);
sb.inodes[new_ino].start_lba = sb.inodes[old_ino].start_lba;
sb.inodes[new_ino].size_bytes = sb.inodes[old_ino].size_bytes;
sb.inodes[new_ino].cap_sectors = sb.inodes[old_ino].cap_sectors;
/* Update old inode nlink */
if (sb.inodes[old_ino].nlink < 2) sb.inodes[old_ino].nlink = 2;
else sb.inodes[old_ino].nlink++;
return diskfs_super_store(&sb);
}
int diskfs_rmdir(const char* rel_path) {
if (!g_ready) return -ENODEV;
if (!rel_path || rel_path[0] == 0) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t ino = 0;
int rc = diskfs_lookup_path(&sb, rel_path, &ino, 0, 0, 0);
if (rc < 0) return rc;
if (ino == 0) return -EPERM;
if (ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[ino].type != DISKFS_INODE_DIR) return -ENOTDIR;
// Check directory is empty (no children).
for (uint16_t i = 0; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type == DISKFS_INODE_FREE) continue;
if (sb.inodes[i].parent == ino && i != ino) return -ENOTEMPTY;
}
memset(&sb.inodes[ino], 0, sizeof(sb.inodes[ino]));
return diskfs_super_store(&sb);
}
int diskfs_rename(const char* old_rel, const char* new_rel) {
if (!g_ready) return -ENODEV;
if (!old_rel || old_rel[0] == 0) return -EINVAL;
if (!new_rel || new_rel[0] == 0) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t src_ino = 0;
int rc = diskfs_lookup_path(&sb, old_rel, &src_ino, 0, 0, 0);
if (rc < 0) return rc;
if (src_ino == 0) return -EPERM;
if (src_ino >= DISKFS_MAX_INODES) return -EIO;
// Resolve destination: if it exists, it must be same type or we fail.
uint16_t dst_ino = 0;
uint16_t dst_parent = 0;
char dst_last[DISKFS_NAME_MAX];
dst_last[0] = 0;
rc = diskfs_lookup_path(&sb, new_rel, &dst_ino, &dst_parent, dst_last, sizeof(dst_last));
if (rc == 0) {
// Destination exists: if it's a dir and source is file (or vice-versa), error.
if (sb.inodes[dst_ino].type != sb.inodes[src_ino].type) return -EINVAL;
if (dst_ino == src_ino) return 0; // same inode
// Remove destination.
memset(&sb.inodes[dst_ino], 0, sizeof(sb.inodes[dst_ino]));
} else if (rc == -ENOENT) {
// Parent must exist and be a dir.
if (dst_parent >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[dst_parent].type != DISKFS_INODE_DIR) return -ENOTDIR;
} else {
return rc;
}
// Move: update parent and name.
sb.inodes[src_ino].parent = dst_parent;
memset(sb.inodes[src_ino].name, 0, sizeof(sb.inodes[src_ino].name));
diskfs_strlcpy(sb.inodes[src_ino].name, dst_last, sizeof(sb.inodes[src_ino].name));
return diskfs_super_store(&sb);
}
int diskfs_getdents(uint16_t dir_ino, uint32_t* inout_index, void* out, uint32_t out_len) {
if (!inout_index || !out) return -EINVAL;
if (!g_ready) return -ENODEV;
if (out_len < sizeof(struct diskfs_kdirent)) return -EINVAL;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
if (dir_ino >= DISKFS_MAX_INODES) return -ENOENT;
if (sb.inodes[dir_ino].type != DISKFS_INODE_DIR) return -ENOTDIR;
uint32_t idx = *inout_index;
uint32_t written = 0;
struct diskfs_kdirent* ents = (struct diskfs_kdirent*)out;
uint32_t cap = out_len / (uint32_t)sizeof(struct diskfs_kdirent);
// index 0 => '.' ; index 1 => '..' ; index >=2 => scan inodes
while (written < cap) {
struct diskfs_kdirent e;
memset(&e, 0, sizeof(e));
if (idx == 0) {
e.d_ino = (uint32_t)dir_ino;
e.d_type = (uint8_t)DISKFS_INODE_DIR;
diskfs_strlcpy(e.d_name, ".", sizeof(e.d_name));
} else if (idx == 1) {
e.d_ino = (uint32_t)sb.inodes[dir_ino].parent;
e.d_type = (uint8_t)DISKFS_INODE_DIR;
diskfs_strlcpy(e.d_name, "..", sizeof(e.d_name));
} else {
uint16_t scan = (uint16_t)(idx - 2);
int found = 0;
for (; scan < DISKFS_MAX_INODES; scan++) {
if (sb.inodes[scan].type == DISKFS_INODE_FREE) continue;
if (sb.inodes[scan].parent != dir_ino) continue;
if (sb.inodes[scan].name[0] == 0) continue;
e.d_ino = (uint32_t)scan;
e.d_type = sb.inodes[scan].type;
diskfs_strlcpy(e.d_name, sb.inodes[scan].name, sizeof(e.d_name));
found = 1;
scan++;
idx = (uint32_t)scan + 2U;
break;
}
if (!found) break;
}
e.d_reclen = (uint16_t)sizeof(e);
ents[written] = e;
written++;
if (idx == 0) idx = 1;
else if (idx == 1) idx = 2;
}
*inout_index = idx;
return (int)(written * (uint32_t)sizeof(struct diskfs_kdirent));
}
/* ---- VFS callback wrappers ---- */
static int diskfs_vfs_create(struct fs_node* dir, const char* name, uint32_t flags, struct fs_node** out) {
if (!dir || !name || !out) return -EINVAL;
*out = 0;
if (!g_ready) return -ENODEV;
struct diskfs_node* parent = (struct diskfs_node*)dir;
uint16_t parent_ino = parent->ino;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
if (parent_ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[parent_ino].type != DISKFS_INODE_DIR) return -ENOTDIR;
/* Check if it already exists */
int existing = diskfs_find_child(&sb, parent_ino, name);
if (existing >= 0) {
uint16_t ino = (uint16_t)existing;
if (sb.inodes[ino].type != DISKFS_INODE_FILE) return -EISDIR;
if ((flags & 0x200U) != 0U) { /* O_TRUNC */
sb.inodes[ino].size_bytes = 0;
if (diskfs_super_store(&sb) < 0) return -EIO;
}
struct diskfs_node* dn = (struct diskfs_node*)kmalloc(sizeof(*dn));
if (!dn) return -ENOMEM;
memset(dn, 0, sizeof(*dn));
diskfs_strlcpy(dn->vfs.name, name, sizeof(dn->vfs.name));
dn->vfs.flags = FS_FILE;
dn->vfs.inode = 100 + (uint32_t)ino;
dn->vfs.length = sb.inodes[ino].size_bytes;
dn->vfs.f_ops = &diskfs_file_fops;
dn->vfs.i_ops = &diskfs_file_iops;
dn->ino = ino;
*out = &dn->vfs;
return 0;
}
/* Create new file */
if ((flags & 0x40U) == 0U) return -ENOENT; /* O_CREAT not set */
uint16_t new_ino = 0;
int rc = diskfs_alloc_inode_file(&sb, parent_ino, name, DISKFS_DEFAULT_CAP_SECTORS, &new_ino);
if (rc < 0) return rc;
if (diskfs_super_store(&sb) < 0) return -EIO;
struct diskfs_node* dn = (struct diskfs_node*)kmalloc(sizeof(*dn));
if (!dn) return -ENOMEM;
memset(dn, 0, sizeof(*dn));
diskfs_strlcpy(dn->vfs.name, name, sizeof(dn->vfs.name));
dn->vfs.flags = FS_FILE;
dn->vfs.inode = 100 + (uint32_t)new_ino;
dn->vfs.length = 0;
dn->vfs.f_ops = &diskfs_file_fops;
dn->vfs.i_ops = &diskfs_file_iops;
dn->ino = new_ino;
*out = &dn->vfs;
return 0;
}
static int diskfs_vfs_mkdir(struct fs_node* dir, const char* name) {
if (!dir || !name || name[0] == 0) return -EINVAL;
if (!g_ready) return -ENODEV;
struct diskfs_node* parent = (struct diskfs_node*)dir;
uint16_t parent_ino = parent->ino;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
if (parent_ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[parent_ino].type != DISKFS_INODE_DIR) return -ENOTDIR;
if (diskfs_find_child(&sb, parent_ino, name) >= 0) return -EEXIST;
for (uint16_t i = 1; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type != DISKFS_INODE_FREE) continue;
sb.inodes[i].type = DISKFS_INODE_DIR;
sb.inodes[i].parent = parent_ino;
memset(sb.inodes[i].name, 0, sizeof(sb.inodes[i].name));
diskfs_strlcpy(sb.inodes[i].name, name, sizeof(sb.inodes[i].name));
sb.inodes[i].start_lba = 0;
sb.inodes[i].size_bytes = 0;
sb.inodes[i].cap_sectors = 0;
return diskfs_super_store(&sb);
}
return -ENOSPC;
}
static int diskfs_vfs_unlink(struct fs_node* dir, const char* name) {
if (!dir || !name || name[0] == 0) return -EINVAL;
if (!g_ready) return -ENODEV;
struct diskfs_node* parent = (struct diskfs_node*)dir;
uint16_t parent_ino = parent->ino;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
int child = diskfs_find_child(&sb, parent_ino, name);
if (child < 0) return -ENOENT;
uint16_t ino = (uint16_t)child;
if (ino == 0) return -EPERM;
if (sb.inodes[ino].type == DISKFS_INODE_DIR) return -EISDIR;
if (sb.inodes[ino].type != DISKFS_INODE_FILE) return -ENOENT;
memset(&sb.inodes[ino], 0, sizeof(sb.inodes[ino]));
return diskfs_super_store(&sb);
}
static int diskfs_vfs_rmdir(struct fs_node* dir, const char* name) {
if (!dir || !name || name[0] == 0) return -EINVAL;
if (!g_ready) return -ENODEV;
struct diskfs_node* parent = (struct diskfs_node*)dir;
uint16_t parent_ino = parent->ino;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
int child = diskfs_find_child(&sb, parent_ino, name);
if (child < 0) return -ENOENT;
uint16_t ino = (uint16_t)child;
if (ino == 0) return -EPERM;
if (sb.inodes[ino].type != DISKFS_INODE_DIR) return -ENOTDIR;
/* Check directory is empty */
for (uint16_t i = 0; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type == DISKFS_INODE_FREE) continue;
if (sb.inodes[i].parent == ino && i != ino) return -ENOTEMPTY;
}
memset(&sb.inodes[ino], 0, sizeof(sb.inodes[ino]));
return diskfs_super_store(&sb);
}
static int diskfs_vfs_rename(struct fs_node* old_dir, const char* old_name,
struct fs_node* new_dir, const char* new_name) {
if (!old_dir || !old_name || !new_dir || !new_name) return -EINVAL;
if (!g_ready) return -ENODEV;
struct diskfs_node* odir = (struct diskfs_node*)old_dir;
struct diskfs_node* ndir = (struct diskfs_node*)new_dir;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
int src = diskfs_find_child(&sb, odir->ino, old_name);
if (src < 0) return -ENOENT;
uint16_t src_ino = (uint16_t)src;
if (src_ino == 0) return -EPERM;
/* Check if destination exists */
int dst = diskfs_find_child(&sb, ndir->ino, new_name);
if (dst >= 0) {
uint16_t dst_ino = (uint16_t)dst;
if (sb.inodes[dst_ino].type != sb.inodes[src_ino].type) return -EINVAL;
if (dst_ino == src_ino) return 0;
memset(&sb.inodes[dst_ino], 0, sizeof(sb.inodes[dst_ino]));
}
sb.inodes[src_ino].parent = ndir->ino;
memset(sb.inodes[src_ino].name, 0, sizeof(sb.inodes[src_ino].name));
diskfs_strlcpy(sb.inodes[src_ino].name, new_name, sizeof(sb.inodes[src_ino].name));
return diskfs_super_store(&sb);
}
static int diskfs_vfs_truncate(struct fs_node* node, uint32_t length) {
if (!node) return -EINVAL;
if (!g_ready) return -ENODEV;
struct diskfs_node* dn = (struct diskfs_node*)node;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
if (dn->ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[dn->ino].type != DISKFS_INODE_FILE) return -EISDIR;
if (length < sb.inodes[dn->ino].size_bytes) {
sb.inodes[dn->ino].size_bytes = length;
}
node->length = sb.inodes[dn->ino].size_bytes;
return diskfs_super_store(&sb);
}
static int diskfs_vfs_link(struct fs_node* dir, const char* name, struct fs_node* target) {
if (!dir || !name || name[0] == 0 || !target) return -EINVAL;
if (!g_ready) return -ENODEV;
struct diskfs_node* parent = (struct diskfs_node*)dir;
struct diskfs_node* src = (struct diskfs_node*)target;
struct diskfs_super sb;
if (diskfs_super_load(&sb) < 0) return -EIO;
uint16_t src_ino = src->ino;
if (src_ino >= DISKFS_MAX_INODES) return -EIO;
if (sb.inodes[src_ino].type != DISKFS_INODE_FILE) return -EPERM;
/* Check new name doesn't already exist */
if (diskfs_find_child(&sb, parent->ino, name) >= 0) return -EEXIST;
/* Find a free inode slot */
uint16_t new_ino = 0;
for (uint16_t i = 1; i < DISKFS_MAX_INODES; i++) {
if (sb.inodes[i].type == DISKFS_INODE_FREE) { new_ino = i; break; }
}
if (new_ino == 0) return -ENOSPC;
/* Create new inode sharing same data blocks */
sb.inodes[new_ino].type = DISKFS_INODE_FILE;
sb.inodes[new_ino].nlink = 2;
sb.inodes[new_ino].parent = parent->ino;
memset(sb.inodes[new_ino].name, 0, sizeof(sb.inodes[new_ino].name));
diskfs_strlcpy(sb.inodes[new_ino].name, name, sizeof(sb.inodes[new_ino].name));
sb.inodes[new_ino].start_lba = sb.inodes[src_ino].start_lba;
sb.inodes[new_ino].size_bytes = sb.inodes[src_ino].size_bytes;
sb.inodes[new_ino].cap_sectors = sb.inodes[src_ino].cap_sectors;
/* Update source inode nlink */
if (sb.inodes[src_ino].nlink < 2) sb.inodes[src_ino].nlink = 2;
else sb.inodes[src_ino].nlink++;
return diskfs_super_store(&sb);
}
fs_node_t* diskfs_create_root(int drive) {
if (!g_ready) {
g_diskfs_drive = drive;
if (ata_pio_drive_present(drive)) {
g_ready = 1;
} else {
g_ready = 0;
}
memset(&g_root, 0, sizeof(g_root));
strcpy(g_root.vfs.name, "disk");
g_root.vfs.flags = FS_DIRECTORY;
g_root.vfs.inode = 100;
g_root.vfs.length = 0;
g_root.vfs.f_ops = &diskfs_dir_fops;
g_root.vfs.i_ops = &diskfs_dir_iops;
g_root.ino = 0;
if (g_ready) {
struct diskfs_super sb;
(void)diskfs_super_load(&sb);
}
}
return g_ready ? &g_root.vfs : 0;
}