1use async_trait::async_trait;
6use std::collections::HashMap;
7use std::sync::atomic::{AtomicU64, Ordering};
8use tokio::sync::RwLock;
9
10use crate::fs::*;
11
12pub 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 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}