Skip to main content

embednfs/
memfs.rs

1/// In-memory filesystem implementation.
2///
3/// Provides a fully functional in-memory filesystem for testing and as
4/// a reference implementation of the public filesystem traits.
5use async_trait::async_trait;
6use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use tokio::sync::RwLock;
9
10use crate::fs::*;
11
12/// An in-memory filesystem.
13pub struct MemFs {
14    inner: RwLock<MemFsInner>,
15    next_id: AtomicU64,
16}
17
18struct MemFsInner {
19    inodes: HashMap<FileId, Inode>,
20}
21
22struct Inode {
23    kind: NodeKind,
24    data: InodeData,
25    xattrs: HashMap<String, Vec<u8>>,
26}
27
28enum InodeData {
29    File(Vec<u8>),
30    Directory(HashMap<String, FileId>),
31    Symlink(String),
32}
33
34impl MemFs {
35    /// Create a new in-memory filesystem with an empty root directory.
36    pub fn new() -> Self {
37        let mut inodes = HashMap::new();
38        inodes.insert(
39            1,
40            Inode {
41                kind: NodeKind::Directory,
42                data: InodeData::Directory(HashMap::new()),
43                xattrs: HashMap::new(),
44            },
45        );
46
47        MemFs {
48            inner: RwLock::new(MemFsInner { inodes }),
49            next_id: AtomicU64::new(2),
50        }
51    }
52
53    fn next_id(&self) -> FileId {
54        self.next_id.fetch_add(1, Ordering::Relaxed)
55    }
56
57    fn inode_size(inode: &Inode) -> u64 {
58        match &inode.data {
59            InodeData::File(data) => data.len() as u64,
60            InodeData::Directory(entries) => entries.len() as u64,
61            InodeData::Symlink(target) => target.len() as u64,
62        }
63    }
64
65    fn has_remaining_links(inner: &MemFsInner, target: FileId) -> bool {
66        inner.inodes.values().any(|inode| match &inode.data {
67            InodeData::Directory(entries) => entries.values().any(|id| *id == target),
68            _ => false,
69        })
70    }
71}
72
73#[async_trait]
74impl NfsFileSystem for MemFs {
75    async fn stat(&self, id: FileId) -> NfsResult<NodeInfo> {
76        let inner = self.inner.read().await;
77        let inode = inner.inodes.get(&id).ok_or(NfsError::Stale)?;
78        Ok(NodeInfo {
79            kind: inode.kind,
80            size: Self::inode_size(inode),
81        })
82    }
83
84    async fn lookup(&self, dir_id: FileId, name: &str) -> NfsResult<FileId> {
85        let inner = self.inner.read().await;
86        let inode = inner.inodes.get(&dir_id).ok_or(NfsError::Stale)?;
87        match &inode.data {
88            InodeData::Directory(entries) => entries.get(name).copied().ok_or(NfsError::Noent),
89            _ => Err(NfsError::Notdir),
90        }
91    }
92
93    async fn lookup_parent(&self, id: FileId) -> NfsResult<FileId> {
94        if id == 1 {
95            return Ok(1);
96        }
97
98        let inner = self.inner.read().await;
99        for (dir_id, inode) in &inner.inodes {
100            if let InodeData::Directory(entries) = &inode.data
101                && entries.values().any(|child| *child == id)
102            {
103                return Ok(*dir_id);
104            }
105        }
106
107        Err(NfsError::Noent)
108    }
109
110    async fn readdir(&self, dir_id: FileId) -> NfsResult<Vec<DirEntry>> {
111        let inner = self.inner.read().await;
112        let inode = inner.inodes.get(&dir_id).ok_or(NfsError::Stale)?;
113        match &inode.data {
114            InodeData::Directory(entries) => {
115                let mut result: Vec<DirEntry> = entries
116                    .iter()
117                    .map(|(name, fileid)| DirEntry {
118                        fileid: *fileid,
119                        name: name.clone(),
120                    })
121                    .collect();
122                result.sort_by(|a, b| a.name.cmp(&b.name));
123                Ok(result)
124            }
125            _ => Err(NfsError::Notdir),
126        }
127    }
128
129    async fn read(&self, id: FileId, offset: u64, count: u32) -> NfsResult<(Vec<u8>, bool)> {
130        let inner = self.inner.read().await;
131        let inode = inner.inodes.get(&id).ok_or(NfsError::Stale)?;
132        match &inode.data {
133            InodeData::File(data) => {
134                let offset = offset as usize;
135                if offset >= data.len() {
136                    return Ok((vec![], true));
137                }
138                let end = (offset + count as usize).min(data.len());
139                Ok((data[offset..end].to_vec(), end == data.len()))
140            }
141            _ => Err(NfsError::Inval),
142        }
143    }
144
145    async fn write(&self, id: FileId, offset: u64, data: &[u8]) -> NfsResult<u32> {
146        let mut inner = self.inner.write().await;
147        let inode = inner.inodes.get_mut(&id).ok_or(NfsError::Stale)?;
148        match &mut inode.data {
149            InodeData::File(file_data) => {
150                let offset = offset as usize;
151                let end = offset + data.len();
152                if end > file_data.len() {
153                    file_data.resize(end, 0);
154                }
155                file_data[offset..end].copy_from_slice(data);
156                Ok(data.len() as u32)
157            }
158            _ => Err(NfsError::Inval),
159        }
160    }
161
162    async fn truncate(&self, id: FileId, size: u64) -> NfsResult<()> {
163        let mut inner = self.inner.write().await;
164        let inode = inner.inodes.get_mut(&id).ok_or(NfsError::Stale)?;
165        match &mut inode.data {
166            InodeData::File(file_data) => {
167                file_data.resize(size as usize, 0);
168                Ok(())
169            }
170            _ => Err(NfsError::Inval),
171        }
172    }
173
174    async fn create_file(&self, dir_id: FileId, name: &str) -> NfsResult<FileId> {
175        let new_id = self.next_id();
176        let mut inner = self.inner.write().await;
177
178        let dir = inner.inodes.get_mut(&dir_id).ok_or(NfsError::Stale)?;
179        match &mut dir.data {
180            InodeData::Directory(entries) => {
181                if entries.contains_key(name) {
182                    return Err(NfsError::Exist);
183                }
184                entries.insert(name.to_string(), new_id);
185            }
186            _ => return Err(NfsError::Notdir),
187        }
188
189        inner.inodes.insert(
190            new_id,
191            Inode {
192                kind: NodeKind::File,
193                data: InodeData::File(Vec::new()),
194                xattrs: HashMap::new(),
195            },
196        );
197
198        Ok(new_id)
199    }
200
201    async fn create_dir(&self, dir_id: FileId, name: &str) -> NfsResult<FileId> {
202        let new_id = self.next_id();
203        let mut inner = self.inner.write().await;
204
205        let dir = inner.inodes.get_mut(&dir_id).ok_or(NfsError::Stale)?;
206        match &mut dir.data {
207            InodeData::Directory(entries) => {
208                if entries.contains_key(name) {
209                    return Err(NfsError::Exist);
210                }
211                entries.insert(name.to_string(), new_id);
212            }
213            _ => return Err(NfsError::Notdir),
214        }
215
216        inner.inodes.insert(
217            new_id,
218            Inode {
219                kind: NodeKind::Directory,
220                data: InodeData::Directory(HashMap::new()),
221                xattrs: HashMap::new(),
222            },
223        );
224
225        Ok(new_id)
226    }
227
228    async fn remove(&self, dir_id: FileId, name: &str) -> NfsResult<()> {
229        let mut inner = self.inner.write().await;
230
231        let child_id = {
232            let dir = inner.inodes.get(&dir_id).ok_or(NfsError::Stale)?;
233            match &dir.data {
234                InodeData::Directory(entries) => *entries.get(name).ok_or(NfsError::Noent)?,
235                _ => return Err(NfsError::Notdir),
236            }
237        };
238
239        if let Some(child) = inner.inodes.get(&child_id)
240            && let InodeData::Directory(entries) = &child.data
241            && !entries.is_empty()
242        {
243            return Err(NfsError::Notempty);
244        }
245
246        let dir = inner.inodes.get_mut(&dir_id).unwrap();
247        if let InodeData::Directory(entries) = &mut dir.data {
248            entries.remove(name);
249        }
250
251        if !Self::has_remaining_links(&inner, child_id) {
252            inner.inodes.remove(&child_id);
253        }
254
255        Ok(())
256    }
257
258    async fn rename(
259        &self,
260        from_dir: FileId,
261        from_name: &str,
262        to_dir: FileId,
263        to_name: &str,
264    ) -> NfsResult<()> {
265        let mut inner = self.inner.write().await;
266
267        let child_id = {
268            let dir = inner.inodes.get(&from_dir).ok_or(NfsError::Stale)?;
269            match &dir.data {
270                InodeData::Directory(entries) => *entries.get(from_name).ok_or(NfsError::Noent)?,
271                _ => return Err(NfsError::Notdir),
272            }
273        };
274
275        {
276            let dir = inner.inodes.get_mut(&from_dir).ok_or(NfsError::Stale)?;
277            if let InodeData::Directory(entries) = &mut dir.data {
278                entries.remove(from_name);
279            }
280        }
281
282        let removed_target = {
283            let tgt = inner.inodes.get_mut(&to_dir).ok_or(NfsError::Stale)?;
284            match &mut tgt.data {
285                InodeData::Directory(entries) => entries.insert(to_name.to_string(), child_id),
286                _ => return Err(NfsError::Notdir),
287            }
288        };
289
290        if let Some(old_id) = removed_target
291            && !Self::has_remaining_links(&inner, old_id)
292        {
293            inner.inodes.remove(&old_id);
294        }
295
296        Ok(())
297    }
298
299    fn symlinks(&self) -> Option<&dyn NfsSymlinks> {
300        Some(self)
301    }
302
303    fn hard_links(&self) -> Option<&dyn NfsHardLinks> {
304        Some(self)
305    }
306
307    fn named_attrs(&self) -> Option<&dyn NfsNamedAttrs> {
308        Some(self)
309    }
310
311    fn syncer(&self) -> Option<&dyn NfsSync> {
312        Some(self)
313    }
314}
315
316#[async_trait]
317impl NfsSymlinks for MemFs {
318    async fn symlink(&self, dir_id: FileId, name: &str, target: &str) -> NfsResult<FileId> {
319        let new_id = self.next_id();
320        let mut inner = self.inner.write().await;
321
322        let dir = inner.inodes.get_mut(&dir_id).ok_or(NfsError::Stale)?;
323        match &mut dir.data {
324            InodeData::Directory(entries) => {
325                if entries.contains_key(name) {
326                    return Err(NfsError::Exist);
327                }
328                entries.insert(name.to_string(), new_id);
329            }
330            _ => return Err(NfsError::Notdir),
331        }
332
333        inner.inodes.insert(
334            new_id,
335            Inode {
336                kind: NodeKind::Symlink,
337                data: InodeData::Symlink(target.to_string()),
338                xattrs: HashMap::new(),
339            },
340        );
341
342        Ok(new_id)
343    }
344
345    async fn readlink(&self, id: FileId) -> NfsResult<String> {
346        let inner = self.inner.read().await;
347        let inode = inner.inodes.get(&id).ok_or(NfsError::Stale)?;
348        match &inode.data {
349            InodeData::Symlink(target) => Ok(target.clone()),
350            _ => Err(NfsError::Inval),
351        }
352    }
353}
354
355#[async_trait]
356impl NfsHardLinks for MemFs {
357    async fn link(&self, id: FileId, dir_id: FileId, name: &str) -> NfsResult<()> {
358        let mut inner = self.inner.write().await;
359        let inode = inner.inodes.get(&id).ok_or(NfsError::Stale)?;
360        if inode.kind == NodeKind::Directory {
361            return Err(NfsError::Isdir);
362        }
363
364        let dir = inner.inodes.get_mut(&dir_id).ok_or(NfsError::Stale)?;
365        match &mut dir.data {
366            InodeData::Directory(entries) => {
367                if entries.contains_key(name) {
368                    return Err(NfsError::Exist);
369                }
370                entries.insert(name.to_string(), id);
371                Ok(())
372            }
373            _ => Err(NfsError::Notdir),
374        }
375    }
376}
377
378#[async_trait]
379impl NfsNamedAttrs for MemFs {
380    async fn list_xattrs(&self, id: FileId) -> NfsResult<Vec<String>> {
381        let inner = self.inner.read().await;
382        let inode = inner.inodes.get(&id).ok_or(NfsError::Stale)?;
383        let mut names: Vec<String> = inode.xattrs.keys().cloned().collect();
384        names.sort();
385        Ok(names)
386    }
387
388    async fn get_xattr(&self, id: FileId, name: &str) -> NfsResult<Vec<u8>> {
389        let inner = self.inner.read().await;
390        let inode = inner.inodes.get(&id).ok_or(NfsError::Stale)?;
391        inode.xattrs.get(name).cloned().ok_or(NfsError::Noent)
392    }
393
394    async fn set_xattr(
395        &self,
396        id: FileId,
397        name: &str,
398        value: &[u8],
399        mode: XattrSetMode,
400    ) -> NfsResult<()> {
401        let mut inner = self.inner.write().await;
402        let inode = inner.inodes.get_mut(&id).ok_or(NfsError::Stale)?;
403        let exists = inode.xattrs.contains_key(name);
404        match mode {
405            XattrSetMode::CreateOrReplace => {}
406            XattrSetMode::CreateOnly if exists => return Err(NfsError::Exist),
407            XattrSetMode::ReplaceOnly if !exists => return Err(NfsError::Noent),
408            XattrSetMode::CreateOnly | XattrSetMode::ReplaceOnly => {}
409        }
410        inode.xattrs.insert(name.to_string(), value.to_vec());
411        Ok(())
412    }
413
414    async fn remove_xattr(&self, id: FileId, name: &str) -> NfsResult<()> {
415        let mut inner = self.inner.write().await;
416        let inode = inner.inodes.get_mut(&id).ok_or(NfsError::Stale)?;
417        inode
418            .xattrs
419            .remove(name)
420            .map(|_| ())
421            .ok_or(NfsError::Noent)
422    }
423}
424
425#[async_trait]
426impl NfsSync for MemFs {
427    async fn commit(&self, _id: FileId) -> NfsResult<()> {
428        Ok(())
429    }
430}
431
432impl Default for MemFs {
433    fn default() -> Self {
434        Self::new()
435    }
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441
442    #[tokio::test]
443    async fn test_create_and_read() {
444        let fs = MemFs::new();
445        let id = fs.create_file(1, "test.txt").await.unwrap();
446        let written = fs.write(id, 0, b"hello world").await.unwrap();
447        assert_eq!(written, 11);
448        let (data, eof) = fs.read(id, 0, 1024).await.unwrap();
449        assert_eq!(data, b"hello world");
450        assert!(eof);
451    }
452
453    #[tokio::test]
454    async fn test_mkdir_and_readdir() {
455        let fs = MemFs::new();
456        let dir_id = fs.create_dir(1, "subdir").await.unwrap();
457        let entries = fs.readdir(1).await.unwrap();
458        assert_eq!(entries.len(), 1);
459        assert_eq!(entries[0].name, "subdir");
460        assert_eq!(entries[0].fileid, dir_id);
461    }
462
463    #[tokio::test]
464    async fn test_remove() {
465        let fs = MemFs::new();
466        let _id = fs.create_file(1, "to_delete.txt").await.unwrap();
467        fs.remove(1, "to_delete.txt").await.unwrap();
468        assert!(fs.lookup(1, "to_delete.txt").await.is_err());
469    }
470
471    #[tokio::test]
472    async fn test_rename() {
473        let fs = MemFs::new();
474        fs.create_file(1, "old.txt").await.unwrap();
475        fs.rename(1, "old.txt", 1, "new.txt").await.unwrap();
476        assert!(fs.lookup(1, "old.txt").await.is_err());
477        assert!(fs.lookup(1, "new.txt").await.is_ok());
478    }
479
480    #[tokio::test]
481    async fn test_named_attrs_roundtrip() {
482        let fs = MemFs::new();
483        let id = fs.create_file(1, "notes.txt").await.unwrap();
484
485        fs.set_xattr(id, "user.demo", b"value", XattrSetMode::CreateOnly)
486            .await
487            .unwrap();
488        assert_eq!(fs.get_xattr(id, "user.demo").await.unwrap(), b"value");
489        assert_eq!(fs.list_xattrs(id).await.unwrap(), vec!["user.demo".to_string()]);
490
491        fs.remove_xattr(id, "user.demo").await.unwrap();
492        assert!(matches!(fs.get_xattr(id, "user.demo").await, Err(NfsError::Noent)));
493    }
494}