1use crate::engine::error::{VfsError, VfsResult};
2use crate::engine::metadata::MetadataStore;
3use crate::engine::types::{
4 normalize_path, parent_and_name, BlockKey, ChunkEdit, ChunkRange, ChunkRef, CreateInodeAttrs,
5 DentryStat, InodeMeta, InodePatch, InodeType, SnapshotId, Storage, Timespec,
6 DEFAULT_CHUNK_SIZE, MAX_SYMLINK_DEPTH,
7};
8use async_trait::async_trait;
9use std::collections::{BTreeMap, BTreeSet};
10use std::sync::{Arc, Mutex};
11
12#[derive(Debug, Clone)]
13pub struct InMemoryMetadataStore {
14 state: Arc<Mutex<State>>,
15}
16
17#[derive(Debug, Clone)]
18pub struct MetadataDump {
19 pub next_ino: u64,
20 pub inodes: BTreeMap<u64, InodeMeta>,
21 pub dentries: BTreeMap<(u64, String), u64>,
22 pub chunks: BTreeMap<(u64, u64), ChunkRef>,
23 pub block_refs: BTreeMap<BlockKey, u64>,
24}
25
26#[derive(Debug, Clone)]
27struct State {
28 next_ino: u64,
29 next_snapshot_id: u64,
30 inodes: BTreeMap<u64, InodeMeta>,
31 dentries: BTreeMap<(u64, String), u64>,
32 chunks: BTreeMap<(u64, u64), ChunkRef>,
33 block_refs: BTreeMap<BlockKey, u64>,
34 snapshots: BTreeMap<SnapshotId, Snapshot>,
35}
36
37#[derive(Debug, Clone)]
38struct Snapshot {
39 root_ino: u64,
40 inodes: BTreeMap<u64, InodeMeta>,
41 dentries: BTreeMap<(u64, String), u64>,
42 chunks: BTreeMap<(u64, u64), ChunkRef>,
43}
44
45impl Default for InMemoryMetadataStore {
46 fn default() -> Self {
47 Self::new()
48 }
49}
50
51impl InMemoryMetadataStore {
52 pub const ROOT_INO: u64 = 1;
53
54 pub fn new() -> Self {
55 let now = Timespec::now();
56 let root = InodeMeta {
57 ino: Self::ROOT_INO,
58 kind: InodeType::Directory,
59 mode: 0o755,
60 uid: 0,
61 gid: 0,
62 size: 0,
63 nlink: 2,
64 atime: now,
65 mtime: now,
66 ctime: now,
67 birthtime: now,
68 storage: Storage::None,
69 symlink_target: None,
70 };
71 let mut inodes = BTreeMap::new();
72 inodes.insert(Self::ROOT_INO, root);
73 Self {
74 state: Arc::new(Mutex::new(State {
75 next_ino: 2,
76 next_snapshot_id: 1,
77 inodes,
78 dentries: BTreeMap::new(),
79 chunks: BTreeMap::new(),
80 block_refs: BTreeMap::new(),
81 snapshots: BTreeMap::new(),
82 })),
83 }
84 }
85
86 pub fn refcount(&self, key: &BlockKey) -> u64 {
87 self.state
88 .lock()
89 .expect("metadata mutex poisoned")
90 .block_refs
91 .get(key)
92 .copied()
93 .unwrap_or(0)
94 }
95
96 pub fn dump(&self) -> MetadataDump {
97 let state = self.state.lock().expect("metadata mutex poisoned");
98 MetadataDump {
99 next_ino: state.next_ino,
100 inodes: state.inodes.clone(),
101 dentries: state.dentries.clone(),
102 chunks: state.chunks.clone(),
103 block_refs: state.block_refs.clone(),
104 }
105 }
106
107 pub fn from_dump(dump: MetadataDump) -> Self {
108 Self {
109 state: Arc::new(Mutex::new(State {
110 next_ino: dump.next_ino,
111 next_snapshot_id: 1,
112 inodes: dump.inodes,
113 dentries: dump.dentries,
114 chunks: dump.chunks,
115 block_refs: dump.block_refs,
116 snapshots: BTreeMap::new(),
117 })),
118 }
119 }
120}
121
122impl State {
123 fn now_touch(meta: &mut InodeMeta) {
124 let now = Timespec::now();
125 meta.mtime = now;
126 meta.ctime = now;
127 }
128
129 fn inode(&self, ino: u64) -> VfsResult<InodeMeta> {
130 self.inodes
131 .get(&ino)
132 .cloned()
133 .ok_or_else(|| VfsError::enoent(format!("inode {ino}")))
134 }
135
136 fn alloc_inode(&mut self, attrs: CreateInodeAttrs) -> InodeMeta {
137 let ino = self.next_ino;
138 self.next_ino += 1;
139 let now = Timespec::now();
140 let size = match &attrs.storage {
141 Storage::Inline(data) => data.len() as u64,
142 Storage::Chunked { .. } | Storage::None => attrs
143 .symlink_target
144 .as_ref()
145 .map(|target| target.len() as u64)
146 .unwrap_or(0),
147 };
148 InodeMeta {
149 ino,
150 kind: attrs.kind,
151 mode: attrs.mode,
152 uid: attrs.uid,
153 gid: attrs.gid,
154 size,
155 nlink: if attrs.kind == InodeType::Directory {
156 2
157 } else {
158 1
159 },
160 atime: now,
161 mtime: now,
162 ctime: now,
163 birthtime: now,
164 storage: attrs.storage,
165 symlink_target: attrs.symlink_target,
166 }
167 }
168
169 fn name_child(&self, parent: u64, name: &str) -> VfsResult<u64> {
170 self.dentries
171 .get(&(parent, name.to_string()))
172 .copied()
173 .ok_or_else(|| VfsError::enoent(name))
174 }
175
176 fn resolve_path(&self, path: &str, follow_final: bool) -> VfsResult<InodeMeta> {
177 self.resolve_path_depth(path, follow_final, 0)
178 }
179
180 fn resolve_path_depth(
181 &self,
182 path: &str,
183 follow_final: bool,
184 depth: usize,
185 ) -> VfsResult<InodeMeta> {
186 if depth > MAX_SYMLINK_DEPTH {
187 return Err(VfsError::eloop(path));
188 }
189 let normalized = normalize_path(path)?;
190 if normalized == "/" {
191 return self.inode(InMemoryMetadataStore::ROOT_INO);
192 }
193
194 let parts: Vec<&str> = normalized.trim_start_matches('/').split('/').collect();
195 let mut current = InMemoryMetadataStore::ROOT_INO;
196 let mut prefix = String::new();
197 for (idx, part) in parts.iter().enumerate() {
198 let parent_meta = self.inode(current)?;
199 if parent_meta.kind != InodeType::Directory {
200 return Err(VfsError::enotdir(&prefix));
201 }
202 let child = self.name_child(current, part)?;
203 let meta = self.inode(child)?;
204 let final_component = idx == parts.len() - 1;
205 if meta.kind == InodeType::Symlink && (!final_component || follow_final) {
206 let target = meta.symlink_target.clone().unwrap_or_default();
207 let rest = parts[idx + 1..].join("/");
208 let base = if prefix.is_empty() { "/" } else { &prefix };
209 let next_path = resolve_symlink_target(base, &target, &rest)?;
210 return self.resolve_path_depth(&next_path, follow_final, depth + 1);
211 }
212 current = child;
213 if prefix == "/" || prefix.is_empty() {
214 prefix = format!("/{part}");
215 } else {
216 prefix.push('/');
217 prefix.push_str(part);
218 }
219 }
220 self.inode(current)
221 }
222
223 fn collect_subtree(&self, root: u64) -> VfsResult<BTreeSet<u64>> {
224 let mut seen = BTreeSet::new();
225 self.collect_subtree_into(root, &mut seen)?;
226 Ok(seen)
227 }
228
229 fn collect_subtree_into(&self, ino: u64, seen: &mut BTreeSet<u64>) -> VfsResult<()> {
230 if !seen.insert(ino) {
231 return Ok(());
232 }
233 let meta = self.inode(ino)?;
234 if meta.kind == InodeType::Directory {
235 for ((parent, _), child) in self.dentries.range((ino, String::new())..) {
236 if *parent != ino {
237 break;
238 }
239 self.collect_subtree_into(*child, seen)?;
240 }
241 }
242 Ok(())
243 }
244
245 fn dec_block_ref(&mut self, key: &BlockKey, freed: &mut Vec<BlockKey>) {
246 if let Some(refcount) = self.block_refs.get_mut(key) {
247 *refcount = refcount.saturating_sub(1);
248 if *refcount == 0 {
249 self.block_refs.remove(key);
250 freed.push(key.clone());
251 }
252 }
253 }
254
255 fn inc_block_ref(&mut self, key: &BlockKey) {
256 *self.block_refs.entry(key.clone()).or_insert(0) += 1;
257 }
258
259 fn drop_inode_content(&mut self, ino: u64, freed: &mut Vec<BlockKey>) {
260 let keys: Vec<BlockKey> = self
261 .chunks
262 .range((ino, 0)..)
263 .take_while(|((chunk_ino, _), _)| *chunk_ino == ino)
264 .map(|(_, chunk)| chunk.key.clone())
265 .collect();
266 self.chunks.retain(|(chunk_ino, _), _| *chunk_ino != ino);
267 for key in keys {
268 self.dec_block_ref(&key, freed);
269 }
270 }
271
272 fn remove_child(&mut self, parent: u64, name: &str) -> VfsResult<Vec<BlockKey>> {
273 let child = self.name_child(parent, name)?;
274 let meta = self.inode(child)?;
275 if meta.kind == InodeType::Directory
276 && self
277 .dentries
278 .range((child, String::new())..)
279 .next()
280 .is_some_and(|((candidate_parent, _), _)| *candidate_parent == child)
281 {
282 return Err(VfsError::enotempty(name));
283 }
284 self.dentries.remove(&(parent, name.to_string()));
285 let mut freed = Vec::new();
286 if let Some(child_meta) = self.inodes.get_mut(&child) {
287 child_meta.nlink = child_meta.nlink.saturating_sub(1);
288 child_meta.ctime = Timespec::now();
289 if child_meta.nlink > 0 {
290 return Ok(freed);
291 }
292 }
293 self.drop_inode_content(child, &mut freed);
294 self.inodes.remove(&child);
295 Ok(freed)
296 }
297}
298
299fn resolve_symlink_target(base: &str, target: &str, rest: &str) -> VfsResult<String> {
300 let mut path = if target.starts_with('/') {
301 target.to_string()
302 } else if base == "/" {
303 format!("/{target}")
304 } else {
305 format!("{base}/{target}")
306 };
307 if !rest.is_empty() {
308 if path != "/" {
309 path.push('/');
310 }
311 path.push_str(rest);
312 }
313 normalize_path(&path)
314}
315
316#[async_trait]
317impl MetadataStore for InMemoryMetadataStore {
318 async fn resolve(&self, path: &str) -> VfsResult<InodeMeta> {
319 self.state
320 .lock()
321 .expect("metadata mutex poisoned")
322 .resolve_path(path, true)
323 }
324
325 async fn resolve_parent(&self, path: &str) -> VfsResult<(InodeMeta, String)> {
326 let (parent, name) = parent_and_name(path)?;
327 let parent_meta = self
328 .state
329 .lock()
330 .expect("metadata mutex poisoned")
331 .resolve_path(&parent, true)?;
332 if parent_meta.kind != InodeType::Directory {
333 return Err(VfsError::enotdir(parent));
334 }
335 Ok((parent_meta, name))
336 }
337
338 async fn lstat(&self, path: &str) -> VfsResult<InodeMeta> {
339 self.state
340 .lock()
341 .expect("metadata mutex poisoned")
342 .resolve_path(path, false)
343 }
344
345 async fn list_dir(&self, ino: u64) -> VfsResult<Vec<DentryStat>> {
346 let state = self.state.lock().expect("metadata mutex poisoned");
347 let meta = state.inode(ino)?;
348 if meta.kind != InodeType::Directory {
349 return Err(VfsError::enotdir(format!("inode {ino}")));
350 }
351 let mut entries = Vec::new();
352 for ((parent, name), child) in state.dentries.range((ino, String::new())..) {
353 if *parent != ino {
354 break;
355 }
356 entries.push(DentryStat {
357 name: name.clone(),
358 meta: state.inode(*child)?,
359 });
360 }
361 Ok(entries)
362 }
363
364 async fn create(
365 &self,
366 parent: u64,
367 name: &str,
368 attrs: CreateInodeAttrs,
369 ) -> VfsResult<InodeMeta> {
370 let mut state = self.state.lock().expect("metadata mutex poisoned");
371 let mut parent_meta = state.inode(parent)?;
372 if parent_meta.kind != InodeType::Directory {
373 return Err(VfsError::enotdir(format!("inode {parent}")));
374 }
375 if state.dentries.contains_key(&(parent, name.to_string())) {
376 return Err(VfsError::eexist(name));
377 }
378 let meta = state.alloc_inode(attrs);
379 let ino = meta.ino;
380 state.inodes.insert(ino, meta.clone());
381 state.dentries.insert((parent, name.to_string()), ino);
382 State::now_touch(&mut parent_meta);
383 state.inodes.insert(parent, parent_meta);
384 Ok(meta)
385 }
386
387 async fn link(&self, parent: u64, name: &str, target: u64) -> VfsResult<()> {
388 let mut state = self.state.lock().expect("metadata mutex poisoned");
389 let parent_meta = state.inode(parent)?;
390 if parent_meta.kind != InodeType::Directory {
391 return Err(VfsError::enotdir(format!("inode {parent}")));
392 }
393 if state.dentries.contains_key(&(parent, name.to_string())) {
394 return Err(VfsError::eexist(name));
395 }
396 let target_meta = state
397 .inodes
398 .get_mut(&target)
399 .ok_or_else(|| VfsError::enoent(format!("inode {target}")))?;
400 if target_meta.kind == InodeType::Directory {
401 return Err(VfsError::eopnotsupp(
402 "hard links to directories are not supported",
403 ));
404 }
405 target_meta.nlink += 1;
406 target_meta.ctime = Timespec::now();
407 state.dentries.insert((parent, name.to_string()), target);
408 Ok(())
409 }
410
411 async fn remove(&self, parent: u64, name: &str) -> VfsResult<Vec<BlockKey>> {
412 self.state
413 .lock()
414 .expect("metadata mutex poisoned")
415 .remove_child(parent, name)
416 }
417
418 async fn rename(
419 &self,
420 src_parent: u64,
421 src: &str,
422 dst_parent: u64,
423 dst: &str,
424 ) -> VfsResult<Vec<BlockKey>> {
425 let mut state = self.state.lock().expect("metadata mutex poisoned");
426 let child = state.name_child(src_parent, src)?;
427 let source_meta = state.inode(child)?;
428 if source_meta.kind == InodeType::Directory {
429 let descendant = state.collect_subtree(child)?.contains(&dst_parent);
430 if descendant {
431 return Err(VfsError::einval("cannot move a directory into itself"));
432 }
433 }
434 let mut freed = Vec::new();
435 if state.dentries.contains_key(&(dst_parent, dst.to_string())) {
436 freed = state.remove_child(dst_parent, dst)?;
437 }
438 state.dentries.remove(&(src_parent, src.to_string()));
439 state.dentries.insert((dst_parent, dst.to_string()), child);
440 Ok(freed)
441 }
442
443 async fn set_attr(&self, ino: u64, patch: InodePatch) -> VfsResult<Vec<BlockKey>> {
444 let mut state = self.state.lock().expect("metadata mutex poisoned");
445 let mut freed = Vec::new();
446 if let Some(storage) = &patch.storage {
447 if matches!(storage, Storage::Inline(_) | Storage::None) {
448 state.drop_inode_content(ino, &mut freed);
449 }
450 }
451 let meta = state
452 .inodes
453 .get_mut(&ino)
454 .ok_or_else(|| VfsError::enoent(format!("inode {ino}")))?;
455 if let Some(mode) = patch.mode {
456 meta.mode = mode;
457 }
458 if let Some(uid) = patch.uid {
459 meta.uid = uid;
460 }
461 if let Some(gid) = patch.gid {
462 meta.gid = gid;
463 }
464 if let Some(atime) = patch.atime {
465 meta.atime = atime;
466 }
467 if let Some(mtime) = patch.mtime {
468 meta.mtime = mtime;
469 }
470 if let Some(storage) = patch.storage {
471 meta.size = match &storage {
472 Storage::Inline(data) => data.len() as u64,
473 Storage::Chunked { .. } => patch.size.unwrap_or(meta.size),
474 Storage::None => meta
475 .symlink_target
476 .as_ref()
477 .map_or(0, |target| target.len() as u64),
478 };
479 meta.storage = storage;
480 }
481 if let Some(size) = patch.size {
482 meta.size = size;
483 }
484 meta.ctime = Timespec::now();
485 Ok(freed)
486 }
487
488 async fn commit_write(
489 &self,
490 ino: u64,
491 edits: Vec<ChunkEdit>,
492 new_size: u64,
493 ) -> VfsResult<Vec<BlockKey>> {
494 let mut state = self.state.lock().expect("metadata mutex poisoned");
495 let meta = state.inode(ino)?;
496 let chunk_size = match meta.storage {
497 Storage::Chunked { chunk_size } => u64::from(chunk_size),
498 Storage::Inline(_) | Storage::None => DEFAULT_CHUNK_SIZE as u64,
499 };
500 let mut freed = Vec::new();
501 let keep_chunks = if new_size == 0 {
502 0
503 } else {
504 new_size.div_ceil(chunk_size)
505 };
506 let truncated: Vec<(u64, BlockKey)> = state
507 .chunks
508 .range((ino, 0)..)
509 .take_while(|((chunk_ino, _), _)| *chunk_ino == ino)
510 .filter(|((_, index), _)| *index >= keep_chunks)
511 .map(|((_, index), chunk)| (*index, chunk.key.clone()))
512 .collect();
513 for (index, key) in truncated {
514 state.chunks.remove(&(ino, index));
515 state.dec_block_ref(&key, &mut freed);
516 }
517 for edit in edits {
518 if edit.index >= keep_chunks {
519 continue;
520 }
521 let key = edit.key.clone();
522 if let Some(previous) = state.chunks.insert(
523 (ino, edit.index),
524 ChunkRef {
525 index: edit.index,
526 key: edit.key,
527 len: edit.len,
528 },
529 ) {
530 if previous.key != key {
531 state.dec_block_ref(&previous.key, &mut freed);
532 state.inc_block_ref(&key);
533 }
534 } else {
535 state.inc_block_ref(&key);
536 }
537 }
538 let meta = state
539 .inodes
540 .get_mut(&ino)
541 .ok_or_else(|| VfsError::enoent(format!("inode {ino}")))?;
542 meta.size = new_size;
543 meta.ctime = Timespec::now();
544 meta.mtime = meta.ctime;
545 Ok(freed)
546 }
547
548 async fn get_chunks(&self, ino: u64, range: ChunkRange) -> VfsResult<Vec<ChunkRef>> {
549 let state = self.state.lock().expect("metadata mutex poisoned");
550 state.inode(ino)?;
551 let mut chunks = Vec::new();
552 for ((chunk_ino, index), chunk) in state.chunks.range((ino, range.start)..) {
553 if *chunk_ino != ino {
554 break;
555 }
556 if let Some(end) = range.end {
557 if *index >= end {
558 break;
559 }
560 }
561 chunks.push(chunk.clone());
562 }
563 Ok(chunks)
564 }
565
566 async fn snapshot(&self, root: u64) -> VfsResult<SnapshotId> {
567 let mut state = self.state.lock().expect("metadata mutex poisoned");
568 let reachable = state.collect_subtree(root)?;
569 let mut inodes = BTreeMap::new();
570 let mut dentries = BTreeMap::new();
571 let mut chunks = BTreeMap::new();
572 let mut keys_to_inc = Vec::new();
573 for ino in reachable {
574 inodes.insert(ino, state.inode(ino)?);
575 for ((parent, name), child) in state.dentries.range((ino, String::new())..) {
576 if *parent != ino {
577 break;
578 }
579 dentries.insert((*parent, name.clone()), *child);
580 }
581 for ((chunk_ino, index), chunk) in &state.chunks {
582 if *chunk_ino == ino {
583 chunks.insert((*chunk_ino, *index), chunk.clone());
584 keys_to_inc.push(chunk.key.clone());
585 }
586 }
587 }
588 for key in keys_to_inc {
589 state.inc_block_ref(&key);
590 }
591 let id = SnapshotId(state.next_snapshot_id);
592 state.next_snapshot_id += 1;
593 state.snapshots.insert(
594 id,
595 Snapshot {
596 root_ino: root,
597 inodes,
598 dentries,
599 chunks,
600 },
601 );
602 Ok(id)
603 }
604
605 async fn fork(&self, snap: SnapshotId) -> VfsResult<u64> {
606 let mut state = self.state.lock().expect("metadata mutex poisoned");
607 let snapshot = state
608 .snapshots
609 .get(&snap)
610 .cloned()
611 .ok_or_else(|| VfsError::enoent(format!("snapshot {}", snap.0)))?;
612 let mut map = BTreeMap::new();
613 for old_ino in snapshot.inodes.keys() {
614 let new_ino = state.next_ino;
615 state.next_ino += 1;
616 map.insert(*old_ino, new_ino);
617 }
618 for (old_ino, mut meta) in snapshot.inodes {
619 meta.ino = map[&old_ino];
620 state.inodes.insert(meta.ino, meta);
621 }
622 for ((old_parent, name), old_child) in snapshot.dentries {
623 if let (Some(parent), Some(child)) = (map.get(&old_parent), map.get(&old_child)) {
624 state.dentries.insert((*parent, name), *child);
625 }
626 }
627 for ((old_ino, index), mut chunk) in snapshot.chunks {
628 if let Some(new_ino) = map.get(&old_ino) {
629 let key = chunk.key.clone();
630 chunk.index = index;
631 state.chunks.insert((*new_ino, index), chunk);
632 state.inc_block_ref(&key);
633 }
634 }
635 Ok(map[&snapshot.root_ino])
636 }
637
638 async fn gc(&self) -> VfsResult<Vec<BlockKey>> {
639 Ok(Vec::new())
640 }
641}