summaryrefslogtreecommitdiffstats
path: root/rsnix/src/fs.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/fs.rs164
1 files changed, 160 insertions, 4 deletions
diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs
index df9308b..6950171 100644
--- a/rsnix/src/fs.rs
+++ b/rsnix/src/fs.rs
@@ -51,6 +51,19 @@ enum NodeData {
51 Regular(Vec<u8>), 51 Regular(Vec<u8>),
52 Dir(Vec<usize>), 52 Dir(Vec<usize>),
53 Char(CharDevice), 53 Char(CharDevice),
54 Disk(DiskInfo),
55}
56
57/// A node backed by the FAT32 volume (`/disk` mount). `parent` is the
58/// directory cluster holding the entry (used to re-locate it on every
59/// op, so it stays correct as the volume changes), `cluster` the entry's
60/// own first cluster.
61struct DiskInfo {
62 parent: u32,
63 name: String,
64 is_dir: bool,
65 size: u64,
66 cluster: u32,
54} 67}
55 68
56struct Node { 69struct Node {
@@ -161,6 +174,23 @@ pub fn init() -> (usize, usize) {
161 ), 174 ),
162 ); 175 );
163 176
177 // Mount the FAT32 volume at /disk when one is present.
178 if crate::fat::is_mounted() {
179 let root_cl = crate::fat::root_cluster();
180 add(
181 "disk",
182 Some(root),
183 FileType::Dir,
184 NodeData::Disk(DiskInfo {
185 parent: root_cl,
186 name: String::new(),
187 is_dir: true,
188 size: 0,
189 cluster: root_cl,
190 }),
191 );
192 }
193
164 let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count(); 194 let dirs = nodes().iter().filter(|n| n.kind == FileType::Dir).count();
165 let files = nodes().len() - dirs; 195 let files = nodes().len() - dirs;
166 (dirs, files) 196 (dirs, files)
@@ -180,22 +210,52 @@ fn lookup(dir: usize, name: &str) -> Option<usize> {
180 None 210 None
181} 211}
182 212
183/// Resolve an absolute path (supports `.` and `..`). 213/// Resolve an absolute path (supports `.` and `..`). Paths under
214/// `/disk` walk the mounted FAT32 volume through ephemeral Disk nodes.
184pub fn resolve(path: &str) -> Option<usize> { 215pub fn resolve(path: &str) -> Option<usize> {
185 if nodes().is_empty() { 216 if nodes().is_empty() {
186 return None; 217 return None;
187 } 218 }
188 let mut cur = 0usize; // root 219 let mut cur = 0usize; // root
220 // (cluster, parent, name) of directories descended into under /disk.
221 let mut dstack: Vec<(u32, u32, String)> = Vec::new();
189 for seg in path.split('/') { 222 for seg in path.split('/') {
190 match seg { 223 match seg {
191 "" | "." => {} 224 "" | "." => {}
192 ".." => { 225 ".." => {
193 if let Some(p) = nodes()[cur].parent { 226 if let Some((cl, p, n)) = dstack.pop() {
227 cur = materialize_disk(DiskInfo {
228 parent: p,
229 name: n,
230 is_dir: true,
231 size: 0,
232 cluster: cl,
233 });
234 } else if let Some(p) = nodes()[cur].parent {
194 cur = p; 235 cur = p;
195 } 236 }
196 } 237 }
197 name => { 238 name => {
198 cur = lookup(cur, name)?; 239 let disk_dir: Option<u32> = match &nodes()[cur].data {
240 NodeData::Disk(info) => Some(info.cluster),
241 _ => None,
242 };
243 if let Some(dir_cluster) = disk_dir {
244 let e = crate::fat::find(dir_cluster, name).ok()?;
245 let d = DiskInfo {
246 parent: dir_cluster,
247 name: e.name.clone(),
248 is_dir: e.is_dir,
249 size: e.size,
250 cluster: e.cluster,
251 };
252 if e.is_dir {
253 dstack.push((e.cluster, dir_cluster, e.name));
254 }
255 cur = materialize_disk(d);
256 } else {
257 cur = lookup(cur, name)?;
258 }
199 } 259 }
200 } 260 }
201 } 261 }
@@ -209,12 +269,34 @@ pub fn kind(h: usize) -> FileType {
209pub fn size(h: usize) -> u64 { 269pub fn size(h: usize) -> u64 {
210 match &nodes()[h].data { 270 match &nodes()[h].data {
211 NodeData::Regular(v) => v.len() as u64, 271 NodeData::Regular(v) => v.len() as u64,
272 NodeData::Disk(i) => i.size,
212 _ => 0, 273 _ => 0,
213 } 274 }
214} 275}
215 276
216/// Entries of a directory as (name, kind, size) — tombstoned nodes skipped. 277/// Entries of a directory as (name, kind, size) — tombstoned nodes
278/// skipped; disk directories are listed fresh from the FAT volume.
217pub fn children(h: usize) -> Vec<(String, FileType, u64)> { 279pub fn children(h: usize) -> Vec<(String, FileType, u64)> {
280 if let NodeData::Disk(i) = &nodes()[h].data {
281 let cl = i.cluster;
282 return crate::fat::list_dir(cl)
283 .map(|es| {
284 es.into_iter()
285 .map(|e| {
286 (
287 e.name,
288 if e.is_dir {
289 FileType::Dir
290 } else {
291 FileType::Regular
292 },
293 e.size,
294 )
295 })
296 .collect()
297 })
298 .unwrap_or_default();
299 }
218 let kids = match &nodes()[h].data { 300 let kids = match &nodes()[h].data {
219 NodeData::Dir(v) => v.clone(), 301 NodeData::Dir(v) => v.clone(),
220 _ => return Vec::new(), 302 _ => return Vec::new(),
@@ -275,6 +357,7 @@ pub fn read(h: usize, buf: &mut [u8], pos: u64) -> Result<usize, ()> {
275 buf[..n].copy_from_slice(&bytes[p..p + n]); 357 buf[..n].copy_from_slice(&bytes[p..p + n]);
276 Ok(n) 358 Ok(n)
277 } 359 }
360 NodeData::Disk(i) => crate::fat::read_file(i.parent, &i.name, buf, pos),
278 NodeData::Dir(_) => Err(()), 361 NodeData::Dir(_) => Err(()),
279 } 362 }
280} 363}
@@ -311,6 +394,7 @@ pub fn write(h: usize, buf: &[u8], pos: u64) -> Result<usize, ()> {
311 } 394 }
312 _ => Err(()), // read-only devices 395 _ => Err(()), // read-only devices
313 }, 396 },
397 NodeData::Disk(i) => crate::fat::write_file(i.parent, &i.name, buf, pos),
314 NodeData::Dir(_) => Err(()), 398 NodeData::Dir(_) => Err(()),
315 } 399 }
316} 400}
@@ -322,6 +406,7 @@ pub fn set_len(h: usize, len: u64) -> Result<(), ()> {
322 v.resize(len as usize, 0); 406 v.resize(len as usize, 0);
323 Ok(()) 407 Ok(())
324 } 408 }
409 NodeData::Disk(i) => crate::fat::set_len(i.parent, &i.name, len),
325 _ => Err(()), 410 _ => Err(()),
326 } 411 }
327} 412}
@@ -331,6 +416,30 @@ pub fn create(parent: usize, name: &str, kind: FileType) -> Result<usize, ()> {
331 if name.is_empty() || name.contains('/') || kind == FileType::Char { 416 if name.is_empty() || name.contains('/') || kind == FileType::Char {
332 return Err(()); 417 return Err(());
333 } 418 }
419 // Disk directory: create through the FAT volume.
420 let disk_parent: Option<u32> = match &nodes()[parent].data {
421 NodeData::Disk(i) => {
422 if !i.is_dir {
423 return Err(());
424 }
425 Some(i.cluster)
426 }
427 _ => None,
428 };
429 if let Some(p) = disk_parent {
430 if crate::fat::find(p, name).is_ok() {
431 return Err(());
432 }
433 crate::fat::create(p, name, kind == FileType::Dir)?;
434 let e = crate::fat::find(p, name)?;
435 return Ok(materialize_disk(DiskInfo {
436 parent: p,
437 name: e.name,
438 is_dir: e.is_dir,
439 size: e.size,
440 cluster: e.cluster,
441 }));
442 }
334 if lookup(parent, name).is_some() { 443 if lookup(parent, name).is_some() {
335 return Err(()); 444 return Err(());
336 } 445 }
@@ -344,6 +453,15 @@ pub fn create(parent: usize, name: &str, kind: FileType) -> Result<usize, ()> {
344 453
345/// Remove a regular file or an empty directory (tombstone). 454/// Remove a regular file or an empty directory (tombstone).
346pub fn remove(h: usize) -> Result<(), ()> { 455pub fn remove(h: usize) -> Result<(), ()> {
456 // Disk nodes: delete through the FAT volume (dirs must be empty).
457 if let NodeData::Disk(i) = &nodes()[h].data {
458 if i.name.is_empty() {
459 return Err(()); // the /disk mount itself
460 }
461 let p = i.parent;
462 let n = i.name.clone();
463 return crate::fat::remove(p, &n);
464 }
347 let parent = match nodes()[h].parent { 465 let parent = match nodes()[h].parent {
348 Some(p) => p, 466 Some(p) => p,
349 None => return Err(()), 467 None => return Err(()),
@@ -414,6 +532,44 @@ pub fn remove_path(path: &str) -> Result<(), ()> {
414 remove(h) 532 remove(h)
415} 533}
416 534
535// --- disk mount ------------------------------------------------------------
536
537/// Round-robin pool of ephemeral disk nodes so a long-lived shell doesn't
538/// grow the node table forever.
539const DISK_SLOTS: usize = 24;
540static mut DISK_POOL: *mut Vec<usize> = core::ptr::null_mut();
541static mut DISK_ROBIN: usize = 0;
542
543fn materialize_disk(info: DiskInfo) -> usize {
544 unsafe {
545 if DISK_POOL.is_null() {
546 DISK_POOL = Box::into_raw(Box::new(Vec::new()));
547 }
548 let pool = &mut *DISK_POOL;
549 let idx = DISK_ROBIN % DISK_SLOTS;
550 DISK_ROBIN = DISK_ROBIN.wrapping_add(1);
551 let kind = if info.is_dir {
552 FileType::Dir
553 } else {
554 FileType::Regular
555 };
556 if idx < pool.len() {
557 let h = pool[idx];
558 let n = &mut nodes()[h];
559 n.name = info.name.clone();
560 n.kind = kind;
561 n.parent = None;
562 n.data = NodeData::Disk(info);
563 n.removed = false;
564 return h;
565 }
566 let name = info.name.clone();
567 let h = add(&name, None, kind, NodeData::Disk(info));
568 pool.push(h);
569 h
570 }
571}
572
417// --- character devices ---------------------------------------------------- 573// --- character devices ----------------------------------------------------
418 574
419fn version_text() -> String { 575fn version_text() -> String {