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 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}