1extern crate alloc;
40
41use alloc::collections::BTreeMap;
42use alloc::string::String;
43use alloc::vec;
44use alloc::vec::Vec;
45use core::cmp;
46
47use grafos_std::block::{BlockLease, BLOCK_SIZE};
48use grafos_std::error::{FabricError, Result};
49use serde::{Deserialize, Serialize};
50
51const SUPERBLOCK_MAGIC: [u8; 4] = *b"GFFS";
52const SUPERBLOCK_VERSION: u32 = 1;
53const INODE_SIZE: usize = 256;
54const INODES_PER_BLOCK: usize = BLOCK_SIZE / INODE_SIZE;
55const MAX_DIRECT_BLOCKS: usize = 12;
56const ROOT_INODE_ID: u64 = 0;
57const MAX_NAME_LEN: usize = 255;
58
59const DEFAULT_STRIPE_THRESHOLD: u64 = 1024 * 1024; const DEFAULT_STRIPE_SIZE: u32 = 256; const FILE_TYPE_FILE: u8 = 1;
65const FILE_TYPE_DIR: u8 = 2;
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
71struct Superblock {
72 magic: [u8; 4],
73 version: u32,
74 block_size: u32,
75 total_blocks: u64,
76 inode_table_start: u64,
77 data_start: u64,
78 free_bitmap_start: u64,
79 next_inode_id: u64,
80 inode_table_blocks: u64,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85struct Inode {
86 inode_id: u64,
87 file_type: u8,
88 size: u64,
89 block_count: u32,
90 direct_blocks: [u64; MAX_DIRECT_BLOCKS],
91 indirect_block: u64,
92 created_at: u64,
93 modified_at: u64,
94 name_hash: u64,
95}
96
97impl Inode {
98 fn new(inode_id: u64, file_type: u8) -> Self {
99 Inode {
100 inode_id,
101 file_type,
102 size: 0,
103 block_count: 0,
104 direct_blocks: [0; MAX_DIRECT_BLOCKS],
105 indirect_block: 0,
106 created_at: 0,
107 modified_at: 0,
108 name_hash: 0,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct DirEntry {
116 pub inode_id: u64,
118 pub name: String,
120 pub file_type: u8,
122}
123
124#[derive(Debug, Clone)]
126pub struct FileStat {
127 pub size: u64,
129 pub file_type: u8,
131 pub block_count: u32,
133 pub created_at: u64,
135 pub modified_at: u64,
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum OpenFlags {
142 Read,
144 Write,
146 ReadWrite,
148}
149
150#[derive(Debug, Clone, Copy)]
152pub enum SeekFrom {
153 Start(u64),
155 Current(i64),
157 End(i64),
159}
160
161#[derive(Debug)]
163pub struct FileHandle {
164 inode_id: u64,
165 position: u64,
166 flags: OpenFlags,
167}
168
169pub struct FabricFs {
175 leases: Vec<BlockLease>,
176 superblock: Superblock,
177 inode_cache: BTreeMap<u64, Inode>,
179 dir_cache: BTreeMap<u64, Vec<DirEntry>>,
180 dirty_inodes: BTreeMap<u64, bool>,
181 dirty_dirs: BTreeMap<u64, bool>,
182 stripe_threshold: u64,
184 stripe_size: u32,
185 bitmap_cache: Vec<u8>,
187 bitmap_dirty: bool,
188}
189
190impl FabricFs {
191 pub fn format(leases: Vec<BlockLease>) -> Result<Self> {
196 if leases.is_empty() {
197 return Err(FabricError::IoError(-1));
198 }
199
200 let total_blocks = leases[0].block().num_blocks();
201 if total_blocks < 16 {
202 return Err(FabricError::CapacityExceeded);
203 }
204
205 let bitmap_blocks = total_blocks.div_ceil(BLOCK_SIZE as u64 * 8);
207 let free_bitmap_start = 1u64;
208 let inode_table_start = free_bitmap_start + bitmap_blocks;
209 let inode_table_blocks = cmp::max(4, total_blocks / 64);
210 let data_start = inode_table_start + inode_table_blocks;
211
212 if data_start >= total_blocks {
213 return Err(FabricError::CapacityExceeded);
214 }
215
216 let superblock = Superblock {
217 magic: SUPERBLOCK_MAGIC,
218 version: SUPERBLOCK_VERSION,
219 block_size: BLOCK_SIZE as u32,
220 total_blocks,
221 inode_table_start,
222 data_start,
223 free_bitmap_start,
224 next_inode_id: 1,
225 inode_table_blocks,
226 };
227
228 let sb_bytes = Self::serialize_superblock(&superblock);
230 leases[0].block().write_block(0, &sb_bytes)?;
231
232 let bitmap_size = (total_blocks as usize).div_ceil(8);
234 let mut bitmap = vec![0u8; bitmap_size];
235 for blk in 0..data_start as usize {
236 bitmap[blk / 8] |= 1 << (blk % 8);
237 }
238
239 let bitmap_block_count = bitmap_blocks as usize;
241 for i in 0..bitmap_block_count {
242 let mut block = [0u8; BLOCK_SIZE];
243 let start = i * BLOCK_SIZE;
244 let end = cmp::min(start + BLOCK_SIZE, bitmap.len());
245 if start < end {
246 block[..end - start].copy_from_slice(&bitmap[start..end]);
247 }
248 leases[0]
249 .block()
250 .write_block(free_bitmap_start + i as u64, &block)?;
251 }
252
253 let zero_block = [0u8; BLOCK_SIZE];
255 for i in 0..inode_table_blocks {
256 leases[0]
257 .block()
258 .write_block(inode_table_start + i, &zero_block)?;
259 }
260
261 let root_inode = Inode::new(ROOT_INODE_ID, FILE_TYPE_DIR);
263
264 let mut fs = FabricFs {
265 leases,
266 superblock,
267 inode_cache: BTreeMap::new(),
268 dir_cache: BTreeMap::new(),
269 dirty_inodes: BTreeMap::new(),
270 dirty_dirs: BTreeMap::new(),
271 stripe_threshold: DEFAULT_STRIPE_THRESHOLD,
272 stripe_size: DEFAULT_STRIPE_SIZE,
273 bitmap_cache: bitmap,
274 bitmap_dirty: false,
275 };
276
277 fs.inode_cache.insert(ROOT_INODE_ID, root_inode);
278 fs.dirty_inodes.insert(ROOT_INODE_ID, true);
279 fs.dir_cache.insert(ROOT_INODE_ID, Vec::new());
280 fs.dirty_dirs.insert(ROOT_INODE_ID, true);
281 fs.sync()?;
282
283 Ok(fs)
284 }
285
286 pub fn mount(leases: Vec<BlockLease>) -> Result<Self> {
291 if leases.is_empty() {
292 return Err(FabricError::IoError(-1));
293 }
294
295 let sb_block = leases[0].block().read_block(0)?;
296 let superblock = Self::deserialize_superblock(&sb_block)?;
297
298 if superblock.magic != SUPERBLOCK_MAGIC {
299 return Err(FabricError::IoError(-2));
300 }
301 if superblock.version != SUPERBLOCK_VERSION {
302 return Err(FabricError::IoError(-3));
303 }
304
305 let bitmap_blocks = superblock.total_blocks.div_ceil(BLOCK_SIZE as u64 * 8);
307 let bitmap_size = (superblock.total_blocks as usize).div_ceil(8);
308 let mut bitmap = vec![0u8; bitmap_size];
309 for i in 0..bitmap_blocks as usize {
310 let block = leases[0]
311 .block()
312 .read_block(superblock.free_bitmap_start + i as u64)?;
313 let start = i * BLOCK_SIZE;
314 let end = cmp::min(start + BLOCK_SIZE, bitmap_size);
315 if start < end {
316 bitmap[start..end].copy_from_slice(&block[..end - start]);
317 }
318 }
319
320 let mut fs = FabricFs {
321 leases,
322 superblock,
323 inode_cache: BTreeMap::new(),
324 dir_cache: BTreeMap::new(),
325 dirty_inodes: BTreeMap::new(),
326 dirty_dirs: BTreeMap::new(),
327 stripe_threshold: DEFAULT_STRIPE_THRESHOLD,
328 stripe_size: DEFAULT_STRIPE_SIZE,
329 bitmap_cache: bitmap,
330 bitmap_dirty: false,
331 };
332
333 let root_inode = fs.read_inode_from_disk(ROOT_INODE_ID)?;
335 fs.inode_cache.insert(ROOT_INODE_ID, root_inode);
336
337 let entries = fs.read_dir_entries(ROOT_INODE_ID)?;
339 for entry in &entries {
341 if !fs.inode_cache.contains_key(&entry.inode_id) {
342 if let Ok(inode) = fs.read_inode_from_disk(entry.inode_id) {
343 fs.inode_cache.insert(entry.inode_id, inode);
344 }
345 }
346 }
347 fs.dir_cache.insert(ROOT_INODE_ID, entries);
348
349 Ok(fs)
350 }
351
352 pub fn create(&mut self, path: &str) -> Result<FileHandle> {
354 let (parent_inode_id, name) = self.resolve_parent(path)?;
355 if name.is_empty() || name.len() > MAX_NAME_LEN {
356 return Err(FabricError::IoError(-1));
357 }
358
359 let existing_inode = self
361 .dir_cache
362 .get(&parent_inode_id)
363 .and_then(|entries| entries.iter().find(|e| e.name == name))
364 .map(|e| e.inode_id);
365
366 if let Some(inode_id) = existing_inode {
367 self.truncate_inode(inode_id)?;
369 return Ok(FileHandle {
370 inode_id,
371 position: 0,
372 flags: OpenFlags::Write,
373 });
374 }
375
376 let inode_id = self.superblock.next_inode_id;
378 self.superblock.next_inode_id += 1;
379
380 let mut inode = Inode::new(inode_id, FILE_TYPE_FILE);
381 inode.name_hash = fnv_hash(name.as_bytes());
382
383 self.inode_cache.insert(inode_id, inode);
384 self.dirty_inodes.insert(inode_id, true);
385
386 let dir_entry = DirEntry {
388 inode_id,
389 name,
390 file_type: FILE_TYPE_FILE,
391 };
392 let entries = self.dir_cache.entry(parent_inode_id).or_default();
393 entries.push(dir_entry);
394 self.dirty_dirs.insert(parent_inode_id, true);
395
396 self.write_superblock()?;
397
398 Ok(FileHandle {
399 inode_id,
400 position: 0,
401 flags: OpenFlags::Write,
402 })
403 }
404
405 pub fn open(&mut self, path: &str, flags: OpenFlags) -> Result<FileHandle> {
407 let inode_id = self.resolve_path(path)?;
408 let inode = self.get_inode(inode_id)?;
409 if inode.file_type != FILE_TYPE_FILE {
410 return Err(FabricError::IoError(-1));
411 }
412
413 if flags == OpenFlags::Write {
414 self.truncate_inode(inode_id)?;
415 }
416
417 Ok(FileHandle {
418 inode_id,
419 position: 0,
420 flags,
421 })
422 }
423
424 pub fn read(&self, handle: &FileHandle, buf: &mut [u8]) -> Result<usize> {
428 let inode = self
429 .inode_cache
430 .get(&handle.inode_id)
431 .ok_or(FabricError::IoError(-1))?;
432
433 if handle.position >= inode.size {
434 return Ok(0);
435 }
436
437 let remaining = (inode.size - handle.position) as usize;
438 let to_read = cmp::min(buf.len(), remaining);
439 let mut bytes_read = 0;
440 let mut pos = handle.position;
441
442 while bytes_read < to_read {
443 let block_index = (pos / BLOCK_SIZE as u64) as usize;
444 let offset_in_block = (pos % BLOCK_SIZE as u64) as usize;
445 let bytes_in_block = cmp::min(BLOCK_SIZE - offset_in_block, to_read - bytes_read);
446
447 let block_addr = self.get_data_block_addr(inode, block_index)?;
448 if block_addr == 0 {
449 for b in &mut buf[bytes_read..bytes_read + bytes_in_block] {
450 *b = 0;
451 }
452 } else {
453 let (lease_idx, lba) = decode_block_addr(block_addr);
454 let block_data = self.leases[lease_idx].block().read_block(lba)?;
455 buf[bytes_read..bytes_read + bytes_in_block].copy_from_slice(
456 &block_data[offset_in_block..offset_in_block + bytes_in_block],
457 );
458 }
459
460 bytes_read += bytes_in_block;
461 pos += bytes_in_block as u64;
462 }
463
464 Ok(bytes_read)
465 }
466
467 pub fn write(&mut self, handle: &mut FileHandle, data: &[u8]) -> Result<usize> {
471 if handle.flags == OpenFlags::Read {
472 return Err(FabricError::IoError(-1));
473 }
474
475 let mut bytes_written = 0;
476 let mut pos = handle.position;
477
478 while bytes_written < data.len() {
479 let block_index = (pos / BLOCK_SIZE as u64) as usize;
480 let offset_in_block = (pos % BLOCK_SIZE as u64) as usize;
481 let bytes_in_block = cmp::min(BLOCK_SIZE - offset_in_block, data.len() - bytes_written);
482
483 let block_addr = self.ensure_data_block(handle.inode_id, block_index)?;
484 let (lease_idx, lba) = decode_block_addr(block_addr);
485
486 let mut block_data = if offset_in_block != 0 || bytes_in_block != BLOCK_SIZE {
487 self.leases[lease_idx].block().read_block(lba)?
488 } else {
489 [0u8; BLOCK_SIZE]
490 };
491
492 block_data[offset_in_block..offset_in_block + bytes_in_block]
493 .copy_from_slice(&data[bytes_written..bytes_written + bytes_in_block]);
494
495 self.leases[lease_idx]
496 .block()
497 .write_block(lba, &block_data)?;
498
499 bytes_written += bytes_in_block;
500 pos += bytes_in_block as u64;
501 }
502
503 handle.position = pos;
504
505 let inode = self
507 .inode_cache
508 .get_mut(&handle.inode_id)
509 .ok_or(FabricError::IoError(-1))?;
510 if pos > inode.size {
511 inode.size = pos;
512 }
513 self.dirty_inodes.insert(handle.inode_id, true);
514
515 Ok(bytes_written)
516 }
517
518 pub fn seek(&mut self, handle: &mut FileHandle, pos: SeekFrom) -> Result<u64> {
520 let inode = self
521 .inode_cache
522 .get(&handle.inode_id)
523 .ok_or(FabricError::IoError(-1))?;
524
525 let new_pos = match pos {
526 SeekFrom::Start(offset) => offset,
527 SeekFrom::Current(offset) => {
528 if offset < 0 {
529 handle
530 .position
531 .checked_sub((-offset) as u64)
532 .ok_or(FabricError::IoError(-1))?
533 } else {
534 handle.position + offset as u64
535 }
536 }
537 SeekFrom::End(offset) => {
538 if offset < 0 {
539 inode
540 .size
541 .checked_sub((-offset) as u64)
542 .ok_or(FabricError::IoError(-1))?
543 } else {
544 inode.size + offset as u64
545 }
546 }
547 };
548
549 handle.position = new_pos;
550 Ok(new_pos)
551 }
552
553 pub fn close(&mut self, _handle: FileHandle) -> Result<()> {
555 self.sync()
556 }
557
558 pub fn remove(&mut self, path: &str) -> Result<()> {
560 let (parent_inode_id, name) = self.resolve_parent(path)?;
561
562 let entries = self
563 .dir_cache
564 .get(&parent_inode_id)
565 .cloned()
566 .unwrap_or_default();
567
568 let mut found_inode_id = None;
569 let mut found_idx = None;
570 for (i, entry) in entries.iter().enumerate() {
571 if entry.name == name {
572 if entry.file_type == FILE_TYPE_DIR {
573 let dir_entries = self.dir_cache.get(&entry.inode_id);
574 if let Some(de) = dir_entries {
575 if !de.is_empty() {
576 return Err(FabricError::IoError(-1));
577 }
578 }
579 }
580 found_inode_id = Some(entry.inode_id);
581 found_idx = Some(i);
582 break;
583 }
584 }
585
586 let inode_id = found_inode_id.ok_or(FabricError::IoError(-1))?;
587 let idx = found_idx.unwrap();
588
589 let blocks_to_free: Vec<u64> = if let Some(inode) = self.inode_cache.get(&inode_id) {
591 let mut blocks = Vec::new();
592 for i in 0..inode.block_count as usize {
593 if i < MAX_DIRECT_BLOCKS && inode.direct_blocks[i] != 0 {
594 blocks.push(inode.direct_blocks[i]);
595 }
596 }
597 if inode.indirect_block != 0 {
598 blocks.push(inode.indirect_block);
599 }
600 blocks
601 } else {
602 Vec::new()
603 };
604
605 for block_addr in blocks_to_free {
607 self.free_block(block_addr);
608 }
609
610 let entries = self.dir_cache.get_mut(&parent_inode_id).unwrap();
612 entries.remove(idx);
613 self.dirty_dirs.insert(parent_inode_id, true);
614
615 self.inode_cache.remove(&inode_id);
617 self.dir_cache.remove(&inode_id);
618
619 self.zero_inode_on_disk(inode_id)?;
620 self.sync()?;
621 Ok(())
622 }
623
624 pub fn readdir(&mut self, path: &str) -> Result<Vec<DirEntry>> {
626 let inode_id = self.resolve_path(path)?;
627 let inode = self.get_inode(inode_id)?;
628 if inode.file_type != FILE_TYPE_DIR {
629 return Err(FabricError::IoError(-1));
630 }
631
632 if !self.dir_cache.contains_key(&inode_id) {
633 let entries = self.read_dir_entries(inode_id)?;
634 self.dir_cache.insert(inode_id, entries);
635 }
636
637 Ok(self.dir_cache.get(&inode_id).cloned().unwrap_or_default())
638 }
639
640 pub fn stat(&mut self, path: &str) -> Result<FileStat> {
642 let inode_id = self.resolve_path(path)?;
643 let inode = self.get_inode(inode_id)?;
644 Ok(FileStat {
645 size: inode.size,
646 file_type: inode.file_type,
647 block_count: inode.block_count,
648 created_at: inode.created_at,
649 modified_at: inode.modified_at,
650 })
651 }
652
653 pub fn mkdir(&mut self, path: &str) -> Result<()> {
655 let (parent_inode_id, name) = self.resolve_parent(path)?;
656 if name.is_empty() || name.len() > MAX_NAME_LEN {
657 return Err(FabricError::IoError(-1));
658 }
659
660 let exists = self
662 .dir_cache
663 .get(&parent_inode_id)
664 .map(|entries| entries.iter().any(|e| e.name == name))
665 .unwrap_or(false);
666 if exists {
667 return Err(FabricError::IoError(-1));
668 }
669
670 let inode_id = self.superblock.next_inode_id;
671 self.superblock.next_inode_id += 1;
672
673 let mut inode = Inode::new(inode_id, FILE_TYPE_DIR);
674 inode.name_hash = fnv_hash(name.as_bytes());
675
676 self.inode_cache.insert(inode_id, inode);
677 self.dirty_inodes.insert(inode_id, true);
678 self.dir_cache.insert(inode_id, Vec::new());
679 self.dirty_dirs.insert(inode_id, true);
680
681 let dir_entry = DirEntry {
682 inode_id,
683 name,
684 file_type: FILE_TYPE_DIR,
685 };
686 let entries = self.dir_cache.entry(parent_inode_id).or_default();
687 entries.push(dir_entry);
688 self.dirty_dirs.insert(parent_inode_id, true);
689
690 self.write_superblock()?;
691 self.sync()?;
692 Ok(())
693 }
694
695 pub fn sync(&mut self) -> Result<()> {
697 let dirty_dirs: Vec<u64> = self.dirty_dirs.keys().cloned().collect();
699 for inode_id in dirty_dirs {
700 if let Some(entries) = self.dir_cache.get(&inode_id).cloned() {
701 self.write_dir_entries(inode_id, &entries)?;
702 }
703 self.dirty_dirs.remove(&inode_id);
704 }
705
706 let dirty_inodes: Vec<u64> = self.dirty_inodes.keys().cloned().collect();
708 for inode_id in dirty_inodes {
709 if let Some(inode) = self.inode_cache.get(&inode_id).cloned() {
710 self.write_inode(&inode)?;
711 }
712 self.dirty_inodes.remove(&inode_id);
713 }
714
715 if self.bitmap_dirty {
717 self.flush_bitmap()?;
718 self.bitmap_dirty = false;
719 }
720
721 Ok(())
722 }
723
724 pub fn unmount(mut self) -> Result<Vec<BlockLease>> {
726 self.sync()?;
727 Ok(self.leases)
728 }
729
730 pub fn set_stripe_threshold(&mut self, threshold: u64) {
732 self.stripe_threshold = threshold;
733 }
734
735 pub fn set_stripe_size(&mut self, size: u32) {
737 self.stripe_size = size;
738 }
739
740 fn truncate_inode(&mut self, inode_id: u64) -> Result<()> {
743 let blocks_to_free: Vec<u64> = {
745 let inode = self
746 .inode_cache
747 .get(&inode_id)
748 .ok_or(FabricError::IoError(-1))?;
749 let mut blocks = Vec::new();
750 for i in 0..inode.block_count as usize {
751 if i < MAX_DIRECT_BLOCKS && inode.direct_blocks[i] != 0 {
752 blocks.push(inode.direct_blocks[i]);
753 }
754 }
755 blocks
756 };
757
758 for block_addr in blocks_to_free {
759 self.free_block(block_addr);
760 }
761
762 let inode = self
763 .inode_cache
764 .get_mut(&inode_id)
765 .ok_or(FabricError::IoError(-1))?;
766 for i in 0..MAX_DIRECT_BLOCKS {
767 inode.direct_blocks[i] = 0;
768 }
769 inode.size = 0;
770 inode.block_count = 0;
771 self.dirty_inodes.insert(inode_id, true);
772 Ok(())
773 }
774
775 fn serialize_superblock(sb: &Superblock) -> [u8; BLOCK_SIZE] {
776 let mut block = [0u8; BLOCK_SIZE];
777 block[0..4].copy_from_slice(&sb.magic);
778 block[4..8].copy_from_slice(&sb.version.to_le_bytes());
779 block[8..12].copy_from_slice(&sb.block_size.to_le_bytes());
780 block[12..20].copy_from_slice(&sb.total_blocks.to_le_bytes());
781 block[20..28].copy_from_slice(&sb.inode_table_start.to_le_bytes());
782 block[28..36].copy_from_slice(&sb.data_start.to_le_bytes());
783 block[36..44].copy_from_slice(&sb.free_bitmap_start.to_le_bytes());
784 block[44..52].copy_from_slice(&sb.next_inode_id.to_le_bytes());
785 block[52..60].copy_from_slice(&sb.inode_table_blocks.to_le_bytes());
786 block
787 }
788
789 fn deserialize_superblock(block: &[u8; BLOCK_SIZE]) -> Result<Superblock> {
790 let mut magic = [0u8; 4];
791 magic.copy_from_slice(&block[0..4]);
792 Ok(Superblock {
793 magic,
794 version: u32::from_le_bytes([block[4], block[5], block[6], block[7]]),
795 block_size: u32::from_le_bytes([block[8], block[9], block[10], block[11]]),
796 total_blocks: u64::from_le_bytes(block[12..20].try_into().unwrap()),
797 inode_table_start: u64::from_le_bytes(block[20..28].try_into().unwrap()),
798 data_start: u64::from_le_bytes(block[28..36].try_into().unwrap()),
799 free_bitmap_start: u64::from_le_bytes(block[36..44].try_into().unwrap()),
800 next_inode_id: u64::from_le_bytes(block[44..52].try_into().unwrap()),
801 inode_table_blocks: u64::from_le_bytes(block[52..60].try_into().unwrap()),
802 })
803 }
804
805 fn write_superblock(&self) -> Result<()> {
806 let sb_bytes = Self::serialize_superblock(&self.superblock);
807 self.leases[0].block().write_block(0, &sb_bytes)
808 }
809
810 fn read_inode_from_disk(&self, inode_id: u64) -> Result<Inode> {
811 let max_inodes = self.superblock.inode_table_blocks * INODES_PER_BLOCK as u64;
812 if inode_id >= max_inodes {
813 return Err(FabricError::IoError(-1));
814 }
815
816 let block_offset = inode_id as usize / INODES_PER_BLOCK;
817 let slot_in_block = inode_id as usize % INODES_PER_BLOCK;
818 let block_lba = self.superblock.inode_table_start + block_offset as u64;
819
820 let block_data = self.leases[0].block().read_block(block_lba)?;
821 let start = slot_in_block * INODE_SIZE;
822 let inode_bytes = &block_data[start..start + INODE_SIZE];
823
824 Self::deserialize_inode(inode_bytes)
825 }
826
827 fn write_inode(&self, inode: &Inode) -> Result<()> {
828 let block_offset = inode.inode_id as usize / INODES_PER_BLOCK;
829 let slot_in_block = inode.inode_id as usize % INODES_PER_BLOCK;
830 let block_lba = self.superblock.inode_table_start + block_offset as u64;
831
832 let mut block_data = self.leases[0].block().read_block(block_lba)?;
833 let start = slot_in_block * INODE_SIZE;
834 let inode_bytes = Self::serialize_inode(inode);
835 block_data[start..start + INODE_SIZE].copy_from_slice(&inode_bytes);
836
837 self.leases[0].block().write_block(block_lba, &block_data)
838 }
839
840 fn zero_inode_on_disk(&self, inode_id: u64) -> Result<()> {
841 let block_offset = inode_id as usize / INODES_PER_BLOCK;
842 let slot_in_block = inode_id as usize % INODES_PER_BLOCK;
843 let block_lba = self.superblock.inode_table_start + block_offset as u64;
844
845 let mut block_data = self.leases[0].block().read_block(block_lba)?;
846 let start = slot_in_block * INODE_SIZE;
847 for b in &mut block_data[start..start + INODE_SIZE] {
848 *b = 0;
849 }
850 self.leases[0].block().write_block(block_lba, &block_data)
851 }
852
853 fn serialize_inode(inode: &Inode) -> [u8; INODE_SIZE] {
854 let mut buf = [0u8; INODE_SIZE];
855 buf[0..8].copy_from_slice(&inode.inode_id.to_le_bytes());
856 buf[8] = inode.file_type;
857 buf[9..17].copy_from_slice(&inode.size.to_le_bytes());
858 buf[17..21].copy_from_slice(&inode.block_count.to_le_bytes());
859 for (i, &blk) in inode.direct_blocks.iter().enumerate() {
860 let off = 21 + i * 8;
861 buf[off..off + 8].copy_from_slice(&blk.to_le_bytes());
862 }
863 let off = 21 + MAX_DIRECT_BLOCKS * 8; buf[off..off + 8].copy_from_slice(&inode.indirect_block.to_le_bytes());
865 buf[off + 8..off + 16].copy_from_slice(&inode.created_at.to_le_bytes());
866 buf[off + 16..off + 24].copy_from_slice(&inode.modified_at.to_le_bytes());
867 buf[off + 24..off + 32].copy_from_slice(&inode.name_hash.to_le_bytes());
868 buf
869 }
870
871 fn deserialize_inode(buf: &[u8]) -> Result<Inode> {
872 if buf.len() < INODE_SIZE {
873 return Err(FabricError::IoError(-1));
874 }
875 let inode_id = u64::from_le_bytes(buf[0..8].try_into().unwrap());
876 let file_type = buf[8];
877 let size = u64::from_le_bytes(buf[9..17].try_into().unwrap());
878 let block_count = u32::from_le_bytes(buf[17..21].try_into().unwrap());
879 let mut direct_blocks = [0u64; MAX_DIRECT_BLOCKS];
880 for (i, block) in direct_blocks.iter_mut().enumerate() {
881 let off = 21 + i * 8;
882 *block = u64::from_le_bytes(buf[off..off + 8].try_into().unwrap());
883 }
884 let off = 21 + MAX_DIRECT_BLOCKS * 8;
885 let indirect_block = u64::from_le_bytes(buf[off..off + 8].try_into().unwrap());
886 let created_at = u64::from_le_bytes(buf[off + 8..off + 16].try_into().unwrap());
887 let modified_at = u64::from_le_bytes(buf[off + 16..off + 24].try_into().unwrap());
888 let name_hash = u64::from_le_bytes(buf[off + 24..off + 32].try_into().unwrap());
889
890 Ok(Inode {
891 inode_id,
892 file_type,
893 size,
894 block_count,
895 direct_blocks,
896 indirect_block,
897 created_at,
898 modified_at,
899 name_hash,
900 })
901 }
902
903 fn get_inode(&mut self, inode_id: u64) -> Result<Inode> {
904 if let Some(inode) = self.inode_cache.get(&inode_id) {
905 return Ok(inode.clone());
906 }
907 let inode = self.read_inode_from_disk(inode_id)?;
908 self.inode_cache.insert(inode_id, inode.clone());
909 Ok(inode)
910 }
911
912 fn read_dir_entries(&self, inode_id: u64) -> Result<Vec<DirEntry>> {
913 let inode = self
914 .inode_cache
915 .get(&inode_id)
916 .cloned()
917 .ok_or(FabricError::IoError(-1))?;
918
919 if inode.size == 0 {
920 return Ok(Vec::new());
921 }
922
923 let num_blocks = (inode.size as usize).div_ceil(BLOCK_SIZE);
924 let mut data = Vec::with_capacity(inode.size as usize);
925 for i in 0..num_blocks {
926 let block_addr = self.get_data_block_addr(&inode, i)?;
927 if block_addr == 0 {
928 data.extend_from_slice(&[0u8; BLOCK_SIZE]);
929 } else {
930 let (lease_idx, lba) = decode_block_addr(block_addr);
931 let block = self.leases[lease_idx].block().read_block(lba)?;
932 data.extend_from_slice(&block);
933 }
934 }
935 data.truncate(inode.size as usize);
936
937 postcard::from_bytes(&data).map_err(|_| FabricError::IoError(-1))
938 }
939
940 fn write_dir_entries(&mut self, inode_id: u64, entries: &[DirEntry]) -> Result<()> {
941 let data = postcard::to_allocvec(entries).map_err(|_| FabricError::IoError(-1))?;
942 let num_blocks = data.len().div_ceil(BLOCK_SIZE);
943
944 for i in 0..num_blocks {
946 self.ensure_data_block(inode_id, i)?;
947 }
948
949 let inode = self.inode_cache.get(&inode_id).unwrap().clone();
951
952 for i in 0..num_blocks {
954 let start = i * BLOCK_SIZE;
955 let end = cmp::min(start + BLOCK_SIZE, data.len());
956 let mut block = [0u8; BLOCK_SIZE];
957 block[..end - start].copy_from_slice(&data[start..end]);
958
959 let block_addr = self.get_data_block_addr(&inode, i)?;
960 let (lease_idx, lba) = decode_block_addr(block_addr);
961 self.leases[lease_idx].block().write_block(lba, &block)?;
962 }
963
964 let inode = self.inode_cache.get_mut(&inode_id).unwrap();
966 inode.size = data.len() as u64;
967 self.dirty_inodes.insert(inode_id, true);
968
969 Ok(())
970 }
971
972 fn get_data_block_addr(&self, inode: &Inode, block_index: usize) -> Result<u64> {
973 if block_index < MAX_DIRECT_BLOCKS {
974 Ok(inode.direct_blocks[block_index])
975 } else {
976 if inode.indirect_block == 0 {
977 return Ok(0);
978 }
979 let indirect_index = block_index - MAX_DIRECT_BLOCKS;
980 let entries_per_block = BLOCK_SIZE / 8;
981 if indirect_index >= entries_per_block {
982 return Err(FabricError::CapacityExceeded);
983 }
984 let (lease_idx, lba) = decode_block_addr(inode.indirect_block);
985 let block = self.leases[lease_idx].block().read_block(lba)?;
986 let off = indirect_index * 8;
987 Ok(u64::from_le_bytes(block[off..off + 8].try_into().unwrap()))
988 }
989 }
990
991 fn ensure_data_block(&mut self, inode_id: u64, block_index: usize) -> Result<u64> {
992 let existing = {
994 let inode = self.inode_cache.get(&inode_id).unwrap();
995 self.get_data_block_addr(inode, block_index)?
996 };
997 if existing != 0 {
998 return Ok(existing);
999 }
1000
1001 let new_block = self.alloc_block_for_file(block_index)?;
1003
1004 if block_index < MAX_DIRECT_BLOCKS {
1005 let inode = self.inode_cache.get_mut(&inode_id).unwrap();
1006 inode.direct_blocks[block_index] = new_block;
1007 inode.block_count += 1;
1008 } else {
1009 let indirect_index = block_index - MAX_DIRECT_BLOCKS;
1011 let entries_per_block = BLOCK_SIZE / 8;
1012 if indirect_index >= entries_per_block {
1013 return Err(FabricError::CapacityExceeded);
1014 }
1015
1016 let indirect_addr = {
1018 let inode = self.inode_cache.get(&inode_id).unwrap();
1019 inode.indirect_block
1020 };
1021 let indirect_addr = if indirect_addr == 0 {
1022 let new_indirect = self.alloc_block_primary()?;
1023 let (li, lba) = decode_block_addr(new_indirect);
1024 let zero = [0u8; BLOCK_SIZE];
1025 self.leases[li].block().write_block(lba, &zero)?;
1026 let inode = self.inode_cache.get_mut(&inode_id).unwrap();
1027 inode.indirect_block = new_indirect;
1028 new_indirect
1029 } else {
1030 indirect_addr
1031 };
1032
1033 let (li, lba) = decode_block_addr(indirect_addr);
1035 let mut block = self.leases[li].block().read_block(lba)?;
1036 let off = indirect_index * 8;
1037 block[off..off + 8].copy_from_slice(&new_block.to_le_bytes());
1038 self.leases[li].block().write_block(lba, &block)?;
1039
1040 let inode = self.inode_cache.get_mut(&inode_id).unwrap();
1041 inode.block_count += 1;
1042 }
1043
1044 self.dirty_inodes.insert(inode_id, true);
1045 Ok(new_block)
1046 }
1047
1048 fn alloc_block_for_file(&mut self, block_index: usize) -> Result<u64> {
1049 if self.leases.len() <= 1 {
1050 return self.alloc_block_primary();
1051 }
1052
1053 let stripe_unit = self.stripe_size as usize;
1054 let stripe_num = block_index / stripe_unit;
1055 let lease_idx = stripe_num % self.leases.len();
1056
1057 if lease_idx == 0 {
1058 self.alloc_block_primary()
1059 } else {
1060 let lba = self.find_free_secondary_block(lease_idx)?;
1061 let num_blocks = self.leases[lease_idx].block().num_blocks();
1062 if lba >= num_blocks {
1063 return Err(FabricError::CapacityExceeded);
1064 }
1065 Ok(encode_block_addr(lease_idx, lba))
1066 }
1067 }
1068
1069 fn find_free_secondary_block(&self, lease_idx: usize) -> Result<u64> {
1070 let mut used_lbas: Vec<u64> = Vec::new();
1071 for inode in self.inode_cache.values() {
1072 for i in 0..inode.block_count as usize {
1073 if i < MAX_DIRECT_BLOCKS {
1074 let addr = inode.direct_blocks[i];
1075 if addr != 0 {
1076 let (li, lba) = decode_block_addr(addr);
1077 if li == lease_idx {
1078 used_lbas.push(lba);
1079 }
1080 }
1081 }
1082 }
1083 }
1084 used_lbas.sort_unstable();
1085
1086 let num_blocks = self.leases[lease_idx].block().num_blocks();
1087 let mut candidate = 0u64;
1088 for &used in &used_lbas {
1089 if candidate == used {
1090 candidate += 1;
1091 }
1092 }
1093 if candidate >= num_blocks {
1094 return Err(FabricError::CapacityExceeded);
1095 }
1096 Ok(candidate)
1097 }
1098
1099 fn alloc_block_primary(&mut self) -> Result<u64> {
1100 let start = self.superblock.data_start as usize;
1101 let total = self.superblock.total_blocks as usize;
1102
1103 for blk in start..total {
1104 let byte_idx = blk / 8;
1105 let bit_idx = blk % 8;
1106 if byte_idx < self.bitmap_cache.len()
1107 && (self.bitmap_cache[byte_idx] & (1 << bit_idx)) == 0
1108 {
1109 self.bitmap_cache[byte_idx] |= 1 << bit_idx;
1110 self.bitmap_dirty = true;
1111 return Ok(encode_block_addr(0, blk as u64));
1112 }
1113 }
1114
1115 Err(FabricError::CapacityExceeded)
1116 }
1117
1118 fn free_block(&mut self, block_addr: u64) {
1119 let (lease_idx, lba) = decode_block_addr(block_addr);
1120 if lease_idx == 0 {
1121 let blk = lba as usize;
1122 let byte_idx = blk / 8;
1123 let bit_idx = blk % 8;
1124 if byte_idx < self.bitmap_cache.len() {
1125 self.bitmap_cache[byte_idx] &= !(1 << bit_idx);
1126 self.bitmap_dirty = true;
1127 }
1128 }
1129 }
1130
1131 fn flush_bitmap(&self) -> Result<()> {
1132 let bitmap_blocks = self.superblock.total_blocks.div_ceil(BLOCK_SIZE as u64 * 8);
1133 for i in 0..bitmap_blocks as usize {
1134 let mut block = [0u8; BLOCK_SIZE];
1135 let start = i * BLOCK_SIZE;
1136 let end = cmp::min(start + BLOCK_SIZE, self.bitmap_cache.len());
1137 if start < end {
1138 block[..end - start].copy_from_slice(&self.bitmap_cache[start..end]);
1139 }
1140 self.leases[0]
1141 .block()
1142 .write_block(self.superblock.free_bitmap_start + i as u64, &block)?;
1143 }
1144 Ok(())
1145 }
1146
1147 fn resolve_path(&mut self, path: &str) -> Result<u64> {
1148 if path == "/" {
1149 return Ok(ROOT_INODE_ID);
1150 }
1151
1152 let path = path.trim_start_matches('/');
1153 let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
1154
1155 let mut current_inode_id = ROOT_INODE_ID;
1156 for component in &components {
1157 if !self.dir_cache.contains_key(¤t_inode_id) {
1158 let entries = self.read_dir_entries(current_inode_id)?;
1159 self.dir_cache.insert(current_inode_id, entries);
1160 }
1161
1162 let entries = self.dir_cache.get(¤t_inode_id).unwrap();
1163 let mut found = false;
1164 for entry in entries {
1165 if entry.name == *component {
1166 current_inode_id = entry.inode_id;
1167 if !self.inode_cache.contains_key(¤t_inode_id) {
1168 let inode = self.read_inode_from_disk(current_inode_id)?;
1169 self.inode_cache.insert(current_inode_id, inode);
1170 }
1171 found = true;
1172 break;
1173 }
1174 }
1175 if !found {
1176 return Err(FabricError::IoError(-1));
1177 }
1178 }
1179
1180 Ok(current_inode_id)
1181 }
1182
1183 fn resolve_parent(&mut self, path: &str) -> Result<(u64, String)> {
1185 let path = path.trim_start_matches('/');
1186 if path.is_empty() {
1187 return Err(FabricError::IoError(-1));
1188 }
1189
1190 let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
1191 if components.is_empty() {
1192 return Err(FabricError::IoError(-1));
1193 }
1194
1195 let name = String::from(*components.last().unwrap());
1196
1197 if components.len() == 1 {
1198 return Ok((ROOT_INODE_ID, name));
1200 }
1201
1202 let parent_components = &components[..components.len() - 1];
1204 let mut current_inode_id = ROOT_INODE_ID;
1205 for component in parent_components {
1206 if !self.dir_cache.contains_key(¤t_inode_id) {
1207 let entries = self.read_dir_entries(current_inode_id)?;
1208 self.dir_cache.insert(current_inode_id, entries);
1209 }
1210
1211 let entries = self.dir_cache.get(¤t_inode_id).unwrap();
1212 let mut found = false;
1213 for entry in entries {
1214 if entry.name == *component {
1215 current_inode_id = entry.inode_id;
1216 if !self.inode_cache.contains_key(¤t_inode_id) {
1217 let inode = self.read_inode_from_disk(current_inode_id)?;
1218 self.inode_cache.insert(current_inode_id, inode);
1219 }
1220 if !self.dir_cache.contains_key(¤t_inode_id) {
1222 let dir_entries = self.read_dir_entries(current_inode_id)?;
1223 self.dir_cache.insert(current_inode_id, dir_entries);
1224 }
1225 found = true;
1226 break;
1227 }
1228 }
1229 if !found {
1230 return Err(FabricError::IoError(-1));
1231 }
1232 }
1233
1234 Ok((current_inode_id, name))
1235 }
1236}
1237
1238fn encode_block_addr(lease_idx: usize, lba: u64) -> u64 {
1241 ((lease_idx as u64) << 48) | (lba & 0x0000_FFFF_FFFF_FFFF)
1242}
1243
1244fn decode_block_addr(addr: u64) -> (usize, u64) {
1246 let lease_idx = (addr >> 48) as usize;
1247 let lba = addr & 0x0000_FFFF_FFFF_FFFF;
1248 (lease_idx, lba)
1249}
1250
1251fn fnv_hash(data: &[u8]) -> u64 {
1253 let mut hash: u64 = 0xcbf29ce484222325;
1254 for &b in data {
1255 hash ^= b as u64;
1256 hash = hash.wrapping_mul(0x100000001b3);
1257 }
1258 hash
1259}
1260
1261#[cfg(test)]
1262mod tests {
1263 use super::*;
1264 use grafos_std::block::BlockBuilder;
1265 use grafos_std::host;
1266
1267 fn setup_lease(num_blocks: u64) -> BlockLease {
1268 host::reset_mock();
1269 host::mock_set_fbbu_num_blocks(num_blocks);
1270 BlockBuilder::new()
1271 .min_blocks(num_blocks)
1272 .acquire()
1273 .expect("acquire")
1274 }
1275
1276 fn setup_leases(count: usize, num_blocks: u64) -> Vec<BlockLease> {
1277 host::reset_mock();
1278 host::mock_set_fbbu_num_blocks(num_blocks);
1279 let mut leases = Vec::new();
1280 for _ in 0..count {
1281 leases.push(BlockBuilder::new().acquire().expect("acquire"));
1282 }
1283 leases
1284 }
1285
1286 #[test]
1287 fn format_mount_roundtrip() {
1288 let lease = setup_lease(2048);
1289 let fs = FabricFs::format(vec![lease]).expect("format");
1290 let leases = fs.unmount().expect("unmount");
1291
1292 let fs = FabricFs::mount(leases).expect("mount");
1293 assert_eq!(fs.superblock.magic, *b"GFFS");
1294 assert_eq!(fs.superblock.version, 1);
1295 assert_eq!(fs.superblock.block_size, 512);
1296 assert_eq!(fs.superblock.total_blocks, 2048);
1297 }
1298
1299 #[test]
1300 fn create_write_read() {
1301 let lease = setup_lease(2048);
1302 let mut fs = FabricFs::format(vec![lease]).expect("format");
1303
1304 let mut fh = fs.create("/test.txt").expect("create");
1305 let data = b"hello fabricBIOS filesystem";
1306 fs.write(&mut fh, data).expect("write");
1307 fs.close(fh).expect("close");
1308
1309 let fh = fs.open("/test.txt", OpenFlags::Read).expect("open");
1310 let mut buf = [0u8; 64];
1311 let n = fs.read(&fh, &mut buf).expect("read");
1312 assert_eq!(n, data.len());
1313 assert_eq!(&buf[..n], data);
1314 }
1315
1316 #[test]
1317 fn nested_directory_and_file() {
1318 let lease = setup_lease(4096);
1319 let mut fs = FabricFs::format(vec![lease]).expect("format");
1320
1321 fs.mkdir("/a").expect("mkdir a");
1322 fs.mkdir("/a/b").expect("mkdir a/b");
1323 fs.mkdir("/a/b/c").expect("mkdir a/b/c");
1324
1325 let mut fh = fs.create("/a/b/c/file.txt").expect("create");
1326 fs.write(&mut fh, b"nested file").expect("write");
1327 fs.close(fh).expect("close");
1328
1329 let fh = fs.open("/a/b/c/file.txt", OpenFlags::Read).expect("open");
1330 let mut buf = [0u8; 64];
1331 let n = fs.read(&fh, &mut buf).expect("read");
1332 assert_eq!(&buf[..n], b"nested file");
1333 }
1334
1335 #[test]
1336 fn readdir_lists_files() {
1337 let lease = setup_lease(2048);
1338 let mut fs = FabricFs::format(vec![lease]).expect("format");
1339
1340 let mut fh = fs.create("/alpha.txt").expect("create");
1341 fs.write(&mut fh, b"a").expect("write");
1342 fs.close(fh).expect("close");
1343
1344 let mut fh = fs.create("/beta.txt").expect("create");
1345 fs.write(&mut fh, b"b").expect("write");
1346 fs.close(fh).expect("close");
1347
1348 fs.mkdir("/subdir").expect("mkdir");
1349
1350 let entries = fs.readdir("/").expect("readdir");
1351 assert_eq!(entries.len(), 3);
1352 let names: Vec<&str> = entries.iter().map(|e| e.name.as_str()).collect();
1353 assert!(names.contains(&"alpha.txt"));
1354 assert!(names.contains(&"beta.txt"));
1355 assert!(names.contains(&"subdir"));
1356 }
1357
1358 #[test]
1359 fn remove_file_frees_blocks() {
1360 let lease = setup_lease(2048);
1361 let mut fs = FabricFs::format(vec![lease]).expect("format");
1362
1363 let mut fh = fs.create("/delete_me.txt").expect("create");
1364 let data = vec![0xABu8; 1024];
1365 fs.write(&mut fh, &data).expect("write");
1366 fs.close(fh).expect("close");
1367
1368 let stat = fs.stat("/delete_me.txt").expect("stat");
1369 assert_eq!(stat.size, 1024);
1370
1371 fs.remove("/delete_me.txt").expect("remove");
1372 assert!(fs.open("/delete_me.txt", OpenFlags::Read).is_err());
1373 }
1374
1375 #[test]
1376 fn stat_returns_correct_size() {
1377 let lease = setup_lease(2048);
1378 let mut fs = FabricFs::format(vec![lease]).expect("format");
1379
1380 let mut fh = fs.create("/sized.txt").expect("create");
1381 fs.write(&mut fh, b"twelve bytes").expect("write");
1382 fs.close(fh).expect("close");
1383
1384 let stat = fs.stat("/sized.txt").expect("stat");
1385 assert_eq!(stat.size, 12);
1386 assert_eq!(stat.file_type, FILE_TYPE_FILE);
1387 }
1388
1389 #[test]
1390 fn seek_and_read() {
1391 let lease = setup_lease(2048);
1392 let mut fs = FabricFs::format(vec![lease]).expect("format");
1393
1394 let mut fh = fs.create("/seekable.txt").expect("create");
1395 fs.write(&mut fh, b"ABCDEFGHIJ").expect("write");
1396 fs.close(fh).expect("close");
1397
1398 let mut fh = fs.open("/seekable.txt", OpenFlags::Read).expect("open");
1399 fs.seek(&mut fh, SeekFrom::Start(5)).expect("seek");
1400 let mut buf = [0u8; 5];
1401 let n = fs.read(&fh, &mut buf).expect("read");
1402 assert_eq!(n, 5);
1403 assert_eq!(&buf, b"FGHIJ");
1404
1405 fs.seek(&mut fh, SeekFrom::Start(0)).expect("seek start");
1406 let mut buf2 = [0u8; 3];
1407 let n = fs.read(&fh, &mut buf2).expect("read");
1408 assert_eq!(n, 3);
1409 assert_eq!(&buf2, b"ABC");
1410 }
1411
1412 #[test]
1413 fn large_file_striped_across_leases() {
1414 let leases = setup_leases(3, 8192);
1415 let mut fs = FabricFs::format(leases).expect("format");
1416 fs.set_stripe_threshold(0);
1417
1418 let mut fh = fs.create("/striped.dat").expect("create");
1419 let data: Vec<u8> = (0..4096u32).map(|i| (i % 251) as u8).collect();
1420 fs.write(&mut fh, &data).expect("write");
1421 fs.close(fh).expect("close");
1422
1423 let fh = fs.open("/striped.dat", OpenFlags::Read).expect("open");
1424 let mut buf = vec![0u8; 4096];
1425 let n = fs.read(&fh, &mut buf).expect("read");
1426 assert_eq!(n, 4096);
1427 assert_eq!(buf, data);
1428 }
1429
1430 #[test]
1431 fn format_create_many_small_files() {
1432 let lease = setup_lease(4096);
1433 let mut fs = FabricFs::format(vec![lease]).expect("format");
1434
1435 for i in 0..20u32 {
1436 let name = alloc::format!("/file_{}.txt", i);
1437 let mut fh = fs.create(&name).expect("create");
1438 let content = alloc::format!("content {}", i);
1439 fs.write(&mut fh, content.as_bytes()).expect("write");
1440 fs.close(fh).expect("close");
1441 }
1442
1443 let entries = fs.readdir("/").expect("readdir");
1444 assert_eq!(entries.len(), 20);
1445
1446 let fh = fs.open("/file_7.txt", OpenFlags::Read).expect("open");
1447 let mut buf = [0u8; 64];
1448 let n = fs.read(&fh, &mut buf).expect("read");
1449 assert_eq!(&buf[..n], b"content 7");
1450 }
1451
1452 #[test]
1453 fn unmount_releases_leases() {
1454 let lease = setup_lease(2048);
1455 let mut fs = FabricFs::format(vec![lease]).expect("format");
1456
1457 let mut fh = fs.create("/test.txt").expect("create");
1458 fs.write(&mut fh, b"data").expect("write");
1459 fs.close(fh).expect("close");
1460
1461 let leases = fs.unmount().expect("unmount");
1462 assert_eq!(leases.len(), 1);
1463 }
1464
1465 #[test]
1466 fn sync_flushes_dirty_metadata() {
1467 let lease = setup_lease(2048);
1468 let mut fs = FabricFs::format(vec![lease]).expect("format");
1469
1470 let mut fh = fs.create("/synced.txt").expect("create");
1471 fs.write(&mut fh, b"sync me").expect("write");
1472
1473 fs.sync().expect("sync");
1474
1475 let inode = fs.inode_cache.get(&fh.inode_id).unwrap();
1476 assert_eq!(inode.size, 7);
1477
1478 fs.close(fh).expect("close");
1479 }
1480
1481 #[test]
1482 fn mount_reads_existing_data() {
1483 let lease = setup_lease(4096);
1484 let mut fs = FabricFs::format(vec![lease]).expect("format");
1485
1486 let mut fh = fs.create("/persist.txt").expect("create");
1487 fs.write(&mut fh, b"persistent data").expect("write");
1488 fs.close(fh).expect("close");
1489
1490 let leases = fs.unmount().expect("unmount");
1491 let mut fs = FabricFs::mount(leases).expect("mount");
1492
1493 let fh = fs.open("/persist.txt", OpenFlags::Read).expect("open");
1494 let mut buf = [0u8; 64];
1495 let n = fs.read(&fh, &mut buf).expect("read");
1496 assert_eq!(&buf[..n], b"persistent data");
1497 }
1498
1499 #[test]
1500 fn write_spanning_multiple_blocks() {
1501 let lease = setup_lease(2048);
1502 let mut fs = FabricFs::format(vec![lease]).expect("format");
1503
1504 let mut fh = fs.create("/big.txt").expect("create");
1505 let data: Vec<u8> = (0..2048u32).map(|i| (i % 256) as u8).collect();
1506 fs.write(&mut fh, &data).expect("write");
1507 fs.close(fh).expect("close");
1508
1509 let fh = fs.open("/big.txt", OpenFlags::Read).expect("open");
1510 let mut buf = vec![0u8; 2048];
1511 let n = fs.read(&fh, &mut buf).expect("read");
1512 assert_eq!(n, 2048);
1513 assert_eq!(buf, data);
1514 }
1515
1516 #[test]
1517 fn seek_from_end() {
1518 let lease = setup_lease(2048);
1519 let mut fs = FabricFs::format(vec![lease]).expect("format");
1520
1521 let mut fh = fs.create("/endseek.txt").expect("create");
1522 fs.write(&mut fh, b"0123456789").expect("write");
1523 fs.close(fh).expect("close");
1524
1525 let mut fh = fs.open("/endseek.txt", OpenFlags::Read).expect("open");
1526 fs.seek(&mut fh, SeekFrom::End(-3)).expect("seek");
1527 let mut buf = [0u8; 3];
1528 let n = fs.read(&fh, &mut buf).expect("read");
1529 assert_eq!(n, 3);
1530 assert_eq!(&buf, b"789");
1531 }
1532
1533 #[test]
1534 fn seek_from_current() {
1535 let lease = setup_lease(2048);
1536 let mut fs = FabricFs::format(vec![lease]).expect("format");
1537
1538 let mut fh = fs.create("/curseek.txt").expect("create");
1539 fs.write(&mut fh, b"ABCDEFGHIJ").expect("write");
1540 fs.close(fh).expect("close");
1541
1542 let mut fh = fs.open("/curseek.txt", OpenFlags::Read).expect("open");
1543 fs.seek(&mut fh, SeekFrom::Start(2)).expect("seek start");
1544 fs.seek(&mut fh, SeekFrom::Current(3))
1545 .expect("seek current");
1546 let mut buf = [0u8; 2];
1547 let n = fs.read(&fh, &mut buf).expect("read");
1548 assert_eq!(n, 2);
1549 assert_eq!(&buf, b"FG");
1550 }
1551
1552 #[test]
1553 fn read_at_eof_returns_zero() {
1554 let lease = setup_lease(2048);
1555 let mut fs = FabricFs::format(vec![lease]).expect("format");
1556
1557 let mut fh = fs.create("/small.txt").expect("create");
1558 fs.write(&mut fh, b"hi").expect("write");
1559 fs.close(fh).expect("close");
1560
1561 let mut fh = fs.open("/small.txt", OpenFlags::Read).expect("open");
1562 fs.seek(&mut fh, SeekFrom::Start(2)).expect("seek");
1563 let mut buf = [0u8; 10];
1564 let n = fs.read(&fh, &mut buf).expect("read");
1565 assert_eq!(n, 0);
1566 }
1567
1568 #[test]
1569 fn open_nonexistent_file_fails() {
1570 let lease = setup_lease(2048);
1571 let mut fs = FabricFs::format(vec![lease]).expect("format");
1572 assert!(fs.open("/does_not_exist.txt", OpenFlags::Read).is_err());
1573 }
1574
1575 #[test]
1576 fn remove_from_subdirectory() {
1577 let lease = setup_lease(4096);
1578 let mut fs = FabricFs::format(vec![lease]).expect("format");
1579
1580 fs.mkdir("/dir").expect("mkdir");
1581 let mut fh = fs.create("/dir/file.txt").expect("create");
1582 fs.write(&mut fh, b"in subdir").expect("write");
1583 fs.close(fh).expect("close");
1584
1585 fs.remove("/dir/file.txt").expect("remove");
1586 assert!(fs.open("/dir/file.txt", OpenFlags::Read).is_err());
1587
1588 let entries = fs.readdir("/dir").expect("readdir");
1589 assert!(entries.is_empty());
1590 }
1591
1592 #[test]
1593 fn stat_directory() {
1594 let lease = setup_lease(2048);
1595 let mut fs = FabricFs::format(vec![lease]).expect("format");
1596
1597 fs.mkdir("/mydir").expect("mkdir");
1598 let stat = fs.stat("/mydir").expect("stat");
1599 assert_eq!(stat.file_type, FILE_TYPE_DIR);
1600 }
1601
1602 #[test]
1603 fn create_existing_file_truncates() {
1604 let lease = setup_lease(2048);
1605 let mut fs = FabricFs::format(vec![lease]).expect("format");
1606
1607 let mut fh = fs.create("/reuse.txt").expect("create");
1608 fs.write(&mut fh, b"original content").expect("write");
1609 fs.close(fh).expect("close");
1610
1611 let mut fh = fs.create("/reuse.txt").expect("create again");
1612 fs.write(&mut fh, b"new").expect("write");
1613 fs.close(fh).expect("close");
1614
1615 let fh = fs.open("/reuse.txt", OpenFlags::Read).expect("open");
1616 let mut buf = [0u8; 64];
1617 let n = fs.read(&fh, &mut buf).expect("read");
1618 assert_eq!(&buf[..n], b"new");
1619 }
1620}