diff --git a/apps/mount/mount.c b/apps/mount/mount.c new file mode 100644 index 0000000..87dd3ce --- /dev/null +++ b/apps/mount/mount.c @@ -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 + * 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 \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 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; +} diff --git a/apps/sh/sh.c b/apps/sh/sh.c index 12789fa..2949c5a 100644 --- a/apps/sh/sh.c +++ b/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 */ 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", ""); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7563f3a..156935e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(kernel sysfs.c ide.c mbr.c + fat32.c env.c keyboard.c interrupts.S diff --git a/src/fat32.c b/src/fat32.c new file mode 100644 index 0000000..224dcc1 --- /dev/null +++ b/src/fat32.c @@ -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 + +/* 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; +} diff --git a/src/fat32.h b/src/fat32.h new file mode 100644 index 0000000..6003ac0 --- /dev/null +++ b/src/fat32.h @@ -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 +#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 */ diff --git a/src/kernel.c b/src/kernel.c index 15f73bc..54695e4 100644 --- a/src/kernel.c +++ b/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");