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", "");
}