summaryrefslogtreecommitdiff
path: root/lume-legacy/src/fs
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
commite90b72ec3082588b296956730bd2930cf8d5d671 (patch)
tree36ff6916af9b4176ea5a9662219d11e721f0b691 /lume-legacy/src/fs
parent1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff)
downloadlume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz
lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'lume-legacy/src/fs')
-rw-r--r--lume-legacy/src/fs/fat32.cc806
-rw-r--r--lume-legacy/src/fs/file.cc358
-rw-r--r--lume-legacy/src/fs/fs.cc268
3 files changed, 1432 insertions, 0 deletions
diff --git a/lume-legacy/src/fs/fat32.cc b/lume-legacy/src/fs/fat32.cc
new file mode 100644
index 0000000..e4089d4
--- /dev/null
+++ b/lume-legacy/src/fs/fat32.cc
@@ -0,0 +1,806 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "fs/fat32.h"
+#include "lib/string.h"
+#include "drivers/uart.h"
+#include "kernel/mm.h"
+#include "kernel/proc.h"
+
+FAT32FileSystem fat32_fs;
+
+// Helper function
+static void generate_short_name(char *dst, const char *src)
+{
+ memset(dst, ' ', 11);
+
+ int i = 0;
+ for (; *src && *src != '.' && i < 8; src++)
+ {
+ char c = *src;
+ if (c >= 'a' && c <= 'z')
+ c -= 32;
+ dst[i++] = c;
+ }
+
+ while (*src && *src != '.')
+ src++;
+ if (*src == '.')
+ src++;
+
+ i = 8;
+ for (; *src && i < 11; src++)
+ {
+ char c = *src;
+ if (c >= 'a' && c <= 'z')
+ c -= 32;
+ dst[i++] = c;
+ }
+}
+
+// FAT32 FileSystem Implementation
+
+void FAT32FileSystem::init()
+{
+ struct Buf *b = BufferCache::bread(0, 0);
+ uint8 *data = (uint8 *)b->data;
+ struct FAT32_BPB *bpb = (struct FAT32_BPB *)data;
+
+ uint32 partition_lba = 0;
+
+ bool is_valid_bpb = (bpb->bytesPerSector == 512) && (bpb->rootClus != 0);
+
+ if (!is_valid_bpb)
+ {
+ Drivers::uart_puts("[FAT32] Sector 0 is not a valid BPB. Checking MBR...\n");
+
+ if (data[510] != 0x55 || data[511] != 0xAA)
+ {
+ Drivers::uart_puts("[FAT32] Panic: Invalid Disk Signature!\n");
+ while (1)
+ ;
+ }
+
+ uint32 lba = *(uint32 *)(data + 446 + 8);
+
+ if (lba > 0)
+ {
+ Drivers::uart_puts("[FAT32] MBR Partition 1 found @ Sector ");
+ Drivers::uart_put_int(lba);
+ Drivers::uart_puts("\n");
+ partition_lba = lba;
+ }
+ else
+ {
+ Drivers::uart_puts("[FAT32] Panic: No partition found.\n");
+ while (1)
+ ;
+ }
+
+ BufferCache::brelse(b);
+
+ b = BufferCache::bread(0, partition_lba / 2);
+ bpb = (struct FAT32_BPB *)b->data;
+ }
+
+ if (bpb->bytesPerSector != 512)
+ {
+ Drivers::uart_puts("[FAT32] Panic: Invalid FAT32 BPB in partition.\n");
+ while (1)
+ ;
+ }
+
+ uint32 fat_sz = bpb->FATSz32;
+ this->fat_sz = fat_sz;
+ uint32 reserved_sec = bpb->reservedSecCnt;
+ uint8 num_fats = bpb->numFATs;
+
+ sectors_per_cluster = bpb->secPerClus;
+ bytes_per_cluster = sectors_per_cluster * 512;
+ root_cluster = bpb->rootClus;
+
+ fat_start_sector = partition_lba + reserved_sec;
+ data_start_sector = partition_lba + reserved_sec + (fat_sz * num_fats);
+
+ Drivers::uart_puts(ANSI_GREEN);
+ Drivers::uart_puts("[FAT32] Init Success: RootClus=");
+ Drivers::uart_put_int(root_cluster);
+ Drivers::uart_puts(", DataStart=");
+ Drivers::uart_put_int(data_start_sector);
+ Drivers::uart_puts(ANSI_RESET);
+ Drivers::uart_puts("\n");
+
+ BufferCache::brelse(b);
+}
+
+Inode *FAT32FileSystem::root()
+{
+ FAT32Inode *ip = new FAT32Inode();
+
+ ip->fs = this;
+ ip->dev = 0;
+ ip->inum = root_cluster;
+ ip->ref_cnt = 1;
+ ip->valid = 1;
+ ip->type = T_DIR;
+ ip->size = 0;
+ ip->nlink = 1;
+
+ ip->first_clus = root_cluster;
+ ip->parent_clus = 0;
+
+ return ip;
+}
+
+uint32 FAT32FileSystem::cluster_to_sector(uint32 cluster)
+{
+ return data_start_sector + (cluster - 2) * sectors_per_cluster;
+}
+
+uint32 FAT32FileSystem::next_cluster(uint32 cluster)
+{
+ uint32 fat_offset = cluster * 4;
+ uint32 fat_sector = fat_start_sector + (fat_offset / 512);
+ uint32 ent_offset = fat_offset % 512;
+
+ // block = sector / 2
+ struct Buf *b = BufferCache::bread(0, fat_sector / 2);
+
+ uint32 block_offset = (fat_sector % 2) * 512 + ent_offset;
+ uint32 next = *(uint32 *)(b->data + block_offset);
+
+ BufferCache::brelse(b);
+
+ return next & 0x0FFFFFFF;
+}
+
+// FAT32 Inode Implementation
+
+static bool match_name(const char *disk_name, const char *target)
+{
+ char temp[13];
+ int i, j;
+
+ for (i = 0; i < 8 && disk_name[i] != ' '; i++)
+ temp[i] = disk_name[i];
+ if (disk_name[8] != ' ')
+ {
+ temp[i++] = '.';
+ for (j = 0; j < 3 && disk_name[8 + j] != ' '; j++)
+ temp[i++] = disk_name[8 + j];
+ }
+ temp[i] = 0;
+ return strcasecmp(temp, target) == 0;
+}
+
+int FAT32Inode::read(char *dst, uint32 off, uint32 n, int isUser)
+{
+ if (first_clus == 0)
+ return 0;
+
+ // [FIX] 只有非目录文件才检查 size 边界
+ // 目录文件 size 可能为 0,应允许读取直到簇链结束
+ if (type != T_DIR)
+ {
+ if (off >= size)
+ {
+ return 0;
+ }
+ if (off + n > size)
+ {
+ n = size - off;
+ }
+ }
+
+ uint32 tot = 0;
+ uint32 clus = first_clus;
+ uint32 clus_sz = fs->bytes_per_cluster;
+
+ uint32 skip = off / clus_sz;
+ uint32 current_off = off % clus_sz;
+
+ while (skip > 0)
+ {
+ clus = fs->next_cluster(clus);
+ if (clus >= 0x0FFFFFF8)
+ return 0;
+ skip--;
+ }
+
+ while (tot < n)
+ {
+ uint32 sec_in_clus = current_off / 512;
+
+ if (sec_in_clus >= fs->sectors_per_cluster)
+ {
+ clus = fs->next_cluster(clus);
+ if (clus >= 0x0FFFFFF8)
+ break;
+ current_off = 0;
+ continue;
+ }
+
+ uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus;
+ uint32 off_in_sec = current_off % 512;
+
+ uint32 m = 512 - off_in_sec;
+ if (n - tot < m)
+ m = n - tot;
+
+ struct Buf *b = BufferCache::bread(dev, sec / 2);
+ uint32 block_offset = (sec % 2) * 512 + off_in_sec;
+
+ if (isUser)
+ {
+ if (VM::copyout(myproc()->pagetable, (uint64)dst, (char *)(b->data + block_offset), m) < 0)
+ {
+ BufferCache::brelse(b);
+ return -1;
+ }
+ }
+ else
+ {
+ memmove(dst, b->data + block_offset, m);
+ }
+ BufferCache::brelse(b);
+
+ tot += m;
+ dst += m;
+ current_off += m;
+ }
+ return tot;
+}
+
+Inode *FAT32Inode::lookup(const char *name)
+{
+ if (type != T_DIR)
+ return nullptr;
+
+ uint32 clus = first_clus;
+ struct FAT32_DirEnt de;
+
+ while (clus < 0x0FFFFFF8)
+ {
+ uint32 sec = fs->cluster_to_sector(clus);
+
+ for (uint32 i = 0; i < fs->sectors_per_cluster; i++)
+ {
+ uint32 current_sec = sec + i;
+ struct Buf *b = BufferCache::bread(dev, current_sec / 2);
+ uint32 block_base = (current_sec % 2) * 512;
+
+ for (int off = 0; off < 512; off += 32)
+ {
+ memmove(&de, b->data + block_base + off, 32);
+
+ if (de.name[0] == 0x00)
+ {
+ BufferCache::brelse(b);
+ return nullptr;
+ }
+ if (de.name[0] == 0xE5)
+ continue;
+ if (de.attr & ATTR_LONG_NAME)
+ continue;
+
+ if (match_name(de.name, name))
+ {
+ FAT32Inode *ip = new FAT32Inode();
+ ip->fs = fs;
+ ip->dev = dev;
+ ip->type = (de.attr & ATTR_DIRECTORY) ? T_DIR : T_FILE;
+ ip->size = de.fileSize;
+ ip->first_clus = (de.fstClusHI << 16) | de.fstClusLO;
+ ip->inum = ip->first_clus;
+ ip->nlink = 1;
+ ip->ref_cnt = 1;
+ ip->valid = 1;
+ ip->ent_sec = current_sec; // Log Sector
+ ip->ent_off = off; // Log Setoff
+
+ BufferCache::brelse(b);
+ return ip;
+ }
+ }
+ BufferCache::brelse(b);
+ }
+ clus = fs->next_cluster(clus);
+ }
+ return nullptr;
+}
+
+// Placeholder
+uint32 FAT32FileSystem::alloc_cluster()
+{
+ uint32 fat_entries_per_sector = 512 / 4;
+ for (uint32 i = 0; i < fat_sz; i++)
+ {
+ uint32 current_fat_sec = fat_start_sector + i;
+ struct Buf *b = BufferCache::bread(0, current_fat_sec / 2);
+ uint32 *table = (uint32 *)(b->data + (current_fat_sec % 2) * 512);
+ for (uint32 j = 0; j < fat_entries_per_sector; j++)
+ {
+ if (i == 0 && j < 2)
+ continue;
+
+ if ((table[j] & 0x0FFFFFFF) == 0)
+ {
+ // Find the free cluster and mark it as EOF (0x0FFFFFFF)
+ table[j] = 0x0FFFFFFF;
+ BufferCache::bwrite(b); // Write back to disk
+ BufferCache::brelse(b);
+
+ uint32 cluster_idx = i * fat_entries_per_sector + j;
+
+ // Clear the data content of the new cluster
+ uint32 data_sec = cluster_to_sector(cluster_idx);
+ for (uint32 k = 0; k < sectors_per_cluster; k++)
+ {
+ struct Buf *db = BufferCache::bread(0, (data_sec + k) / 2);
+ memset(db->data + ((data_sec + k) % 2) * 512, 0, 512);
+ BufferCache::bwrite(db);
+ BufferCache::brelse(db);
+ }
+
+ return cluster_idx;
+ }
+ }
+ BufferCache::brelse(b);
+ }
+ Drivers::uart_puts("[FAT32] Out of space!\n");
+ return 0;
+}
+
+void FAT32FileSystem::free_cluster_chain(uint32 cluster)
+{
+ while (cluster >= 2 && cluster < 0x0FFFFFF8)
+ {
+ uint32 next = next_cluster(cluster);
+
+ // Calculate the current cluster's position in the FAT table
+ uint32 fat_offset = cluster * 4;
+ uint32 fat_sector = fat_start_sector + (fat_offset / 512);
+ uint32 ent_offset = fat_offset % 512;
+
+ // Read FAT sector
+ struct Buf *b = BufferCache::bread(0, fat_sector / 2);
+
+ // Marked as idle (0x00000000)
+ uint32 *entry = (uint32 *)(b->data + (fat_sector % 2) * 512 + ent_offset);
+ *entry = 0;
+
+ BufferCache::bwrite(b);
+ BufferCache::brelse(b);
+
+ cluster = next;
+ }
+}
+
+void FAT32Inode::update()
+{
+ struct Buf *b = BufferCache::bread(dev, ent_sec / 2);
+ uint32 offset = (ent_sec % 2) * 512 + ent_off;
+
+ struct FAT32_DirEnt *de = (struct FAT32_DirEnt *)(b->data + offset);
+
+ // Update file size and starting cluster
+ de->fileSize = size;
+ de->fstClusHI = (uint16)((first_clus >> 16) & 0xFFFF);
+ de->fstClusLO = (uint16)(first_clus & 0xFFFF);
+
+ BufferCache::bwrite(b);
+ BufferCache::brelse(b);
+}
+
+int FAT32Inode::write(const char *src, uint32 off, uint32 n, int isUser)
+{
+ if (off > size)
+ {
+ return -1;
+ }
+
+ uint32 tot = 0;
+ uint32 clus = first_clus;
+ uint32 clus_sz = fs->bytes_per_cluster;
+ uint32 prev_clus = 0;
+
+ if (clus == 0)
+ {
+ clus = fs->alloc_cluster();
+ if (clus == 0)
+ return -1;
+ first_clus = clus;
+ // Update the metadata immediately to prevent data loss in case of a power outage.
+ update();
+ }
+
+ uint32 skip = off / clus_sz;
+ uint32 current_off = off % clus_sz;
+
+ while (skip > 0)
+ {
+ prev_clus = clus;
+ clus = fs->next_cluster(clus);
+ if (clus >= 0x0FFFFFF8)
+ {
+ return -1;
+ }
+ skip--;
+ }
+ while (tot < n)
+ {
+ if (current_off == clus_sz)
+ {
+ uint32 next = fs->next_cluster(clus);
+ if (next >= 0x0FFFFFF8)
+ {
+ uint32 new_clus = fs->alloc_cluster();
+ if (new_clus == 0)
+ break;
+
+ // Point the current cluster to the new cluster (update the FAT table)
+ uint32 fat_offset = clus * 4;
+ uint32 fat_sec = fs->fat_start_sector + (fat_offset / 512);
+ struct Buf *fat_b = BufferCache::bread(dev, fat_sec / 2);
+ uint32 *entry = (uint32 *)(fat_b->data + (fat_sec % 2) * 512 + (fat_offset % 512));
+ *entry = new_clus; // Link
+ BufferCache::bwrite(fat_b);
+ BufferCache::brelse(fat_b);
+
+ clus = new_clus;
+ }
+ else
+ {
+ clus = next;
+ }
+ current_off = 0;
+ }
+
+ // Calculate the physical sector to write
+ uint32 sec_in_clus = current_off / 512;
+ uint32 sec = fs->cluster_to_sector(clus) + sec_in_clus;
+ uint32 off_in_sec = current_off % 512;
+
+ // Calculate how many bytes can be written this time
+ uint32 m = 512 - off_in_sec;
+ if (n - tot < m)
+ m = n - tot;
+
+ struct Buf *b = BufferCache::bread(dev, sec / 2);
+ uint32 block_offset = (sec % 2) * 512 + off_in_sec;
+
+ if (isUser)
+ {
+ if (VM::copyin(myproc()->pagetable, (char *)(b->data + block_offset), (uint64)src, m) < 0)
+ {
+ BufferCache::brelse(b);
+ return -1;
+ }
+ }
+ else
+ {
+ memmove(b->data + block_offset, src, m);
+ }
+
+ BufferCache::bwrite(b);
+ BufferCache::brelse(b);
+
+ tot += m;
+ src += m;
+ current_off += m;
+ }
+ if (off + n > size)
+ {
+ size = off + n;
+ update();
+ }
+
+ return tot;
+}
+
+Inode *FAT32Inode::create(const char *name, short type, short major, short minor)
+{
+ Inode *exist = lookup(name);
+ if (exist)
+ {
+ return exist;
+ }
+
+ struct FAT32_DirEnt de;
+ memset(&de, 0, sizeof(de));
+ generate_short_name(de.name, name);
+ de.attr = (type == T_DIR) ? ATTR_DIRECTORY : ATTR_ARCHIVE;
+
+ uint32 new_dir_clus = 0;
+ if (type == T_DIR)
+ {
+ new_dir_clus = fs->alloc_cluster();
+ if (new_dir_clus == 0)
+ return nullptr;
+
+ de.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF);
+ de.fstClusLO = (uint16)(new_dir_clus & 0xFFFF);
+ }
+ else
+ {
+ de.fileSize = 0;
+ }
+
+ uint32 clus = first_clus;
+ bool written = false;
+
+ while (true)
+ {
+ uint32 sec = fs->cluster_to_sector(clus);
+
+ for (uint32 i = 0; i < fs->sectors_per_cluster; i++)
+ {
+ struct Buf *b = BufferCache::bread(dev, (sec + i) / 2);
+ uint8 *data = (uint8 *)b->data + ((sec + i) % 2) * 512;
+
+ for (int off = 0; off < 512; off += 32)
+ {
+ if (data[off] == 0x00 || data[off] == 0xE5)
+ {
+ memmove(data + off, &de, 32);
+ BufferCache::bwrite(b);
+ BufferCache::brelse(b);
+ written = true;
+ break;
+ }
+ }
+
+ if (written)
+ break;
+ BufferCache::brelse(b);
+ }
+
+ if (written)
+ break;
+
+ uint32 next = fs->next_cluster(clus);
+ if (next >= 0x0FFFFFF8)
+ {
+ uint32 new_parent_clus = fs->alloc_cluster();
+ if (new_parent_clus == 0)
+ return nullptr;
+
+ uint32 fat_off = clus * 4;
+ uint32 fat_sec = fs->fat_start_sector + (fat_off / 512);
+ struct Buf *fb = BufferCache::bread(dev, fat_sec / 2);
+ *(uint32 *)(fb->data + (fat_sec % 2) * 512 + (fat_off % 512)) = new_parent_clus;
+ BufferCache::bwrite(fb);
+ BufferCache::brelse(fb);
+
+ uint32 s = fs->cluster_to_sector(new_parent_clus);
+ for (uint32 k = 0; k < fs->sectors_per_cluster; k++)
+ {
+ struct Buf *zb = BufferCache::bread(dev, (s + k) / 2);
+ uint8 *zdata = (uint8 *)zb->data + ((s + k) % 2) * 512;
+ memset(zdata, 0, 512);
+
+ if (k == 0)
+ {
+ memmove(zdata, &de, 32);
+ }
+
+ BufferCache::bwrite(zb);
+ BufferCache::brelse(zb);
+ }
+
+ written = true;
+ break;
+ }
+ clus = next;
+ }
+
+ if (written && type == T_DIR)
+ {
+ struct FAT32_DirEnt dot;
+ memset(&dot, 0, 32);
+ memset(dot.name, ' ', 11);
+ dot.name[0] = '.';
+ dot.attr = ATTR_DIRECTORY;
+ dot.fstClusHI = (uint16)((new_dir_clus >> 16) & 0xFFFF);
+ dot.fstClusLO = (uint16)(new_dir_clus & 0xFFFF);
+
+ struct FAT32_DirEnt dotdot;
+ memset(&dotdot, 0, 32);
+ memset(dotdot.name, ' ', 11);
+ dotdot.name[0] = '.';
+ dotdot.name[1] = '.';
+ dotdot.attr = ATTR_DIRECTORY;
+
+ uint32 parent_clus_num = this->first_clus;
+ if (parent_clus_num == fs->root_cluster)
+ {
+ parent_clus_num = 0;
+ }
+ dotdot.fstClusHI = (uint16)((parent_clus_num >> 16) & 0xFFFF);
+ dotdot.fstClusLO = (uint16)(parent_clus_num & 0xFFFF);
+
+ uint32 s = fs->cluster_to_sector(new_dir_clus);
+
+ struct Buf *zb = BufferCache::bread(dev, s / 2);
+ uint8 *zdata = (uint8 *)zb->data + (s % 2) * 512;
+
+ memmove(zdata, &dot, 32); // Offset 0
+ memmove(zdata + 32, &dotdot, 32); // Offset 32
+
+ BufferCache::bwrite(zb);
+ BufferCache::brelse(zb);
+ }
+
+ if (written)
+ {
+ return lookup(name);
+ }
+
+ return nullptr;
+}
+
+void FAT32Inode::truncate()
+{
+ if (first_clus == 0)
+ {
+ if (size != 0)
+ {
+ size = 0;
+ update();
+ }
+ return;
+ }
+
+ fs->free_cluster_chain(first_clus);
+
+ size = 0;
+ first_clus = 0;
+
+ update();
+}
+
+int FAT32Inode::unlink(const char *name)
+{
+ if (type != T_DIR)
+ return -1;
+ if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
+ return -1;
+
+ uint32_t clus = first_clus;
+ bool found = false;
+
+ // [FIX] Deadlock prevention: Do not hold buffer while looking up inode
+ uint32_t target_block = 0;
+ uint32_t target_offset = 0;
+ struct FAT32_DirEnt de;
+
+ while (true)
+ {
+ uint32_t sec = fs->cluster_to_sector(clus);
+ for (uint32 i = 0; i < fs->sectors_per_cluster; ++i)
+ {
+ struct Buf *b = BufferCache::bread(dev, (sec + i) / 2);
+ uint8_t *data = (uint8_t *)b->data + ((sec + i) % 2) * 512;
+
+ for (int off = 0; off < 512; off += 32)
+ {
+ memmove(&de, data + off, 32);
+ if (de.name[0] == 0x00) // end of dir
+ {
+ BufferCache::brelse(b);
+ return -1; // Not found
+ }
+ if (de.name[0] == 0xE5)
+ continue; // have been unlink
+ if (de.attr & ATTR_LONG_NAME)
+ continue;
+
+ if (match_name(de.name, name))
+ {
+ target_block = (sec + i) / 2;
+ target_offset = ((sec + i) % 2) * 512 + off;
+ found = true;
+ break;
+ }
+ }
+
+ BufferCache::brelse(b); // Always release buffer
+ if (found)
+ break;
+ }
+
+ if (found)
+ break;
+
+ clus = fs->next_cluster(clus);
+ if (clus >= 0x0FFFFFF8)
+ return -1;
+ }
+
+ if (found)
+ {
+ Inode *target_ip = lookup(name);
+ if (target_ip)
+ {
+ VFS::ilock(target_ip);
+ if (target_ip->type == T_DIR)
+ {
+ VFS::iunlockput(target_ip);
+ return -1;
+ }
+
+ target_ip->nlink--;
+ target_ip->update();
+ VFS::iunlockput(target_ip);
+ }
+
+ // Re-read buffer to mark deletion
+ struct Buf *target_buf = BufferCache::bread(dev, target_block);
+ uint8_t *ptr = (uint8_t *)target_buf->data + target_offset;
+ *ptr = 0xE5;
+
+ BufferCache::bwrite(target_buf);
+ BufferCache::brelse(target_buf);
+ return 0;
+ }
+
+ return -1;
+}
+
+// Test Function
+void fat32_test()
+{
+ static bool inited = false;
+ if (!inited)
+ {
+ fat32_fs.init();
+ inited = true;
+ }
+
+ Drivers::uart_puts("\n[FAT32] Starting Test via Syscall...\n");
+
+ Inode *root = fat32_fs.root();
+ if (root)
+ {
+ Drivers::uart_puts("[FAT32] Root Inode retrieved. Inum: ");
+ Drivers::uart_put_int(root->inum);
+ Drivers::uart_puts("\n");
+
+ Drivers::uart_puts("[FAT32] Looking for TEST.TXT...\n");
+ Inode *node = root->lookup("test.txt");
+
+ if (node)
+ {
+ Drivers::uart_puts("[FAT32] Found TEST.TXT! Size: ");
+ Drivers::uart_put_int(node->size);
+ Drivers::uart_puts("\n");
+
+ char buf[128];
+ int n = node->read(buf, 0, 127, 0);
+ if (n > 0)
+ {
+ buf[n] = 0;
+ Drivers::uart_puts("[FAT32] Content:\n>> ");
+ Drivers::uart_puts(buf);
+ Drivers::uart_puts("\n");
+ }
+ else
+ {
+ Drivers::uart_puts("[FAT32] Read returned 0.\n");
+ }
+ VFS::iput(node);
+ }
+ else
+ {
+ Drivers::uart_puts("[FAT32] TEST.TXT not found.\n");
+ }
+ VFS::iput(root);
+ }
+ else
+ {
+ Drivers::uart_puts("[FAT32] Failed to get root inode.\n");
+ }
+
+ Drivers::uart_puts("[FAT32] Test Finished.\n");
+} \ No newline at end of file
diff --git a/lume-legacy/src/fs/file.cc b/lume-legacy/src/fs/file.cc
new file mode 100644
index 0000000..d814332
--- /dev/null
+++ b/lume-legacy/src/fs/file.cc
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "fs/file.h"
+#include "kernel/spinlock.h"
+#include "kernel/mm.h"
+#include "drivers/uart.h"
+#include "kernel/proc.h"
+#include "kernel/slab.h"
+
+#define NFILE 100
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+
+extern void *operator new(unsigned long, void *p);
+
+int Pipe::create_pair(file** f0, file** f1)
+{
+ void *mem = Slab::kmalloc(sizeof(Pipe));
+ if (!mem)
+ return -1;
+ Pipe *pi = new (mem) Pipe();
+
+ *f0 = FileTable::alloc();
+ *f1 = FileTable::alloc();
+
+ if (*f0 == nullptr || *f1 == nullptr)
+ {
+ if (*f0)
+ FileTable::close(*f0);
+ if (*f1)
+ FileTable::close(*f1);
+ pi->~Pipe();
+ Slab::kfree(pi);
+ return -1;
+ }
+
+ (*f0)->type = FD_PIPE;
+ (*f0)->readable = 1;
+ (*f0)->writable = 0;
+ (*f0)->pipe = pi;
+
+ (*f1)->type = FD_PIPE;
+ (*f1)->readable = 0;
+ (*f1)->writable = 1;
+ (*f1)->pipe = pi;
+
+ return 0;
+}
+
+void Pipe::close(bool is_writer)
+{
+ lock.acquire();
+ if (is_writer)
+ {
+ write_open = false;
+ ProcManager::wakeup(&nread); // Wake up the process that is currently reading
+ }
+ else
+ {
+ read_open = false;
+ ProcManager::wakeup(&nwrite); // Wake up the process that is writing
+ }
+
+ bool empty = !read_open && !write_open;
+ lock.release();
+
+ if (empty)
+ {
+ this->~Pipe();
+ Slab::kfree(this);
+ }
+}
+
+int Pipe::write(uint64_t addr, int n)
+{
+ int i = 0;
+ struct Proc *p = myproc();
+
+ lock.acquire();
+ while (i < n)
+ {
+ if (!read_open || p->xstate) // The read end is closed or the process is abnormal
+ {
+ lock.release();
+ return -1;
+ }
+
+ if (nwrite == nread + SIZE) // buffer is full
+ {
+ ProcManager::wakeup(&nread);
+ ProcManager::sleep(&nwrite, &lock);
+ }
+ else
+ {
+ char ch;
+ if (VM::copyin(p->pagetable, &ch, addr + i, 1) < 0)
+ break;
+ data[nwrite++ % SIZE] = ch;
+ i++;
+ }
+ }
+ ProcManager::wakeup(&nread);
+ lock.release();
+ return i;
+}
+
+int Pipe::read(uint64_t addr, int n)
+{
+ int i = 0;
+ struct Proc *p = myproc();
+
+ lock.acquire();
+ while (nread == nwrite && write_open) // The buffer is empty and the write end is not closed
+ {
+ if (p->xstate)
+ {
+ lock.release();
+ return -1;
+ }
+ ProcManager::sleep(&nread, &lock);
+ }
+
+ for (i = 0; i < n; ++i)
+ {
+ if (nread == nwrite)
+ break;
+ char ch = data[nread++ % SIZE];
+ if (VM::copyout(p->pagetable, addr + i, &ch, 1) < 0)
+ break;
+ }
+ ProcManager::wakeup(&nwrite);
+ lock.release();
+ return i;
+}
+
+namespace FileTable
+{
+ struct
+ {
+ Spinlock lock;
+ struct file files[NFILE];
+ } ftable;
+
+ void init()
+ {
+ ftable.lock.init("file_table");
+ }
+
+ struct file *alloc()
+ {
+ struct file *f;
+ ftable.lock.acquire();
+ for (f = ftable.files; f < ftable.files + NFILE; f++)
+ {
+ if (f->ref == 0)
+ {
+ f->ref = 1;
+ ftable.lock.release();
+ return f;
+ }
+ }
+ ftable.lock.release();
+ return nullptr;
+ }
+
+ struct file *dup(struct file *f)
+ {
+ ftable.lock.acquire();
+ if (f->ref < 1)
+ {
+ Drivers::uart_puts("panic: file dup\n");
+ while (1)
+ ;
+ }
+ f->ref++;
+ ftable.lock.release();
+ return f;
+ }
+
+ void close(struct file *f)
+ {
+ struct file ff;
+
+ ftable.lock.acquire();
+ if(f->ref < 1)
+ {
+ Drivers::uart_puts("panic: file close\n");
+ while (1)
+ ;
+ }
+ f->ref--;
+ if (f->ref > 0)
+ {
+ ftable.lock.release();
+ return;
+ }
+
+ ff = *f;
+ f->ref = 0;
+ f->type = FD_NONE;
+ ftable.lock.release();
+ if (ff.type == FD_INODE)
+ {
+ // Release the corresponding Inode
+ VFS::iput(ff.ip);
+ }
+ else if (ff.type == FD_PIPE)
+ {
+ if (ff.pipe)
+ ff.pipe->close(ff.writable);
+ }
+ }
+
+ int read(struct file *f, uint64 addr, int n)
+ {
+ if (f->readable == 0)
+ return -1;
+ if (f->type == FD_INODE)
+ {
+ VFS::ilock(f->ip);
+
+ if (f->ip->type == T_DEVICE)
+ {
+ if (f->ip->major == 1)
+ {
+ VFS::iunlock(f->ip);
+ return Drivers::console_read(addr, n);
+ }
+ }
+
+ int r = f->ip->read((char*)addr, f->off, n, 1);
+ if(r > 0)
+ {
+ f->off += r;
+ }
+ VFS::iunlock(f->ip);
+ return r;
+ }
+ else if (f->type == FD_PIPE)
+ {
+ if (f->pipe)
+ return f->pipe->read(addr, n);
+ }
+ return -1;
+ }
+
+ int write(struct file *f, uint64 addr, int n)
+ {
+ if (f->writable == 0)
+ return -1;
+ if (f->type == FD_INODE)
+ {
+ VFS::ilock(f->ip);
+
+ if (f->ip->type == T_DEVICE)
+ {
+ if (f->ip->major == 1) // Console
+ {
+ VFS::iunlock(f->ip);
+ struct Proc *proc = myproc();
+ constexpr int MAX_WRITE_BUF = 128;
+ char buf[MAX_WRITE_BUF];
+ int i = 0;
+ while (i < n)
+ {
+ int len = n - i;
+ if (len > MAX_WRITE_BUF)
+ len = MAX_WRITE_BUF;
+
+ if (VM::copyin(proc->pagetable, buf, addr + i, static_cast<uint64>(len)) < 0)
+ {
+ return -1;
+ }
+
+ for (int j = 0; j < len; j++)
+ {
+ Drivers::uart_putc(buf[j]);
+ }
+ i += len;
+ }
+ return n;
+ }
+ }
+
+ int r = f->ip->write((char *)addr, f->off, n, 1);
+ if (r > 0)
+ {
+ f->off += r;
+ }
+ VFS::iunlock(f->ip);
+ return r;
+ }
+ else if (f->type == FD_PIPE)
+ {
+ if (f->pipe)
+ return f->pipe->write(addr, n);
+ }
+
+ return -1;
+ }
+
+ int stat(struct file *f, uint64 addr)
+ {
+ if (f->type == FD_INODE)
+ {
+ struct kstat st;
+ VFS::ilock(f->ip);
+ f->ip->getattr(&st);
+ VFS::iunlock(f->ip);
+
+ struct Proc *p = myproc();
+ if (VM::copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0)
+ return -1;
+ return 0;
+ }
+ return -1;
+ }
+
+ int lseek(struct file *f, int offset, int whence)
+ {
+ if (f->type != FD_INODE)
+ return -1;
+
+ VFS::ilock(f->ip);
+
+ int new_off = f->off;
+ int size = f->ip->size;
+
+ switch (whence)
+ {
+ case SEEK_SET:
+ new_off = offset;
+ break;
+ case SEEK_CUR:
+ new_off += offset;
+ break;
+ case SEEK_END:
+ new_off = size + offset;
+ break;
+ default:
+ VFS::iunlock(f->ip);
+ return -1;
+ }
+
+ if (new_off < 0)
+ {
+ VFS::iunlock(f->ip);
+ return -1;
+ }
+
+ f->off = new_off;
+ VFS::iunlock(f->ip);
+ return new_off;
+ }
+} // namespace FileTable
diff --git a/lume-legacy/src/fs/fs.cc b/lume-legacy/src/fs/fs.cc
new file mode 100644
index 0000000..fc8f555
--- /dev/null
+++ b/lume-legacy/src/fs/fs.cc
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Richard QIn
+ */
+#include "fs/fs.h"
+#include "kernel/riscv.h"
+#include "drivers/uart.h"
+#include "kernel/spinlock.h"
+#include "kernel/proc.h"
+// #include "fs/fat32.h" // Decoupled
+#include "lib/string.h"
+
+static Inode *global_root_inode = nullptr;
+
+static const char *skipelem(const char *path, char *name)
+{
+ const char *s;
+ int len;
+
+ while (*path == '/')
+ path++;
+ if (*path == 0)
+ return nullptr;
+
+ s = path;
+ while (*path != '/' && *path != 0)
+ path++;
+
+ len = path - s;
+ if (len >= 128)
+ len = 127;
+
+ memmove(name, s, len);
+ name[len] = 0;
+
+ while (*path == '/')
+ path++;
+ return path;
+}
+
+void Inode::getattr(struct kstat *st)
+{
+ st->dev = dev;
+ st->ino = inum;
+ st->type = type;
+ st->nlink = nlink;
+ st->size = size;
+
+ st->uid = 0;
+ st->gid = 0;
+ st->mode = 0;
+}
+
+namespace VFS
+{
+ // File System Registry
+ static FileSystem *filesystems[8];
+ static int fs_count = 0;
+
+ void register_fs(FileSystem *fs)
+ {
+ if (fs_count < 8)
+ {
+ filesystems[fs_count++] = fs;
+ }
+ }
+
+ void init()
+ {
+ Drivers::uart_puts("[VFS] Inode subsystem initialized.\n");
+ }
+
+ void iput(Inode *ip)
+ {
+ if (ip == nullptr)
+ return;
+
+ ip->lock.acquire();
+ if(ip->ref_cnt < 1)
+ {
+ Drivers::uart_puts("panic: iput ref\n");
+ while (1)
+ ;
+ }
+ ip->ref_cnt--;
+ int ref = ip->ref_cnt;
+ ip->lock.release();
+
+ if (ref == 0)
+ {
+ delete ip;
+ }
+ }
+
+ Inode* idup(Inode *ip)
+ {
+ if (ip == nullptr)
+ return nullptr;
+
+ ip->lock.acquire();
+ ip->ref_cnt++;
+ ip->lock.release();
+ return ip;
+ }
+
+ void ilock(Inode *ip)
+ {
+ if (ip == 0 || ip->ref_cnt < 1)
+ {
+ Drivers::uart_puts("panic: ilock\n");
+ while (1)
+ ;
+ }
+
+ ip->lock.acquire();
+
+ }
+
+ void iunlock(Inode *ip)
+ {
+ if (ip == 0 || !ip->lock.holding() || ip->ref_cnt < 1)
+ {
+ Drivers::uart_puts("panic: iunlock\n");
+ while (1)
+ ;
+ }
+ ip->lock.release();
+ }
+
+ void iunlockput(Inode *ip)
+ {
+ if (!ip)
+ return;
+ iunlock(ip);
+ iput(ip);
+ }
+
+ void mount_root(Inode *root)
+ {
+ if (global_root_inode)
+ {
+ Drivers::uart_puts("VFS: Root already mounted!\n");
+ return;
+ }
+ global_root_inode = root;
+ Drivers::uart_puts("[VFS] Root filesystem mounted.\n");
+ }
+
+ Inode *get_root()
+ {
+ if (global_root_inode == nullptr)
+ {
+ // Try to mount the first registered FS
+ if (fs_count > 0)
+ {
+ Drivers::uart_puts("[VFS] Lazy init: Mounting root FS...\n");
+ filesystems[0]->init();
+ mount_root(filesystems[0]->root());
+ }
+ else
+ {
+ Drivers::uart_puts("[VFS] No filesystem registered!\n");
+ }
+ }
+ if (!global_root_inode)
+ return nullptr;
+ return idup(global_root_inode);
+ }
+
+ Inode *namei(const char *path)
+ {
+ char name[128];
+ Inode *ip, *next;
+ struct Proc *p = myproc();
+
+ // 1. 确定起点
+ if (*path == '/')
+ {
+ ip = get_root();
+ }
+ else
+ {
+ if (p->cwd == nullptr)
+ {
+ ip = get_root();
+ }
+ else
+ {
+ ip = idup(p->cwd);
+ }
+ }
+
+ if (ip == nullptr)
+ return nullptr;
+
+ while ((path = skipelem(path, name)) != nullptr)
+ {
+ if (strcmp(name, ".") == 0)
+ {
+ continue;
+ }
+
+ if (strcmp(name, "..") == 0)
+ {
+ Inode *root = get_root();
+ bool is_root = (ip->inum == root->inum);
+ iput(root);
+
+ if (is_root)
+ {
+ continue;
+ }
+ }
+
+ ilock(ip);
+
+ if (ip->type != T_DIR)
+ {
+ iunlockput(ip);
+ return nullptr;
+ }
+
+ next = ip->lookup(name);
+ iunlockput(ip);
+
+ ip = next;
+ if (ip == nullptr)
+ return nullptr;
+ }
+
+ return ip;
+ }
+
+ Inode *nameiparent(const char* path, char* name)
+ {
+ Inode* ip;
+ Inode* next;
+
+ if (*path == '/')
+ ip = get_root();
+ else
+ ip = idup(myproc()->cwd ? myproc()->cwd : get_root());
+
+ while ((path = skipelem(path, name)) != nullptr)
+ {
+ if (*path == 0)
+ {
+ return ip;
+ }
+
+ ilock(ip);
+ if (ip->type != T_DIR)
+ {
+ iunlockput(ip);
+ return nullptr;
+ }
+
+ next = ip->lookup(name);
+ iunlockput(ip);
+
+ ip = next;
+ if (!ip)
+ return nullptr;
+ }
+ iput(ip);
+ return nullptr;
+ }
+} // namespace VFS