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", "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user