Skip to main content

vfs/engine/mem/
metadata_store.rs

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        // A range shift can release a content-addressed block at one index and
624        // reintroduce the same key at another index in this commit. Only return
625        // keys that are still unreferenced after every edit has been applied.
626        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}