From 92ed4f6668efccf6af73f45654405d333e6a318c Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 02:19:07 +0200 Subject: fat: mount every FAT32 volume under /disk//, fix multi-entry LFN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fat.rs: VOL: Option → VOLS: Vec; try_mount() returns a Volume per disk and init() mounts every valid FAT32 volume found. Volume UID = sanitized uppercase label + 8-hex volume serial (e.g. RSDOS-5BD1E55F): unique per format, stable across reboots, human-recognizable, machine-parseable (^[A-Z0-9_]+-[0-9A-F]{8}$); cloned volumes get a -N suffix. Public ops take a vol index and select it (select_vol also picks the backing AHCI disk), matching the ahci::select pattern - fs.rs: /disk is now a plain directory whose children are one Disk node per mounted volume, named by UID; DiskInfo carries vol - LFN fix: entries are stored last-first on disk — both the writer (head-entry first) and reader (append in disk order) were reversed and cancelled out for ≤13-char names; multi-entry names (>13 chars) read back mangled (e.g. 'from-kernel.txt' as 'xt'). Writer now places seq=1 closest to the SFN and seq=N|0x40 first; reader places chunks by sequence number and trims at the first NUL. Verified with a 25-char filename round trip: kernel ls/cat correct, mtools shows the full LFN, fsck clean on both volumes - test-disk.sh: test file renamed to disk-test-.txt (>13 chars, exercises multi-entry LFN); UID discovered from the boot log; QEMU stdio redirected to a log file + EXIT trap so an orphaned QEMU can never hang a piped caller (it previously did, 400s) --- rsnix/src/fs.rs | 76 ++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 30 deletions(-) (limited to 'rsnix/src/fs.rs') diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs index 6950171..4b76e07 100644 --- a/rsnix/src/fs.rs +++ b/rsnix/src/fs.rs @@ -59,6 +59,7 @@ enum NodeData { /// op, so it stays correct as the volume changes), `cluster` the entry's /// own first cluster. struct DiskInfo { + vol: usize, // index into fat::VOLS (which FAT32 volume this lives on) parent: u32, name: String, is_dir: bool, @@ -174,21 +175,32 @@ pub fn init() -> (usize, usize) { ), ); - // Mount the FAT32 volume at /disk when one is present. - if crate::fat::is_mounted() { - let root_cl = crate::fat::root_cluster(); - add( + // Mount every FAT32 volume under /disk// (UID = label-serial). + let disks = crate::fat::volume_count(); + if disks > 0 { + let disk_dir = add( "disk", Some(root), FileType::Dir, - NodeData::Disk(DiskInfo { - parent: root_cl, - name: String::new(), - is_dir: true, - size: 0, - cluster: root_cl, - }), + NodeData::Dir(Vec::new()), ); + for vol in 0..disks { + let uid = String::from(crate::fat::uid(vol)); + let root_cl = crate::fat::root_cluster(vol); + add( + &uid, + Some(disk_dir), + FileType::Dir, + NodeData::Disk(DiskInfo { + vol, + parent: root_cl, + name: String::new(), + is_dir: true, + size: 0, + cluster: root_cl, + }), + ); + } } let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count(); @@ -217,14 +229,15 @@ pub fn resolve(path: &str) -> Option { return None; } let mut cur = 0usize; // root - // (cluster, parent, name) of directories descended into under /disk. - let mut dstack: Vec<(u32, u32, String)> = Vec::new(); + // (vol, cluster, parent, name) of directories descended into under /disk. + let mut dstack: Vec<(usize, u32, u32, String)> = Vec::new(); for seg in path.split('/') { match seg { "" | "." => {} ".." => { - if let Some((cl, p, n)) = dstack.pop() { + if let Some((vol, cl, p, n)) = dstack.pop() { cur = materialize_disk(DiskInfo { + vol, parent: p, name: n, is_dir: true, @@ -236,13 +249,14 @@ pub fn resolve(path: &str) -> Option { } } name => { - let disk_dir: Option = match &nodes()[cur].data { - NodeData::Disk(info) => Some(info.cluster), + let disk_ctx: Option<(usize, u32)> = match &nodes()[cur].data { + NodeData::Disk(info) => Some((info.vol, info.cluster)), _ => None, }; - if let Some(dir_cluster) = disk_dir { - let e = crate::fat::find(dir_cluster, name).ok()?; + if let Some((vol, dir_cluster)) = disk_ctx { + let e = crate::fat::find(vol, dir_cluster, name).ok()?; let d = DiskInfo { + vol, parent: dir_cluster, name: e.name.clone(), is_dir: e.is_dir, @@ -250,7 +264,7 @@ pub fn resolve(path: &str) -> Option { cluster: e.cluster, }; if e.is_dir { - dstack.push((e.cluster, dir_cluster, e.name)); + dstack.push((vol, e.cluster, dir_cluster, e.name)); } cur = materialize_disk(d); } else { @@ -279,7 +293,7 @@ pub fn size(h: usize) -> u64 { pub fn children(h: usize) -> Vec<(String, FileType, u64)> { if let NodeData::Disk(i) = &nodes()[h].data { let cl = i.cluster; - return crate::fat::list_dir(cl) + return crate::fat::list_dir(i.vol, cl) .map(|es| { es.into_iter() .map(|e| { @@ -357,7 +371,7 @@ pub fn read(h: usize, buf: &mut [u8], pos: u64) -> Result { buf[..n].copy_from_slice(&bytes[p..p + n]); Ok(n) } - NodeData::Disk(i) => crate::fat::read_file(i.parent, &i.name, buf, pos), + NodeData::Disk(i) => crate::fat::read_file(i.vol, i.parent, &i.name, buf, pos), NodeData::Dir(_) => Err(()), } } @@ -394,7 +408,7 @@ pub fn write(h: usize, buf: &[u8], pos: u64) -> Result { } _ => Err(()), // read-only devices }, - NodeData::Disk(i) => crate::fat::write_file(i.parent, &i.name, buf, pos), + NodeData::Disk(i) => crate::fat::write_file(i.vol, i.parent, &i.name, buf, pos), NodeData::Dir(_) => Err(()), } } @@ -406,7 +420,7 @@ pub fn set_len(h: usize, len: u64) -> Result<(), ()> { v.resize(len as usize, 0); Ok(()) } - NodeData::Disk(i) => crate::fat::set_len(i.parent, &i.name, len), + NodeData::Disk(i) => crate::fat::set_len(i.vol, i.parent, &i.name, len), _ => Err(()), } } @@ -417,22 +431,23 @@ pub fn create(parent: usize, name: &str, kind: FileType) -> Result { return Err(()); } // Disk directory: create through the FAT volume. - let disk_parent: Option = match &nodes()[parent].data { + let disk_parent: Option<(usize, u32)> = match &nodes()[parent].data { NodeData::Disk(i) => { if !i.is_dir { return Err(()); } - Some(i.cluster) + Some((i.vol, i.cluster)) } _ => None, }; - if let Some(p) = disk_parent { - if crate::fat::find(p, name).is_ok() { + if let Some((vol, p)) = disk_parent { + if crate::fat::find(vol, p, name).is_ok() { return Err(()); } - crate::fat::create(p, name, kind == FileType::Dir)?; - let e = crate::fat::find(p, name)?; + crate::fat::create(vol, p, name, kind == FileType::Dir)?; + let e = crate::fat::find(vol, p, name)?; return Ok(materialize_disk(DiskInfo { + vol, parent: p, name: e.name, is_dir: e.is_dir, @@ -458,9 +473,10 @@ pub fn remove(h: usize) -> Result<(), ()> { if i.name.is_empty() { return Err(()); // the /disk mount itself } + let vol = i.vol; let p = i.parent; let n = i.name.clone(); - return crate::fat::remove(p, &n); + return crate::fat::remove(vol, p, &n); } let parent = match nodes()[h].parent { Some(p) => p, -- cgit v1.2.3