diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-01 02:19:07 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-01 02:19:07 +0200 |
| commit | 92ed4f6668efccf6af73f45654405d333e6a318c (patch) | |
| tree | bb9b651dc36edcc90ae0289d7c4223d983454440 /rsnix/src/fs.rs | |
| parent | scripts: automate the data-disk workflow (mkdisk / sendkeys / test-disk) (diff) | |
| download | rs-dos-92ed4f6668efccf6af73f45654405d333e6a318c.tar.gz rs-dos-92ed4f6668efccf6af73f45654405d333e6a318c.zip | |
fat: mount every FAT32 volume under /disk/<UID>/, fix multi-entry LFN
- fat.rs: VOL: Option<Volume> → VOLS: Vec<Volume>; 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)
Diffstat (limited to '')
| -rw-r--r-- | rsnix/src/fs.rs | 76 |
1 files changed, 46 insertions, 30 deletions
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 { | |||
| 59 | /// op, so it stays correct as the volume changes), `cluster` the entry's | 59 | /// op, so it stays correct as the volume changes), `cluster` the entry's |
| 60 | /// own first cluster. | 60 | /// own first cluster. |
| 61 | struct DiskInfo { | 61 | struct DiskInfo { |
| 62 | vol: usize, // index into fat::VOLS (which FAT32 volume this lives on) | ||
| 62 | parent: u32, | 63 | parent: u32, |
| 63 | name: String, | 64 | name: String, |
| 64 | is_dir: bool, | 65 | is_dir: bool, |
| @@ -174,21 +175,32 @@ pub fn init() -> (usize, usize) { | |||
| 174 | ), | 175 | ), |
| 175 | ); | 176 | ); |
| 176 | 177 | ||
| 177 | // Mount the FAT32 volume at /disk when one is present. | 178 | // Mount every FAT32 volume under /disk/<UID>/ (UID = label-serial). |
| 178 | if crate::fat::is_mounted() { | 179 | let disks = crate::fat::volume_count(); |
| 179 | let root_cl = crate::fat::root_cluster(); | 180 | if disks > 0 { |
| 180 | add( | 181 | let disk_dir = add( |
| 181 | "disk", | 182 | "disk", |
| 182 | Some(root), | 183 | Some(root), |
| 183 | FileType::Dir, | 184 | FileType::Dir, |
| 184 | NodeData::Disk(DiskInfo { | 185 | NodeData::Dir(Vec::new()), |
| 185 | parent: root_cl, | ||
| 186 | name: String::new(), | ||
| 187 | is_dir: true, | ||
| 188 | size: 0, | ||
| 189 | cluster: root_cl, | ||
| 190 | }), | ||
| 191 | ); | 186 | ); |
| 187 | for vol in 0..disks { | ||
| 188 | let uid = String::from(crate::fat::uid(vol)); | ||
| 189 | let root_cl = crate::fat::root_cluster(vol); | ||
| 190 | add( | ||
| 191 | &uid, | ||
| 192 | Some(disk_dir), | ||
| 193 | FileType::Dir, | ||
| 194 | NodeData::Disk(DiskInfo { | ||
| 195 | vol, | ||
| 196 | parent: root_cl, | ||
| 197 | name: String::new(), | ||
| 198 | is_dir: true, | ||
| 199 | size: 0, | ||
| 200 | cluster: root_cl, | ||
| 201 | }), | ||
| 202 | ); | ||
| 203 | } | ||
| 192 | } | 204 | } |
| 193 | 205 | ||
| 194 | let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count(); | 206 | let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count(); |
| @@ -217,14 +229,15 @@ pub fn resolve(path: &str) -> Option<usize> { | |||
| 217 | return None; | 229 | return None; |
| 218 | } | 230 | } |
| 219 | let mut cur = 0usize; // root | 231 | let mut cur = 0usize; // root |
| 220 | // (cluster, parent, name) of directories descended into under /disk. | 232 | // (vol, cluster, parent, name) of directories descended into under /disk. |
| 221 | let mut dstack: Vec<(u32, u32, String)> = Vec::new(); | 233 | let mut dstack: Vec<(usize, u32, u32, String)> = Vec::new(); |
| 222 | for seg in path.split('/') { | 234 | for seg in path.split('/') { |
| 223 | match seg { | 235 | match seg { |
| 224 | "" | "." => {} | 236 | "" | "." => {} |
| 225 | ".." => { | 237 | ".." => { |
| 226 | if let Some((cl, p, n)) = dstack.pop() { | 238 | if let Some((vol, cl, p, n)) = dstack.pop() { |
| 227 | cur = materialize_disk(DiskInfo { | 239 | cur = materialize_disk(DiskInfo { |
| 240 | vol, | ||
| 228 | parent: p, | 241 | parent: p, |
| 229 | name: n, | 242 | name: n, |
| 230 | is_dir: true, | 243 | is_dir: true, |
| @@ -236,13 +249,14 @@ pub fn resolve(path: &str) -> Option<usize> { | |||
| 236 | } | 249 | } |
| 237 | } | 250 | } |
| 238 | name => { | 251 | name => { |
| 239 | let disk_dir: Option<u32> = match &nodes()[cur].data { | 252 | let disk_ctx: Option<(usize, u32)> = match &nodes()[cur].data { |
| 240 | NodeData::Disk(info) => Some(info.cluster), | 253 | NodeData::Disk(info) => Some((info.vol, info.cluster)), |
| 241 | _ => None, | 254 | _ => None, |
| 242 | }; | 255 | }; |
| 243 | if let Some(dir_cluster) = disk_dir { | 256 | if let Some((vol, dir_cluster)) = disk_ctx { |
| 244 | let e = crate::fat::find(dir_cluster, name).ok()?; | 257 | let e = crate::fat::find(vol, dir_cluster, name).ok()?; |
| 245 | let d = DiskInfo { | 258 | let d = DiskInfo { |
| 259 | vol, | ||
| 246 | parent: dir_cluster, | 260 | parent: dir_cluster, |
| 247 | name: e.name.clone(), | 261 | name: e.name.clone(), |
| 248 | is_dir: e.is_dir, | 262 | is_dir: e.is_dir, |
| @@ -250,7 +264,7 @@ pub fn resolve(path: &str) -> Option<usize> { | |||
| 250 | cluster: e.cluster, | 264 | cluster: e.cluster, |
| 251 | }; | 265 | }; |
| 252 | if e.is_dir { | 266 | if e.is_dir { |
| 253 | dstack.push((e.cluster, dir_cluster, e.name)); | 267 | dstack.push((vol, e.cluster, dir_cluster, e.name)); |
| 254 | } | 268 | } |
| 255 | cur = materialize_disk(d); | 269 | cur = materialize_disk(d); |
| 256 | } else { | 270 | } else { |
| @@ -279,7 +293,7 @@ pub fn size(h: usize) -> u64 { | |||
| 279 | pub fn children(h: usize) -> Vec<(String, FileType, u64)> { | 293 | pub fn children(h: usize) -> Vec<(String, FileType, u64)> { |
| 280 | if let NodeData::Disk(i) = &nodes()[h].data { | 294 | if let NodeData::Disk(i) = &nodes()[h].data { |
| 281 | let cl = i.cluster; | 295 | let cl = i.cluster; |
| 282 | return crate::fat::list_dir(cl) | 296 | return crate::fat::list_dir(i.vol, cl) |
| 283 | .map(|es| { | 297 | .map(|es| { |
| 284 | es.into_iter() | 298 | es.into_iter() |
| 285 | .map(|e| { | 299 | .map(|e| { |
| @@ -357,7 +371,7 @@ pub fn read(h: usize, buf: &mut [u8], pos: u64) -> Result<usize, ()> { | |||
| 357 | buf[..n].copy_from_slice(&bytes[p..p + n]); | 371 | buf[..n].copy_from_slice(&bytes[p..p + n]); |
| 358 | Ok(n) | 372 | Ok(n) |
| 359 | } | 373 | } |
| 360 | NodeData::Disk(i) => crate::fat::read_file(i.parent, &i.name, buf, pos), | 374 | NodeData::Disk(i) => crate::fat::read_file(i.vol, i.parent, &i.name, buf, pos), |
| 361 | NodeData::Dir(_) => Err(()), | 375 | NodeData::Dir(_) => Err(()), |
| 362 | } | 376 | } |
| 363 | } | 377 | } |
| @@ -394,7 +408,7 @@ pub fn write(h: usize, buf: &[u8], pos: u64) -> Result<usize, ()> { | |||
| 394 | } | 408 | } |
| 395 | _ => Err(()), // read-only devices | 409 | _ => Err(()), // read-only devices |
| 396 | }, | 410 | }, |
| 397 | NodeData::Disk(i) => crate::fat::write_file(i.parent, &i.name, buf, pos), | 411 | NodeData::Disk(i) => crate::fat::write_file(i.vol, i.parent, &i.name, buf, pos), |
| 398 | NodeData::Dir(_) => Err(()), | 412 | NodeData::Dir(_) => Err(()), |
| 399 | } | 413 | } |
| 400 | } | 414 | } |
| @@ -406,7 +420,7 @@ pub fn set_len(h: usize, len: u64) -> Result<(), ()> { | |||
| 406 | v.resize(len as usize, 0); | 420 | v.resize(len as usize, 0); |
| 407 | Ok(()) | 421 | Ok(()) |
| 408 | } | 422 | } |
| 409 | NodeData::Disk(i) => crate::fat::set_len(i.parent, &i.name, len), | 423 | NodeData::Disk(i) => crate::fat::set_len(i.vol, i.parent, &i.name, len), |
| 410 | _ => Err(()), | 424 | _ => Err(()), |
| 411 | } | 425 | } |
| 412 | } | 426 | } |
| @@ -417,22 +431,23 @@ pub fn create(parent: usize, name: &str, kind: FileType) -> Result<usize, ()> { | |||
| 417 | return Err(()); | 431 | return Err(()); |
| 418 | } | 432 | } |
| 419 | // Disk directory: create through the FAT volume. | 433 | // Disk directory: create through the FAT volume. |
| 420 | let disk_parent: Option<u32> = match &nodes()[parent].data { | 434 | let disk_parent: Option<(usize, u32)> = match &nodes()[parent].data { |
| 421 | NodeData::Disk(i) => { | 435 | NodeData::Disk(i) => { |
| 422 | if !i.is_dir { | 436 | if !i.is_dir { |
| 423 | return Err(()); | 437 | return Err(()); |
| 424 | } | 438 | } |
| 425 | Some(i.cluster) | 439 | Some((i.vol, i.cluster)) |
| 426 | } | 440 | } |
| 427 | _ => None, | 441 | _ => None, |
| 428 | }; | 442 | }; |
| 429 | if let Some(p) = disk_parent { | 443 | if let Some((vol, p)) = disk_parent { |
| 430 | if crate::fat::find(p, name).is_ok() { | 444 | if crate::fat::find(vol, p, name).is_ok() { |
| 431 | return Err(()); | 445 | return Err(()); |
| 432 | } | 446 | } |
| 433 | crate::fat::create(p, name, kind == FileType::Dir)?; | 447 | crate::fat::create(vol, p, name, kind == FileType::Dir)?; |
| 434 | let e = crate::fat::find(p, name)?; | 448 | let e = crate::fat::find(vol, p, name)?; |
| 435 | return Ok(materialize_disk(DiskInfo { | 449 | return Ok(materialize_disk(DiskInfo { |
| 450 | vol, | ||
| 436 | parent: p, | 451 | parent: p, |
| 437 | name: e.name, | 452 | name: e.name, |
| 438 | is_dir: e.is_dir, | 453 | is_dir: e.is_dir, |
| @@ -458,9 +473,10 @@ pub fn remove(h: usize) -> Result<(), ()> { | |||
| 458 | if i.name.is_empty() { | 473 | if i.name.is_empty() { |
| 459 | return Err(()); // the /disk mount itself | 474 | return Err(()); // the /disk mount itself |
| 460 | } | 475 | } |
| 476 | let vol = i.vol; | ||
| 461 | let p = i.parent; | 477 | let p = i.parent; |
| 462 | let n = i.name.clone(); | 478 | let n = i.name.clone(); |
| 463 | return crate::fat::remove(p, &n); | 479 | return crate::fat::remove(vol, p, &n); |
| 464 | } | 480 | } |
| 465 | let parent = match nodes()[h].parent { | 481 | let parent = match nodes()[h].parent { |
| 466 | Some(p) => p, | 482 | Some(p) => p, |
