Add FAT32 driver, mount app, fix shell cd/path resolution

- FAT32 VFS driver (fat32.h, fat32.c): reads BPB, FAT table, cluster
  chains, directory entries (8.3 SFN), file read/write, mount via
  fat32_mount_device()
- mount app: writes 'device mountpoint' to /sys/vfs/mount to trigger
  FAT32 mount from userspace
- VFS sysfs mount namespace in kernel.c: handles mount requests via
  sysfs write to /sys/vfs/mount, delegates to FAT32 driver
- Shell cd: validates target directory exists before updating CWD,
  resolves relative paths (., .., components) to absolute paths
- Shell run_command: resolves ARG1 paths relative to CWD so apps
  like cat and ls receive absolute paths automatically
- Fixed FAT32 root directory access: use fs->root_cluster when
  VFS mount root node has inode=0
This commit is contained in:
AI
2026-02-23 14:52:41 +00:00
parent d1bf69ce0d
commit 5229758092
6 changed files with 1198 additions and 8 deletions

95
apps/mount/mount.c Normal file
View File

@@ -0,0 +1,95 @@
/**
* @file mount.c
* @brief Mount a block device as FAT32.
*
* Writes to /sys/vfs/mount to trigger a kernel-level FAT32 mount.
*
* Usage: mount <device> <path>
* e.g.: mount hdd1mbr1 /mnt
*/
#include "syscalls.h"
int main(void) {
char device[64];
char path[128];
if (getenv("ARG1", device, sizeof(device)) < 0 || device[0] == '\0') {
puts("Usage: mount <device> <mountpoint>\n");
puts(" e.g.: mount hdd1mbr1 /mnt\n");
return 1;
}
/* ARG1 contains "device path" — we need to split it.
* Actually, the shell currently only passes the first word as ARG1.
* Let's accept: mount <device> <path> where ARG1 = "device path" */
/* Find the space separating device and path */
int i = 0;
while (device[i] && device[i] != ' ') i++;
if (device[i] == ' ') {
/* ARG1 contains both: "hdd1mbr1 /mnt" */
device[i] = '\0';
/* Copy path part */
int j = i + 1;
while (device[j] == ' ') j++;
int pi = 0;
while (device[j] && pi < 127) {
path[pi++] = device[j++];
}
path[pi] = '\0';
} else {
/* Only device, no path — default to /mnt */
path[0] = '/'; path[1] = 'm'; path[2] = 'n'; path[3] = 't';
path[4] = '\0';
}
if (path[0] == '\0') {
puts("mount: missing mountpoint, using /mnt\n");
path[0] = '/'; path[1] = 'm'; path[2] = 'n'; path[3] = 't';
path[4] = '\0';
}
/* Build the mount command: "device path" */
char cmd[192];
int pos = 0;
for (int k = 0; device[k] && pos < 190; k++) cmd[pos++] = device[k];
cmd[pos++] = ' ';
for (int k = 0; path[k] && pos < 190; k++) cmd[pos++] = path[k];
cmd[pos] = '\0';
/* Open /sys/vfs/mount and write the command */
int32_t fd = open("/sys/vfs/mount", 0);
if (fd < 0) {
puts("mount: cannot open /sys/vfs/mount\n");
return 1;
}
int32_t n = write(fd, cmd, (uint32_t)pos);
close(fd);
if (n < 0) {
/* Read the status to show the error */
fd = open("/sys/vfs/mounts", 0);
if (fd >= 0) {
char status[256];
int32_t sn = read(fd, status, sizeof(status) - 1);
close(fd);
if (sn > 0) {
status[sn] = '\0';
puts(status);
}
} else {
puts("mount: failed\n");
}
return 1;
}
puts("mount: ");
puts(device);
puts(" mounted at ");
puts(path);
puts("\n");
return 0;
}

View File

@@ -56,21 +56,177 @@ static char *find_space(char *s) {
return s;
}
/** String length. */
static int slen(const char *s) {
int n = 0;
while (s[n]) n++;
return n;
}
/** Resolve an absolute path from cwd + a possibly relative path.
* Handles '..', '.', and trailing slashes. Result is always absolute. */
static void resolve_path(const char *cwd, const char *arg, char *out, int out_sz) {
char tmp[256];
int ti = 0;
/* If arg is absolute, start from it; otherwise prepend cwd */
if (arg[0] == '/') {
/* copy arg into tmp */
for (int i = 0; arg[i] && ti < 255; i++)
tmp[ti++] = arg[i];
} else {
/* copy cwd */
for (int i = 0; cwd[i] && ti < 255; i++)
tmp[ti++] = cwd[i];
/* ensure separator */
if (ti > 0 && tmp[ti - 1] != '/' && ti < 255)
tmp[ti++] = '/';
/* append arg */
for (int i = 0; arg[i] && ti < 255; i++)
tmp[ti++] = arg[i];
}
tmp[ti] = '\0';
/* Now canonicalize: split on '/' and process each component */
/* Stack of component start offsets */
int comp_starts[64];
int comp_lens[64];
int depth = 0;
int i = 0;
while (tmp[i]) {
/* skip slashes */
while (tmp[i] == '/') i++;
if (!tmp[i]) break;
/* find end of component */
int start = i;
while (tmp[i] && tmp[i] != '/') i++;
int len = i - start;
if (len == 1 && tmp[start] == '.') {
/* current dir: skip */
continue;
} else if (len == 2 && tmp[start] == '.' && tmp[start + 1] == '.') {
/* parent dir: pop */
if (depth > 0) depth--;
} else {
/* normal component: push */
if (depth < 64) {
comp_starts[depth] = start;
comp_lens[depth] = len;
depth++;
}
}
}
/* Build output */
int oi = 0;
if (depth == 0) {
if (oi < out_sz - 1) out[oi++] = '/';
} else {
for (int d = 0; d < depth && oi < out_sz - 2; d++) {
out[oi++] = '/';
for (int j = 0; j < comp_lens[d] && oi < out_sz - 1; j++)
out[oi++] = tmp[comp_starts[d] + j];
}
}
out[oi] = '\0';
}
/** Built-in: cd <path> */
static void builtin_cd(char *arg) {
if (!arg || !*arg) {
arg = "/";
}
/* Update CWD environment variable */
setenv("CWD", arg);
/* Verify it was set */
/* Get current working directory */
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) >= 0) {
puts("cd: ");
puts(cwd);
putchar('\n');
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
/* Resolve the path (handles relative, .., etc.) */
char resolved[256];
resolve_path(cwd, arg, resolved, sizeof(resolved));
/* Validate: check if the directory exists by trying to readdir it.
* Root "/" always exists. For others, readdir index 0 must not fail
* with -1 unless the dir is simply empty, so we also accept stat-like
* checks. A simple heuristic: if readdir returns -1 for index 0 AND
* the path is not "/", we check if the *parent* can list it. */
if (resolved[0] == '/' && resolved[1] != '\0') {
/* Try listing the first entry; if the path is a valid directory,
* readdir(path, 0, ...) returns >= 0 even for an empty dir
* (it returns -1 meaning "end"), BUT if the path doesn't exist
* at all, the VFS resolve_path will fail and readdir returns -1.
*
* Better approach: try to readdir the parent and look for this
* entry among its children. */
char name[128];
int32_t type = readdir(resolved, 0, name);
/* type >= 0 means there's at least one entry => directory exists.
* type == -1 could mean empty dir or non-existent.
* For empty dirs: the directory itself was resolved successfully.
* For non-existent: the VFS resolution failed.
*
* Our VFS readdir calls resolve_path internally. If the path
* doesn't exist, it returns -1. If it exists but is empty,
* it also returns -1. We need to distinguish these.
* Use a stat-like approach: try to open the path. */
/* Actually the simplest check: try readdir on the *parent* and
* see if our target component exists as a directory entry. */
char parent[256];
char target[128];
/* Split resolved into parent + target */
int rlen = slen(resolved);
int last_slash = 0;
for (int i = 0; i < rlen; i++) {
if (resolved[i] == '/') last_slash = i;
}
if (last_slash == 0) {
/* Parent is root */
parent[0] = '/';
parent[1] = '\0';
strcpy(target, resolved + 1);
} else {
memcpy(parent, resolved, (uint32_t)last_slash);
parent[last_slash] = '\0';
strcpy(target, resolved + last_slash + 1);
}
/* Search parent directory for target */
int found = 0;
uint32_t idx = 0;
int32_t etype;
while ((etype = readdir(parent, idx, name)) >= 0) {
if (strcmp(name, target) == 0) {
if (etype == 2) {
found = 1; /* It's a directory */
} else {
puts("cd: ");
puts(resolved);
puts(": not a directory\n");
return;
}
break;
}
idx++;
}
if (!found) {
puts("cd: ");
puts(resolved);
puts(": no such directory\n");
return;
}
}
/* Update CWD environment variable */
setenv("CWD", resolved);
}
/** Built-in: env - print all known env vars. */
@@ -104,6 +260,16 @@ static void builtin_help(void) {
puts("External commands are loaded from initrd.\n");
}
/** Check if a string looks like a path (contains / or starts with .) */
static int looks_like_path(const char *s) {
if (!s || !*s) return 0;
if (s[0] == '.' || s[0] == '/') return 1;
for (int i = 0; s[i]; i++) {
if (s[i] == '/') return 1;
}
return 0;
}
/** Execute an external command via fork+exec. */
static void run_command(const char *cmd, const char *arg) {
int32_t pid = fork();
@@ -115,7 +281,19 @@ static void run_command(const char *cmd, const char *arg) {
if (pid == 0) {
/* Child: set ARG1 if there's an argument */
if (arg && *arg) {
setenv("ARG1", arg);
/* If the argument looks like a path and isn't absolute,
* resolve it relative to CWD so apps get absolute paths */
if (looks_like_path(arg) && arg[0] != '/') {
char cwd[128];
if (getenv("CWD", cwd, sizeof(cwd)) < 0) {
strcpy(cwd, "/");
}
char resolved_arg[256];
resolve_path(cwd, arg, resolved_arg, sizeof(resolved_arg));
setenv("ARG1", resolved_arg);
} else {
setenv("ARG1", arg);
}
} else {
setenv("ARG1", "");
}

View File

@@ -23,6 +23,7 @@ add_executable(kernel
sysfs.c
ide.c
mbr.c
fat32.c
env.c
keyboard.c
interrupts.S

673
src/fat32.c Normal file
View File

@@ -0,0 +1,673 @@
/**
* @file fat32.c
* @brief FAT32 filesystem driver implementation.
*
* Reads and writes FAT32 filesystems on block devices. Supports:
* - Reading the BPB and FAT
* - Following cluster chains
* - Reading directory entries (short 8.3 names)
* - Reading and writing file data
* - Creating new files
*
* Mounted via fat32_mount_device() which registers VFS operations.
*/
#include "fat32.h"
#include "vfs.h"
#include "kmalloc.h"
#include <string.h>
/* Debug print helpers defined in kernel.c */
extern void offset_print(const char *str);
extern void print_hex(uint32_t val);
/** Maximum number of simultaneous FAT32 mounts. */
#define FAT32_MAX_MOUNTS 4
/** Sector buffer size (one sector). */
#define SECTOR_SIZE 512
/** Static mount contexts. */
static fat32_fs_t fat32_mounts[FAT32_MAX_MOUNTS];
static int fat32_mount_count = 0;
/* ================================================================
* Low-level block I/O helpers
* ================================================================ */
/**
* Read sectors from the underlying block device.
*
* @param fs FAT32 filesystem instance.
* @param lba Logical block address (absolute, 0 = device start).
* @param count Number of sectors to read.
* @param buf Output buffer (must be count * 512 bytes).
* @return 0 on success, -1 on error.
*/
static int fat32_read_sectors(fat32_fs_t *fs, uint32_t lba,
uint32_t count, void *buf) {
if (!fs->dev || !fs->dev->block_ops || !fs->dev->block_ops->read_sectors) {
return -1;
}
return fs->dev->block_ops->read_sectors(fs->dev->dev_data, lba, count, buf);
}
/**
* Write sectors to the underlying block device.
*/
static int fat32_write_sectors(fat32_fs_t *fs, uint32_t lba,
uint32_t count, const void *buf) {
if (!fs->dev || !fs->dev->block_ops || !fs->dev->block_ops->write_sectors) {
return -1;
}
return fs->dev->block_ops->write_sectors(fs->dev->dev_data, lba, count, buf);
}
/* ================================================================
* FAT table operations
* ================================================================ */
/**
* Read a single FAT entry (the next cluster in the chain).
*
* @param fs Filesystem instance.
* @param cluster Current cluster number.
* @return Next cluster number, or FAT32_EOC if end of chain.
*/
static uint32_t fat32_read_fat(fat32_fs_t *fs, uint32_t cluster) {
/* Each FAT entry is 4 bytes. Calculate which sector and offset. */
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + (fat_offset / SECTOR_SIZE);
uint32_t entry_offset = fat_offset % SECTOR_SIZE;
uint8_t sector_buf[SECTOR_SIZE];
if (fat32_read_sectors(fs, fat_sector, 1, sector_buf) != 0) {
return FAT32_EOC;
}
uint32_t entry = *(uint32_t *)(sector_buf + entry_offset);
return entry & 0x0FFFFFFF; /* Mask off top 4 bits */
}
/**
* Write a FAT entry.
*
* @param fs Filesystem instance.
* @param cluster Cluster to update.
* @param value New value for the FAT entry.
* @return 0 on success, -1 on error.
*/
static int fat32_write_fat(fat32_fs_t *fs, uint32_t cluster,
uint32_t value) {
uint32_t fat_offset = cluster * 4;
uint32_t fat_sector = fs->fat_start_lba + (fat_offset / SECTOR_SIZE);
uint32_t entry_offset = fat_offset % SECTOR_SIZE;
uint8_t sector_buf[SECTOR_SIZE];
if (fat32_read_sectors(fs, fat_sector, 1, sector_buf) != 0) {
return -1;
}
/* Preserve top 4 bits of the existing entry */
uint32_t *entry = (uint32_t *)(sector_buf + entry_offset);
*entry = (*entry & 0xF0000000) | (value & 0x0FFFFFFF);
/* Write back to all FATs */
for (uint8_t f = 0; f < fs->bpb.num_fats; f++) {
uint32_t fat_sec = fs->fat_start_lba +
(f * fs->bpb.fat_size_32) + (fat_offset / SECTOR_SIZE);
if (fat32_write_sectors(fs, fat_sec, 1, sector_buf) != 0) {
return -1;
}
}
return 0;
}
/**
* Find a free cluster in the FAT and mark it as end-of-chain.
*
* @param fs Filesystem instance.
* @return Cluster number, or 0 if disk is full.
*/
static uint32_t fat32_alloc_cluster(fat32_fs_t *fs) {
for (uint32_t c = 2; c < fs->total_clusters + 2; c++) {
uint32_t val = fat32_read_fat(fs, c);
if (val == FAT32_FREE) {
fat32_write_fat(fs, c, FAT32_EOC);
/* Zero out the cluster */
uint8_t zero[SECTOR_SIZE];
memset(zero, 0, SECTOR_SIZE);
uint32_t first_sec = fs->data_start_lba +
(c - 2) * fs->sectors_per_cluster;
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
fat32_write_sectors(fs, first_sec + s, 1, zero);
}
return c;
}
}
return 0; /* Full */
}
/* ================================================================
* Cluster / sector helpers
* ================================================================ */
/**
* Convert a cluster number to its first sector (absolute LBA).
*/
static uint32_t cluster_to_lba(fat32_fs_t *fs, uint32_t cluster) {
return fs->data_start_lba + (cluster - 2) * fs->sectors_per_cluster;
}
/**
* Read all data from a cluster chain into a buffer.
*
* @param fs Filesystem instance.
* @param start Starting cluster.
* @param buf Output buffer.
* @param offset Byte offset to start reading from.
* @param size Maximum bytes to read.
* @return Bytes read, or -1 on error.
*/
static int32_t fat32_read_chain(fat32_fs_t *fs, uint32_t start,
uint32_t offset, uint32_t size,
void *buf) {
if (start < 2) return -1;
uint32_t bpc = fs->bytes_per_cluster;
uint32_t bytes_read = 0;
uint32_t cluster = start;
uint32_t pos = 0; /* Byte position in the chain */
/* Skip clusters until we reach the offset */
while (pos + bpc <= offset && cluster >= 2 && cluster < FAT32_EOC) {
pos += bpc;
cluster = fat32_read_fat(fs, cluster);
}
/* Read data */
uint8_t cluster_buf[SECTOR_SIZE]; /* Read sector by sector */
while (cluster >= 2 && cluster < FAT32_EOC && bytes_read < size) {
uint32_t lba = cluster_to_lba(fs, cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster && bytes_read < size; s++) {
uint32_t sec_start = pos;
uint32_t sec_end = pos + SECTOR_SIZE;
if (sec_end <= offset) {
/* This sector is entirely before our offset, skip */
pos += SECTOR_SIZE;
continue;
}
if (fat32_read_sectors(fs, lba + s, 1, cluster_buf) != 0) {
return (bytes_read > 0) ? (int32_t)bytes_read : -1;
}
/* Calculate how much of this sector we need */
uint32_t sect_off = 0;
if (offset > sec_start) {
sect_off = offset - sec_start;
}
uint32_t sect_avail = SECTOR_SIZE - sect_off;
uint32_t to_copy = size - bytes_read;
if (to_copy > sect_avail) to_copy = sect_avail;
memcpy((uint8_t *)buf + bytes_read, cluster_buf + sect_off, to_copy);
bytes_read += to_copy;
pos += SECTOR_SIZE;
}
cluster = fat32_read_fat(fs, cluster);
}
return (int32_t)bytes_read;
}
/**
* Write data to a cluster chain, extending it as needed.
*
* @param fs Filesystem instance.
* @param start Starting cluster (must be valid, >= 2).
* @param offset Byte offset to start writing at.
* @param size Bytes to write.
* @param buf Input data.
* @return Bytes written, or -1 on error.
*/
static int32_t fat32_write_chain(fat32_fs_t *fs, uint32_t start,
uint32_t offset, uint32_t size,
const void *buf) {
if (start < 2) return -1;
uint32_t bpc = fs->bytes_per_cluster;
uint32_t bytes_written = 0;
uint32_t cluster = start;
uint32_t prev_cluster = 0;
uint32_t pos = 0;
/* Navigate to the cluster containing the offset */
while (pos + bpc <= offset) {
if (cluster < 2 || cluster >= FAT32_EOC) {
/* Need to extend the chain */
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) return -1;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
prev_cluster = cluster;
pos += bpc;
cluster = fat32_read_fat(fs, cluster);
}
/* Ensure current cluster is valid */
if (cluster < 2 || cluster >= FAT32_EOC) {
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) return -1;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
/* Write data sector by sector */
uint8_t sector_buf[SECTOR_SIZE];
while (bytes_written < size) {
if (cluster < 2 || cluster >= FAT32_EOC) {
/* Extend chain */
uint32_t new_cluster = fat32_alloc_cluster(fs);
if (new_cluster == 0) break;
if (prev_cluster >= 2) {
fat32_write_fat(fs, prev_cluster, new_cluster);
}
cluster = new_cluster;
}
uint32_t lba = cluster_to_lba(fs, cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster && bytes_written < size; s++) {
uint32_t sec_start = pos;
if (sec_start + SECTOR_SIZE <= offset) {
pos += SECTOR_SIZE;
continue;
}
/* Read-modify-write for partial sectors */
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return (bytes_written > 0) ? (int32_t)bytes_written : -1;
}
uint32_t sect_off = 0;
if (offset > sec_start) {
sect_off = offset - sec_start;
}
uint32_t sect_avail = SECTOR_SIZE - sect_off;
uint32_t to_copy = size - bytes_written;
if (to_copy > sect_avail) to_copy = sect_avail;
memcpy(sector_buf + sect_off, (const uint8_t *)buf + bytes_written, to_copy);
if (fat32_write_sectors(fs, lba + s, 1, sector_buf) != 0) {
return (bytes_written > 0) ? (int32_t)bytes_written : -1;
}
bytes_written += to_copy;
offset += to_copy; /* Advance offset for subsequent sectors */
pos += SECTOR_SIZE;
}
prev_cluster = cluster;
pos = pos; /* pos already advanced in the loop */
cluster = fat32_read_fat(fs, cluster);
}
return (int32_t)bytes_written;
}
/* ================================================================
* Directory operations
* ================================================================ */
/**
* VFS node fs_data encodes the FAT32 context:
* - inode field: first cluster of the file/directory
* - fs_data: pointer to fat32_fs_t
*/
/**
* Convert an 8.3 short filename to a human-readable string.
* "HELLO TXT" -> "HELLO.TXT"
*/
static void sfn_to_name(const uint8_t sfn[11], char *out) {
int pos = 0;
/* Copy base name (bytes 0-7), trimming trailing spaces */
int base_end = 7;
while (base_end >= 0 && sfn[base_end] == ' ') base_end--;
for (int i = 0; i <= base_end; i++) {
char c = (char)sfn[i];
/* Convert to lowercase for display */
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
out[pos++] = c;
}
/* Append extension (bytes 8-10) if non-empty */
int ext_end = 10;
while (ext_end >= 8 && sfn[ext_end] == ' ') ext_end--;
if (ext_end >= 8) {
out[pos++] = '.';
for (int i = 8; i <= ext_end; i++) {
char c = (char)sfn[i];
if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a';
out[pos++] = c;
}
}
out[pos] = '\0';
}
/**
* Convert a human-readable filename to 8.3 short format.
* "hello.txt" -> "HELLO TXT"
*/
static void name_to_sfn(const char *name, uint8_t sfn[11]) {
memset(sfn, ' ', 11);
int pos = 0;
int i = 0;
/* Base name (up to 8 chars) */
while (name[i] && name[i] != '.' && pos < 8) {
char c = name[i];
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
sfn[pos++] = (uint8_t)c;
i++;
}
/* Extension */
if (name[i] == '.') {
i++;
pos = 8;
while (name[i] && pos < 11) {
char c = name[i];
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
sfn[pos++] = (uint8_t)c;
i++;
}
}
}
/**
* Get the first cluster number from a directory entry.
*/
static uint32_t dirent_cluster(const fat32_dirent_t *de) {
return ((uint32_t)de->cluster_hi << 16) | de->cluster_lo;
}
/* ================================================================
* VFS operations
* ================================================================ */
/**
* Read file data for a FAT32 file.
*/
static int32_t fat32_vfs_read(vfs_node_t *node, uint32_t offset,
uint32_t size, void *buf) {
if (!node || !node->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)node->fs_data;
uint32_t cluster = node->inode;
if (cluster < 2) return -1;
/* Don't read past file end */
if (offset >= node->size) return 0;
if (offset + size > node->size) {
size = node->size - offset;
}
return fat32_read_chain(fs, cluster, offset, size, buf);
}
/**
* Write file data for a FAT32 file.
*/
static int32_t fat32_vfs_write(vfs_node_t *node, uint32_t offset,
uint32_t size, const void *buf) {
if (!node || !node->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)node->fs_data;
uint32_t cluster = node->inode;
if (cluster < 2) return -1;
int32_t written = fat32_write_chain(fs, cluster, offset, size, buf);
/* Update file size if we wrote past the end */
if (written > 0 && offset + (uint32_t)written > node->size) {
node->size = offset + (uint32_t)written;
/* TODO: update the directory entry on disk with new size */
}
return written;
}
/**
* Read directory entries from a FAT32 directory.
*/
static int fat32_vfs_readdir(vfs_node_t *dir, uint32_t idx,
vfs_dirent_t *out) {
if (!dir || !dir->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)dir->fs_data;
/* inode == 0 means mount root: use the filesystem's root cluster */
uint32_t cluster = dir->inode;
if (cluster < 2) cluster = fs->root_cluster;
if (cluster < 2) return -1;
/* Read the directory cluster chain and iterate entries.
* We read 32-byte entries, skipping deleted (0xE5), LFN, and
* volume label entries. idx counts only valid file/dir entries. */
uint32_t count = 0;
uint32_t byte_pos = 0;
uint8_t sector_buf[SECTOR_SIZE];
uint32_t cur_cluster = cluster;
while (cur_cluster >= 2 && cur_cluster < FAT32_EOC) {
uint32_t lba = cluster_to_lba(fs, cur_cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return -1;
}
/* Each sector has 512/32 = 16 directory entries */
for (uint32_t e = 0; e < SECTOR_SIZE / 32; e++) {
fat32_dirent_t *de = (fat32_dirent_t *)(sector_buf + e * 32);
/* 0x00 = end of directory */
if (de->name[0] == 0x00) return -1;
/* 0xE5 = deleted entry */
if (de->name[0] == 0xE5) continue;
/* Skip LFN entries */
if ((de->attr & FAT32_ATTR_LFN) == FAT32_ATTR_LFN) continue;
/* Skip volume label */
if (de->attr & FAT32_ATTR_VOLUME_ID) continue;
/* Skip . and .. entries */
if (de->name[0] == '.' &&
(de->name[1] == ' ' || de->name[1] == '.')) {
continue;
}
if (count == idx) {
memset(out, 0, sizeof(vfs_dirent_t));
sfn_to_name(de->name, out->name);
out->type = (de->attr & FAT32_ATTR_DIRECTORY)
? VFS_DIRECTORY : VFS_FILE;
out->inode = dirent_cluster(de);
return 0;
}
count++;
}
}
cur_cluster = fat32_read_fat(fs, cur_cluster);
}
return -1; /* No more entries */
}
/**
* Look up a child by name in a FAT32 directory.
*/
static int fat32_vfs_finddir(vfs_node_t *dir, const char *name,
vfs_node_t *out) {
if (!dir || !dir->fs_data) return -1;
fat32_fs_t *fs = (fat32_fs_t *)dir->fs_data;
/* inode == 0 means mount root: use the filesystem's root cluster */
uint32_t cluster = dir->inode;
if (cluster < 2) cluster = fs->root_cluster;
if (cluster < 2) return -1;
/* Convert search name to 8.3 for comparison */
uint8_t search_sfn[11];
name_to_sfn(name, search_sfn);
uint8_t sector_buf[SECTOR_SIZE];
uint32_t cur_cluster = cluster;
while (cur_cluster >= 2 && cur_cluster < FAT32_EOC) {
uint32_t lba = cluster_to_lba(fs, cur_cluster);
for (uint32_t s = 0; s < fs->sectors_per_cluster; s++) {
if (fat32_read_sectors(fs, lba + s, 1, sector_buf) != 0) {
return -1;
}
for (uint32_t e = 0; e < SECTOR_SIZE / 32; e++) {
fat32_dirent_t *de = (fat32_dirent_t *)(sector_buf + e * 32);
if (de->name[0] == 0x00) return -1;
if (de->name[0] == 0xE5) continue;
if ((de->attr & FAT32_ATTR_LFN) == FAT32_ATTR_LFN) continue;
if (de->attr & FAT32_ATTR_VOLUME_ID) continue;
/* Compare both 8.3 format and human-readable format */
if (memcmp(de->name, search_sfn, 11) == 0) {
/* Match by 8.3 name */
goto found;
}
/* Also compare with the lowercase display name */
char display[13];
sfn_to_name(de->name, display);
if (strcmp(display, name) == 0) {
goto found;
}
continue;
found:
memset(out, 0, sizeof(vfs_node_t));
sfn_to_name(de->name, out->name);
out->inode = dirent_cluster(de);
out->size = de->file_size;
out->type = (de->attr & FAT32_ATTR_DIRECTORY)
? VFS_DIRECTORY : VFS_FILE;
out->fs_data = fs;
return 0;
}
}
cur_cluster = fat32_read_fat(fs, cur_cluster);
}
return -1;
}
/** VFS operations for FAT32. */
static vfs_fs_ops_t fat32_vfs_ops = {
.open = NULL,
.close = NULL,
.read = fat32_vfs_read,
.write = fat32_vfs_write,
.readdir = fat32_vfs_readdir,
.finddir = fat32_vfs_finddir,
};
/* ================================================================
* Mount
* ================================================================ */
int fat32_mount_device(devicefs_device_t *dev, const char *mount_path) {
if (!dev || !mount_path) return -1;
if (fat32_mount_count >= FAT32_MAX_MOUNTS) {
offset_print(" FAT32: too many mounts\n");
return -1;
}
fat32_fs_t *fs = &fat32_mounts[fat32_mount_count];
memset(fs, 0, sizeof(fat32_fs_t));
fs->dev = dev;
/* Read the boot sector / BPB */
uint8_t boot_sector[SECTOR_SIZE];
if (fat32_read_sectors(fs, 0, 1, boot_sector) != 0) {
offset_print(" FAT32: failed to read boot sector\n");
return -1;
}
/* Copy BPB */
memcpy(&fs->bpb, boot_sector, sizeof(fat32_bpb_t));
/* Validate */
if (fs->bpb.bytes_per_sector != 512) {
offset_print(" FAT32: unsupported sector size\n");
return -1;
}
if (fs->bpb.fat_size_32 == 0) {
offset_print(" FAT32: fat_size_32 is 0, not FAT32\n");
return -1;
}
/* Check boot sector signature */
if (boot_sector[510] != 0x55 || boot_sector[511] != 0xAA) {
offset_print(" FAT32: invalid boot sector signature\n");
return -1;
}
/* Compute layout */
fs->sectors_per_cluster = fs->bpb.sectors_per_cluster;
fs->bytes_per_cluster = fs->sectors_per_cluster * SECTOR_SIZE;
fs->fat_start_lba = fs->bpb.reserved_sectors;
fs->data_start_lba = fs->fat_start_lba +
(fs->bpb.num_fats * fs->bpb.fat_size_32);
fs->root_cluster = fs->bpb.root_cluster;
uint32_t total_sectors = fs->bpb.total_sectors_32;
if (total_sectors == 0) total_sectors = fs->bpb.total_sectors_16;
uint32_t data_sectors = total_sectors - fs->data_start_lba;
fs->total_clusters = data_sectors / fs->sectors_per_cluster;
offset_print(" FAT32: mounted ");
offset_print(mount_path);
offset_print(" (");
print_hex(fs->total_clusters);
offset_print(" clusters, ");
print_hex(fs->sectors_per_cluster);
offset_print(" sec/clust)\n");
/* Register with VFS */
if (vfs_mount(mount_path, &fat32_vfs_ops, fs) != 0) {
offset_print(" FAT32: VFS mount failed\n");
return -1;
}
fat32_mount_count++;
return 0;
}

116
src/fat32.h Normal file
View File

@@ -0,0 +1,116 @@
/**
* @file fat32.h
* @brief FAT32 filesystem driver.
*
* Provides a VFS driver for reading and writing FAT32 filesystems
* on block devices. Supports long filenames (LFN), directory traversal,
* file read/write, and file creation.
*
* Usage:
* fat32_mount_device(dev, mount_path);
*
* This reads the BPB from sector 0 of the device, validates the FAT32
* signature, and mounts the filesystem at the specified VFS path.
*/
#ifndef FAT32_H
#define FAT32_H
#include <stdint.h>
#include "devicefs.h"
/** FAT32 cluster chain end marker. */
#define FAT32_EOC 0x0FFFFFF8
#define FAT32_FREE 0x00000000
#define FAT32_BAD 0x0FFFFFF7
/** Maximum filename length (8.3 short name). */
#define FAT32_SFN_LEN 11
/** Directory entry attributes. */
#define FAT32_ATTR_READ_ONLY 0x01
#define FAT32_ATTR_HIDDEN 0x02
#define FAT32_ATTR_SYSTEM 0x04
#define FAT32_ATTR_VOLUME_ID 0x08
#define FAT32_ATTR_DIRECTORY 0x10
#define FAT32_ATTR_ARCHIVE 0x20
#define FAT32_ATTR_LFN (FAT32_ATTR_READ_ONLY | FAT32_ATTR_HIDDEN | \
FAT32_ATTR_SYSTEM | FAT32_ATTR_VOLUME_ID)
/**
* FAT32 BIOS Parameter Block (BPB) — packed, read directly from disk.
* Covers the first 90 bytes of the boot sector.
*/
typedef struct __attribute__((packed)) fat32_bpb {
uint8_t jmp_boot[3]; /**< Jump instruction. */
uint8_t oem_name[8]; /**< OEM identifier. */
uint16_t bytes_per_sector; /**< Usually 512. */
uint8_t sectors_per_cluster;
uint16_t reserved_sectors; /**< Sectors before the first FAT. */
uint8_t num_fats; /**< Number of FATs (usually 2). */
uint16_t root_entry_count; /**< 0 for FAT32. */
uint16_t total_sectors_16; /**< 0 for FAT32. */
uint8_t media_type;
uint16_t fat_size_16; /**< 0 for FAT32. */
uint16_t sectors_per_track;
uint16_t num_heads;
uint32_t hidden_sectors;
uint32_t total_sectors_32;
/* FAT32 extended fields */
uint32_t fat_size_32; /**< Sectors per FAT. */
uint16_t ext_flags;
uint16_t fs_version;
uint32_t root_cluster; /**< First cluster of root directory (usually 2). */
uint16_t fs_info; /**< Sector number of FSInfo structure. */
uint16_t backup_boot; /**< Sector of backup boot sector. */
uint8_t reserved[12];
uint8_t drive_number;
uint8_t reserved1;
uint8_t boot_sig; /**< 0x29 if next 3 fields are valid. */
uint32_t volume_id;
uint8_t volume_label[11];
uint8_t fs_type[8]; /**< "FAT32 " */
} fat32_bpb_t;
/**
* FAT32 directory entry (32 bytes, packed).
*/
typedef struct __attribute__((packed)) fat32_dirent {
uint8_t name[11]; /**< 8.3 short name. */
uint8_t attr; /**< File attributes. */
uint8_t nt_reserved; /**< Reserved (case info). */
uint8_t create_time_tenth; /**< Creation time in tenths of second. */
uint16_t create_time; /**< Creation time. */
uint16_t create_date; /**< Creation date. */
uint16_t access_date; /**< Last access date. */
uint16_t cluster_hi; /**< High 16 bits of first cluster. */
uint16_t write_time; /**< Last write time. */
uint16_t write_date; /**< Last write date. */
uint16_t cluster_lo; /**< Low 16 bits of first cluster. */
uint32_t file_size; /**< File size in bytes. */
} fat32_dirent_t;
/**
* FAT32 filesystem instance (per-mount context).
*/
typedef struct fat32_fs {
devicefs_device_t *dev; /**< Underlying block device. */
fat32_bpb_t bpb; /**< Cached BPB. */
uint32_t fat_start_lba; /**< First sector of the FAT. */
uint32_t data_start_lba; /**< First sector of the data region. */
uint32_t sectors_per_cluster;
uint32_t bytes_per_cluster;
uint32_t root_cluster; /**< Root directory first cluster. */
uint32_t total_clusters;
} fat32_fs_t;
/**
* Mount a FAT32 filesystem from a block device.
*
* @param dev The devicefs block device to read from.
* @param mount_path Where to mount in the VFS (e.g., "/mnt").
* @return 0 on success, -1 on error.
*/
int fat32_mount_device(devicefs_device_t *dev, const char *mount_path);
#endif /* FAT32_H */

View File

@@ -20,6 +20,7 @@
#include "devicefs.h"
#include "sysfs.h"
#include "mbr.h"
#include "fat32.h"
#include "keyboard.h"
#include "framebuffer.h"
@@ -68,6 +69,128 @@ void print_hex(uint32_t val)
outb(0xE9, '\n'); serial_putc('\n');
}
/* ================================================================
* VFS sysfs namespace — handles mount/umount via /sys/vfs
* ================================================================
*
* /sys/vfs/mount — write "device mountpoint" to mount a FAT32 device
* /sys/vfs/mounts — read to list active mounts
*/
/** Last mount status message for reading back. */
static char vfs_sysfs_status[256] = "no mounts\n";
static int vfs_sysfs_list(void *ctx, const char *path, uint32_t idx,
sysfs_entry_t *out) {
(void)ctx;
if (path[0] != '\0') return -1; /* No subdirectories */
static const char *entries[] = { "mount", "mounts" };
if (idx >= 2) return -1;
memset(out, 0, sizeof(sysfs_entry_t));
strncpy(out->name, entries[idx], SYSFS_MAX_NAME - 1);
out->is_dir = 0;
return 0;
}
static int vfs_sysfs_read(void *ctx, const char *path, char *buf,
uint32_t buf_size) {
(void)ctx;
if (strcmp(path, "mount") == 0 || strcmp(path, "mounts") == 0) {
uint32_t len = strlen(vfs_sysfs_status);
if (len + 1 > buf_size) len = buf_size - 1;
memcpy(buf, vfs_sysfs_status, len);
buf[len] = '\0';
return (int)len;
}
return -1;
}
static int vfs_sysfs_write(void *ctx, const char *path, const char *buf,
uint32_t size) {
(void)ctx;
if (strcmp(path, "mount") != 0) return -1;
/* Parse: "device_name mount_path\n" */
char device[64];
char mount_path[128];
int di = 0, mi = 0;
uint32_t i = 0;
/* Parse device name */
while (i < size && buf[i] != ' ' && buf[i] != '\n' && di < 63) {
device[di++] = buf[i++];
}
device[di] = '\0';
/* Skip spaces */
while (i < size && buf[i] == ' ') i++;
/* Parse mount path */
while (i < size && buf[i] != ' ' && buf[i] != '\n' && mi < 127) {
mount_path[mi++] = buf[i++];
}
mount_path[mi] = '\0';
if (di == 0 || mi == 0) {
strncpy(vfs_sysfs_status, "error: usage: device mountpoint\n",
sizeof(vfs_sysfs_status) - 1);
return -1;
}
offset_print(" VFS mount request: ");
offset_print(device);
offset_print(" -> ");
offset_print(mount_path);
offset_print("\n");
/* Find the device */
devicefs_device_t *dev = devicefs_find(device);
if (!dev) {
strncpy(vfs_sysfs_status, "error: device not found\n",
sizeof(vfs_sysfs_status) - 1);
offset_print(" VFS mount: device not found\n");
return -1;
}
/* Mount as FAT32 */
int ret = fat32_mount_device(dev, mount_path);
if (ret != 0) {
strncpy(vfs_sysfs_status, "error: FAT32 mount failed\n",
sizeof(vfs_sysfs_status) - 1);
offset_print(" VFS mount: FAT32 mount failed\n");
return -1;
}
/* Build success message */
strncpy(vfs_sysfs_status, "mounted ", sizeof(vfs_sysfs_status) - 1);
uint32_t slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, device,
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, " at ",
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
strncpy(vfs_sysfs_status + slen, mount_path,
sizeof(vfs_sysfs_status) - 1 - slen);
slen = strlen(vfs_sysfs_status);
if (slen < sizeof(vfs_sysfs_status) - 1) {
vfs_sysfs_status[slen] = '\n';
vfs_sysfs_status[slen + 1] = '\0';
}
return (int)size;
}
static sysfs_ops_t vfs_sysfs_ops = {
.list = vfs_sysfs_list,
.read = vfs_sysfs_read,
.write = vfs_sysfs_write,
};
void kernel_main(uint32_t magic, uint32_t addr) {
/* Initialize serial port first so all debug output goes to COM1 too */
serial_init();
@@ -258,6 +381,10 @@ void kernel_main(uint32_t magic, uint32_t addr) {
EARLY_PRINT("SYS ");
offset_print("Sysfs initialized\n");
/* Register VFS mount namespace in sysfs */
sysfs_register("vfs", &vfs_sysfs_ops, NULL);
offset_print("VFS sysfs namespace registered\n");
init_tss();
EARLY_PRINT("TSS ");
offset_print("TSS initialized\n");