1use std::sync::Arc;
2use std::time::{SystemTime, UNIX_EPOCH};
3
4use rand::RngCore;
5
6use crate::allocator::{BitmapAllocator, SlotAllocator};
7use crate::block_store::BlockStore;
8use crate::codec::{
9 read_encrypted_object, read_encrypted_raw, write_encrypted_object, write_encrypted_raw,
10 ObjectCodec, PostcardCodec,
11};
12use crate::crypto::CryptoEngine;
13use crate::error::{FsError, FsResult};
14use crate::model::*;
15use crate::transaction::TransactionManager;
16
17pub struct FilesystemCore {
24 store: Arc<dyn BlockStore>,
25 crypto: Arc<dyn CryptoEngine>,
26 codec: PostcardCodec,
27 allocator: BitmapAllocator,
28 txn: TransactionManager,
29 superblock: Option<Superblock>,
31 next_inode_id: InodeId,
33}
34
35struct AncestorEntry {
38 inode: Inode,
39 dir_page: DirectoryPage,
40 child_index: usize,
41}
42
43fn max_chunk_payload(block_size: usize) -> usize {
47 if block_size > 200 {
50 block_size - 200
51 } else {
52 0
53 }
54}
55
56fn now_secs() -> u64 {
57 SystemTime::now()
58 .duration_since(UNIX_EPOCH)
59 .unwrap_or_default()
60 .as_secs()
61}
62
63impl FilesystemCore {
64 pub fn new(store: Arc<dyn BlockStore>, crypto: Arc<dyn CryptoEngine>) -> Self {
66 let total_blocks = store.total_blocks();
67 Self {
68 store,
69 crypto,
70 codec: PostcardCodec,
71 allocator: BitmapAllocator::new(total_blocks),
72 txn: TransactionManager::new(),
73 superblock: None,
74 next_inode_id: 1,
75 }
76 }
77
78 pub fn init_filesystem(&mut self) -> FsResult<()> {
83 let block_size = self.store.block_size() as u32;
84 let total_blocks = self.store.total_blocks();
85
86 let header = StorageHeader::new(block_size, total_blocks);
88 let header_bytes = self.codec.serialize_object(&header)?;
89 let bs = self.store.block_size();
90 let mut block = vec![0u8; bs];
91 rand::thread_rng().fill_bytes(&mut block);
92 let len = header_bytes.len() as u32;
93 block[..4].copy_from_slice(&len.to_le_bytes());
94 block[4..4 + header_bytes.len()].copy_from_slice(&header_bytes);
95 self.store.write_block(BLOCK_STORAGE_HEADER, &block)?;
96
97 let root_inode_id = self.alloc_inode_id();
99 let dir_page = DirectoryPage::new();
100 let dir_page_block = self.allocator.allocate()?;
101 write_encrypted_object(
102 self.store.as_ref(),
103 self.crypto.as_ref(),
104 &self.codec,
105 dir_page_block,
106 ObjectKind::DirectoryPage,
107 &dir_page,
108 )?;
109
110 let ts = now_secs();
111 let root_inode = Inode {
112 id: root_inode_id,
113 kind: InodeKind::Directory,
114 size: 0,
115 directory_page_ref: ObjectRef::new(dir_page_block),
116 extent_map_ref: ObjectRef::null(),
117 created_at: ts,
118 modified_at: ts,
119 };
120 let root_inode_block = self.allocator.allocate()?;
121 write_encrypted_object(
122 self.store.as_ref(),
123 self.crypto.as_ref(),
124 &self.codec,
125 root_inode_block,
126 ObjectKind::Inode,
127 &root_inode,
128 )?;
129
130 let sb = Superblock {
132 generation: 1,
133 root_inode_ref: ObjectRef::new(root_inode_block),
134 };
135 self.superblock = Some(sb.clone());
136
137 self.txn.commit(
139 self.store.as_ref(),
140 self.crypto.as_ref(),
141 &self.codec,
142 &self.allocator,
143 &sb,
144 )?;
145
146 Ok(())
147 }
148
149 pub fn open(&mut self) -> FsResult<()> {
151 let header = self.read_storage_header()?;
153 if !header.is_valid() {
154 return Err(FsError::InvalidSuperblock);
155 }
156
157 let (rp, was_b) = TransactionManager::recover_latest(self.store.as_ref(), &self.codec)?
159 .ok_or(FsError::InvalidRootPointer)?;
160
161 let sb: Superblock = read_encrypted_object(
163 self.store.as_ref(),
164 self.crypto.as_ref(),
165 &self.codec,
166 rp.superblock_ref.block_id,
167 )?;
168
169 let sb_bytes = self.codec.serialize_object(&sb)?;
171 let checksum = blake3::hash(&sb_bytes);
172 if *checksum.as_bytes() != rp.checksum {
173 return Err(FsError::InvalidSuperblock);
174 }
175
176 self.txn = TransactionManager::from_recovered(rp.generation, was_b);
177 self.superblock = Some(sb.clone());
178
179 self.rebuild_allocator(&sb)?;
181
182 Ok(())
183 }
184
185 fn split_path(path: &str) -> FsResult<(Vec<&str>, &str)> {
192 let trimmed = path.trim_matches('/');
193 if trimmed.is_empty() {
194 return Err(FsError::Internal("empty path".into()));
195 }
196 let parts: Vec<&str> = trimmed.split('/').collect();
197 let (dirs, leaf) = parts.split_at(parts.len() - 1);
198 Ok((dirs.to_vec(), leaf[0]))
199 }
200
201 fn split_dir_path(path: &str) -> Vec<&str> {
203 let trimmed = path.trim_matches('/');
204 if trimmed.is_empty() {
205 return Vec::new();
206 }
207 trimmed.split('/').collect()
208 }
209
210 fn resolve_dir_chain(
217 &self,
218 components: &[&str],
219 root_inode: &Inode,
220 ) -> FsResult<(Vec<AncestorEntry>, Inode, DirectoryPage)> {
221 let mut ancestors: Vec<AncestorEntry> = Vec::new();
222 let mut current_inode = root_inode.clone();
223 let mut current_dir_page: DirectoryPage =
224 self.read_obj(current_inode.directory_page_ref.block_id)?;
225
226 for component in components {
227 let idx = current_dir_page
228 .entries
229 .iter()
230 .position(|e| e.name == *component)
231 .ok_or_else(|| FsError::DirectoryNotFound(component.to_string()))?;
232
233 let entry = ¤t_dir_page.entries[idx];
234 if entry.kind != InodeKind::Directory {
235 return Err(FsError::NotADirectory(component.to_string()));
236 }
237
238 let child_inode: Inode = self.read_obj(entry.inode_ref.block_id)?;
239 let child_dir_page: DirectoryPage =
240 self.read_obj(child_inode.directory_page_ref.block_id)?;
241
242 ancestors.push(AncestorEntry {
243 inode: current_inode,
244 dir_page: current_dir_page,
245 child_index: idx,
246 });
247
248 current_inode = child_inode;
249 current_dir_page = child_dir_page;
250 }
251
252 Ok((ancestors, current_inode, current_dir_page))
253 }
254
255 fn commit_cow_chain(
262 &mut self,
263 sb: &Superblock,
264 ancestors: &[AncestorEntry],
265 target_inode: &Inode,
266 new_dir_page: &DirectoryPage,
267 ) -> FsResult<()> {
268 let mut new_dp_block = self.allocator.allocate()?;
270 self.write_obj(new_dp_block, ObjectKind::DirectoryPage, new_dir_page)?;
271
272 let mut new_inode = target_inode.clone();
274 new_inode.directory_page_ref = ObjectRef::new(new_dp_block);
275 new_inode.modified_at = now_secs();
276 let mut new_inode_block = self.allocator.allocate()?;
277 self.write_obj(new_inode_block, ObjectKind::Inode, &new_inode)?;
278
279 for ancestor in ancestors.iter().rev() {
281 let mut parent_dp = ancestor.dir_page.clone();
282 parent_dp.entries[ancestor.child_index].inode_ref = ObjectRef::new(new_inode_block);
283
284 new_dp_block = self.allocator.allocate()?;
285 self.write_obj(new_dp_block, ObjectKind::DirectoryPage, &parent_dp)?;
286
287 let mut parent_inode = ancestor.inode.clone();
288 parent_inode.directory_page_ref = ObjectRef::new(new_dp_block);
289 parent_inode.modified_at = now_secs();
290 new_inode_block = self.allocator.allocate()?;
291 self.write_obj(new_inode_block, ObjectKind::Inode, &parent_inode)?;
292 }
293
294 let new_sb = Superblock {
296 generation: sb.generation + 1,
297 root_inode_ref: ObjectRef::new(new_inode_block),
298 };
299 self.commit_superblock(new_sb)?;
300 Ok(())
301 }
302
303 pub fn create_file(&mut self, path: &str) -> FsResult<()> {
310 let (dir_parts, leaf) = Self::split_path(path)?;
311 self.validate_name(leaf)?;
312 let sb = self
313 .superblock
314 .as_ref()
315 .ok_or(FsError::NotInitialized)?
316 .clone();
317
318 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
319 let (ancestors, target_inode, mut dir_page) =
320 self.resolve_dir_chain(&dir_parts, &root_inode)?;
321
322 if dir_page.entries.iter().any(|e| e.name == leaf) {
323 return Err(FsError::FileAlreadyExists(leaf.to_string()));
324 }
325
326 let extent_map = ExtentMap::new();
328 let em_block = self.allocator.allocate()?;
329 self.write_obj(em_block, ObjectKind::ExtentMap, &extent_map)?;
330
331 let inode_id = self.alloc_inode_id();
333 let ts = now_secs();
334 let file_inode = Inode {
335 id: inode_id,
336 kind: InodeKind::File,
337 size: 0,
338 directory_page_ref: ObjectRef::null(),
339 extent_map_ref: ObjectRef::new(em_block),
340 created_at: ts,
341 modified_at: ts,
342 };
343 let inode_block = self.allocator.allocate()?;
344 self.write_obj(inode_block, ObjectKind::Inode, &file_inode)?;
345
346 dir_page.entries.push(DirectoryEntry {
347 name: leaf.to_string(),
348 inode_ref: ObjectRef::new(inode_block),
349 inode_id,
350 kind: InodeKind::File,
351 });
352
353 self.commit_cow_chain(&sb, &ancestors, &target_inode, &dir_page)?;
354 Ok(())
355 }
356
357 pub fn write_file(&mut self, path: &str, offset: u64, data: &[u8]) -> FsResult<()> {
359 let (dir_parts, leaf) = Self::split_path(path)?;
360 let sb = self
361 .superblock
362 .as_ref()
363 .ok_or(FsError::NotInitialized)?
364 .clone();
365 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
366 let (ancestors, target_inode, dir_page) =
367 self.resolve_dir_chain(&dir_parts, &root_inode)?;
368
369 let entry = dir_page
370 .entries
371 .iter()
372 .find(|e| e.name == leaf)
373 .ok_or_else(|| FsError::FileNotFound(leaf.to_string()))?;
374
375 if entry.kind != InodeKind::File {
376 return Err(FsError::NotAFile(leaf.to_string()));
377 }
378
379 let file_inode: Inode = self.read_obj(entry.inode_ref.block_id)?;
380 let mut extent_map: ExtentMap = self.read_obj(file_inode.extent_map_ref.block_id)?;
381
382 let mut buf = self.read_all_chunks(&extent_map)?;
385
386 let end = offset as usize + data.len();
387 if end > buf.len() {
388 buf.resize(end, 0);
389 }
390 buf[offset as usize..end].copy_from_slice(data);
391
392 let total_size = buf.len();
393
394 let chunk_size = max_chunk_payload(self.store.block_size());
396 if chunk_size == 0 {
397 return Err(FsError::DataTooLarge(total_size));
398 }
399
400 let mut new_entries = Vec::new();
401 for (i, chunk_data) in buf.chunks(chunk_size).enumerate() {
402 let data_block = self.allocator.allocate()?;
403 write_encrypted_raw(
404 self.store.as_ref(),
405 self.crypto.as_ref(),
406 &self.codec,
407 data_block,
408 ObjectKind::FileDataChunk,
409 chunk_data,
410 )?;
411 new_entries.push(ExtentEntry {
412 chunk_index: i as u64,
413 data_ref: ObjectRef::new(data_block),
414 plaintext_len: chunk_data.len() as u32,
415 });
416 }
417
418 extent_map.entries = new_entries;
420
421 let new_em_block = self.allocator.allocate()?;
423 self.write_obj(new_em_block, ObjectKind::ExtentMap, &extent_map)?;
424
425 let mut new_file_inode = file_inode.clone();
427 new_file_inode.size = total_size as u64;
428 new_file_inode.extent_map_ref = ObjectRef::new(new_em_block);
429 new_file_inode.modified_at = now_secs();
430 let new_inode_block = self.allocator.allocate()?;
431 self.write_obj(new_inode_block, ObjectKind::Inode, &new_file_inode)?;
432
433 let mut new_dir_page = dir_page.clone();
435 for e in &mut new_dir_page.entries {
436 if e.name == leaf {
437 e.inode_ref = ObjectRef::new(new_inode_block);
438 }
439 }
440
441 self.commit_cow_chain(&sb, &ancestors, &target_inode, &new_dir_page)?;
442 Ok(())
443 }
444
445 pub fn read_file(&self, path: &str, offset: u64, len: usize) -> FsResult<Vec<u8>> {
447 let (dir_parts, leaf) = Self::split_path(path)?;
448 let sb = self.superblock.as_ref().ok_or(FsError::NotInitialized)?;
449 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
450 let (_, _, dir_page) = self.resolve_dir_chain(&dir_parts, &root_inode)?;
451
452 let entry = dir_page
453 .entries
454 .iter()
455 .find(|e| e.name == leaf)
456 .ok_or_else(|| FsError::FileNotFound(leaf.to_string()))?;
457
458 if entry.kind != InodeKind::File {
459 return Err(FsError::NotAFile(leaf.to_string()));
460 }
461
462 let file_inode: Inode = self.read_obj(entry.inode_ref.block_id)?;
463 let extent_map: ExtentMap = self.read_obj(file_inode.extent_map_ref.block_id)?;
464
465 let full_data = self.read_all_chunks(&extent_map)?;
466
467 let start = offset as usize;
468 if start >= full_data.len() {
469 return Ok(Vec::new());
470 }
471 let end = std::cmp::min(start + len, full_data.len());
472 Ok(full_data[start..end].to_vec())
473 }
474
475 pub fn list_directory(&self, path: &str) -> FsResult<Vec<DirListEntry>> {
479 let sb = self.superblock.as_ref().ok_or(FsError::NotInitialized)?;
480 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
481
482 let components = Self::split_dir_path(path);
483 let (_, _, dir_page) = self.resolve_dir_chain(&components, &root_inode)?;
484
485 let mut result = Vec::new();
486 for entry in &dir_page.entries {
487 let inode: Inode = self.read_obj(entry.inode_ref.block_id)?;
488 result.push(DirListEntry {
489 name: entry.name.clone(),
490 kind: entry.kind,
491 size: inode.size,
492 inode_id: entry.inode_id,
493 });
494 }
495 Ok(result)
496 }
497
498 pub fn create_directory(&mut self, path: &str) -> FsResult<()> {
502 let (dir_parts, leaf) = Self::split_path(path)?;
503 self.validate_name(leaf)?;
504 let sb = self
505 .superblock
506 .as_ref()
507 .ok_or(FsError::NotInitialized)?
508 .clone();
509 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
510 let (ancestors, target_inode, mut dir_page) =
511 self.resolve_dir_chain(&dir_parts, &root_inode)?;
512
513 if dir_page.entries.iter().any(|e| e.name == leaf) {
514 return Err(FsError::DirectoryAlreadyExists(leaf.to_string()));
515 }
516
517 let sub_dp = DirectoryPage::new();
519 let sub_dp_block = self.allocator.allocate()?;
520 self.write_obj(sub_dp_block, ObjectKind::DirectoryPage, &sub_dp)?;
521
522 let inode_id = self.alloc_inode_id();
523 let ts = now_secs();
524 let dir_inode = Inode {
525 id: inode_id,
526 kind: InodeKind::Directory,
527 size: 0,
528 directory_page_ref: ObjectRef::new(sub_dp_block),
529 extent_map_ref: ObjectRef::null(),
530 created_at: ts,
531 modified_at: ts,
532 };
533 let inode_block = self.allocator.allocate()?;
534 self.write_obj(inode_block, ObjectKind::Inode, &dir_inode)?;
535
536 dir_page.entries.push(DirectoryEntry {
537 name: leaf.to_string(),
538 inode_ref: ObjectRef::new(inode_block),
539 inode_id,
540 kind: InodeKind::Directory,
541 });
542
543 self.commit_cow_chain(&sb, &ancestors, &target_inode, &dir_page)?;
544 Ok(())
545 }
546
547 pub fn remove_file(&mut self, path: &str) -> FsResult<()> {
549 let (dir_parts, leaf) = Self::split_path(path)?;
550 let sb = self
551 .superblock
552 .as_ref()
553 .ok_or(FsError::NotInitialized)?
554 .clone();
555 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
556 let (ancestors, target_inode, mut dir_page) =
557 self.resolve_dir_chain(&dir_parts, &root_inode)?;
558
559 let idx = dir_page
560 .entries
561 .iter()
562 .position(|e| e.name == leaf)
563 .ok_or_else(|| FsError::FileNotFound(leaf.to_string()))?;
564
565 let entry = &dir_page.entries[idx];
566 if entry.kind == InodeKind::Directory {
567 let dir_inode: Inode = self.read_obj(entry.inode_ref.block_id)?;
568 let sub_page: DirectoryPage = self.read_obj(dir_inode.directory_page_ref.block_id)?;
569 if !sub_page.entries.is_empty() {
570 return Err(FsError::DirectoryNotEmpty(leaf.to_string()));
571 }
572 }
573
574 dir_page.entries.remove(idx);
575 self.commit_cow_chain(&sb, &ancestors, &target_inode, &dir_page)?;
576 Ok(())
577 }
578
579 pub fn rename(&mut self, old_path: &str, new_path: &str) -> FsResult<()> {
582 let (old_dir, old_leaf) = Self::split_path(old_path)?;
583 let (new_dir, new_leaf) = Self::split_path(new_path)?;
584 self.validate_name(new_leaf)?;
585
586 if old_dir != new_dir {
587 return Err(FsError::Internal(
588 "rename across directories is not supported".into(),
589 ));
590 }
591
592 let sb = self
593 .superblock
594 .as_ref()
595 .ok_or(FsError::NotInitialized)?
596 .clone();
597 let root_inode: Inode = self.read_obj(sb.root_inode_ref.block_id)?;
598 let (ancestors, target_inode, mut dir_page) =
599 self.resolve_dir_chain(&old_dir, &root_inode)?;
600
601 if dir_page.entries.iter().any(|e| e.name == new_leaf) {
602 return Err(FsError::FileAlreadyExists(new_leaf.to_string()));
603 }
604
605 let entry = dir_page
606 .entries
607 .iter_mut()
608 .find(|e| e.name == old_leaf)
609 .ok_or_else(|| FsError::FileNotFound(old_leaf.to_string()))?;
610
611 entry.name = new_leaf.to_string();
612
613 self.commit_cow_chain(&sb, &ancestors, &target_inode, &dir_page)?;
614 Ok(())
615 }
616
617 pub fn sync(&self) -> FsResult<()> {
619 self.store.sync()
620 }
621
622 fn alloc_inode_id(&mut self) -> InodeId {
625 let id = self.next_inode_id;
626 self.next_inode_id += 1;
627 id
628 }
629
630 fn validate_name(&self, name: &str) -> FsResult<()> {
631 if name.is_empty() || name.contains('/') || name.contains('\0') {
632 return Err(FsError::Internal("invalid name".into()));
633 }
634 if name.len() > MAX_NAME_LEN {
635 return Err(FsError::NameTooLong(name.len(), MAX_NAME_LEN));
636 }
637 Ok(())
638 }
639
640 fn read_obj<T: serde::de::DeserializeOwned>(&self, block_id: u64) -> FsResult<T> {
641 read_encrypted_object(
642 self.store.as_ref(),
643 self.crypto.as_ref(),
644 &self.codec,
645 block_id,
646 )
647 }
648
649 fn write_obj<T: serde::Serialize>(
650 &self,
651 block_id: u64,
652 kind: ObjectKind,
653 obj: &T,
654 ) -> FsResult<()> {
655 write_encrypted_object(
656 self.store.as_ref(),
657 self.crypto.as_ref(),
658 &self.codec,
659 block_id,
660 kind,
661 obj,
662 )
663 }
664
665 fn read_all_chunks(&self, extent_map: &ExtentMap) -> FsResult<Vec<u8>> {
666 let mut entries = extent_map.entries.clone();
667 entries.sort_by_key(|e| e.chunk_index);
668
669 let mut buf = Vec::new();
670 for entry in &entries {
671 let chunk = read_encrypted_raw(
672 self.store.as_ref(),
673 self.crypto.as_ref(),
674 &self.codec,
675 entry.data_ref.block_id,
676 )?;
677 let len = entry.plaintext_len as usize;
679 if len <= chunk.len() {
680 buf.extend_from_slice(&chunk[..len]);
681 } else {
682 buf.extend_from_slice(&chunk);
683 }
684 }
685 Ok(buf)
686 }
687
688 fn read_storage_header(&self) -> FsResult<StorageHeader> {
689 let block = self.store.read_block(BLOCK_STORAGE_HEADER)?;
690 if block.len() < 4 {
691 return Err(FsError::InvalidSuperblock);
692 }
693 let len = u32::from_le_bytes([block[0], block[1], block[2], block[3]]) as usize;
694 if len == 0 || 4 + len > block.len() {
695 return Err(FsError::InvalidSuperblock);
696 }
697 self.codec
698 .deserialize_object::<StorageHeader>(&block[4..4 + len])
699 }
700
701 fn commit_superblock(&mut self, sb: Superblock) -> FsResult<()> {
702 self.txn.commit(
703 self.store.as_ref(),
704 self.crypto.as_ref(),
705 &self.codec,
706 &self.allocator,
707 &sb,
708 )?;
709 self.superblock = Some(sb);
710 Ok(())
711 }
712
713 fn rebuild_allocator(&mut self, sb: &Superblock) -> FsResult<()> {
716 let (rp, _) = TransactionManager::recover_latest(self.store.as_ref(), &self.codec)?
723 .ok_or(FsError::InvalidRootPointer)?;
724 self.allocator.mark_allocated(rp.superblock_ref.block_id)?;
725
726 self.mark_inode_tree(sb.root_inode_ref.block_id)?;
728
729 Ok(())
733 }
734
735 fn mark_inode_tree(&mut self, inode_block: u64) -> FsResult<()> {
736 self.allocator.mark_allocated(inode_block)?;
737 let inode: Inode = self.read_obj(inode_block)?;
738
739 if inode.id >= self.next_inode_id {
740 self.next_inode_id = inode.id + 1;
741 }
742
743 match inode.kind {
744 InodeKind::Directory => {
745 if !inode.directory_page_ref.is_null() {
746 self.allocator
747 .mark_allocated(inode.directory_page_ref.block_id)?;
748 let dir_page: DirectoryPage =
749 self.read_obj(inode.directory_page_ref.block_id)?;
750 for entry in &dir_page.entries {
751 self.mark_inode_tree(entry.inode_ref.block_id)?;
752 }
753 }
754 }
755 InodeKind::File => {
756 if !inode.extent_map_ref.is_null() {
757 self.allocator
758 .mark_allocated(inode.extent_map_ref.block_id)?;
759 let extent_map: ExtentMap = self.read_obj(inode.extent_map_ref.block_id)?;
760 for entry in &extent_map.entries {
761 self.allocator.mark_allocated(entry.data_ref.block_id)?;
762 }
763 }
764 }
765 }
766 Ok(())
767 }
768}
769
770#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
772pub struct DirListEntry {
773 pub name: String,
774 pub kind: InodeKind,
775 pub size: u64,
776 pub inode_id: InodeId,
777}