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