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:
95
apps/mount/mount.c
Normal file
95
apps/mount/mount.c
Normal 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;
|
||||
}
|
||||
194
apps/sh/sh.c
194
apps/sh/sh.c
@@ -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", "");
|
||||
}
|
||||
|
||||
@@ -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
673
src/fat32.c
Normal 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
116
src/fat32.h
Normal 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 */
|
||||
127
src/kernel.c
127
src/kernel.c
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user