1mod config;
31
32use std::collections::{HashMap, HashSet};
33use std::sync::atomic::AtomicU64;
34use std::sync::{Arc, RwLock};
35use std::time::SystemTime;
36
37pub use config::MemFsConfig;
38use nfs3_types::nfs3::{
39 self as nfs, cookie3, createverf3, entryplus3, fattr3, filename3, ftype3, nfspath3, nfsstat3,
40 nfstime3, sattr3, specdata3,
41};
42use nfs3_types::xdr_codec::Opaque;
43
44use crate::vfs::{
45 FileHandleU64, NextResult, NfsFileSystem, NfsReadFileSystem, ReadDirIterator,
46 ReadDirPlusIterator,
47};
48
49const DELIMITER: char = '/';
50
51#[derive(Debug)]
52struct Dir {
53 name: filename3<'static>,
54 parent: FileHandleU64,
55 attr: fattr3,
56 content: HashSet<FileHandleU64>,
57}
58
59impl Dir {
60 fn new(name: filename3<'static>, id: FileHandleU64, parent: FileHandleU64) -> Self {
61 let current_time = current_time();
62 let attr = fattr3 {
63 type_: ftype3::NF3DIR,
64 mode: 0o777,
65 nlink: 1,
66 uid: 507,
67 gid: 507,
68 size: 0,
69 used: 0,
70 rdev: specdata3::default(),
71 fsid: 0,
72 fileid: id.into(),
73 atime: current_time.clone(),
74 mtime: current_time.clone(),
75 ctime: current_time,
76 };
77 Self {
78 name,
79 parent,
80 attr,
81 content: HashSet::new(),
82 }
83 }
84
85 fn root_dir() -> Self {
86 let name = filename3(Opaque::borrowed(b"/"));
87 let id = 1.into();
88 Self::new(name, id, 0.into())
89 }
90
91 fn add_entry(&mut self, entry: FileHandleU64) -> bool {
92 self.content.insert(entry)
93 }
94}
95
96#[derive(Debug)]
97struct File {
98 name: filename3<'static>,
99 attr: fattr3,
100 content: Vec<u8>,
101 verf: createverf3,
102}
103
104impl File {
105 fn new(
106 name: filename3<'static>,
107 id: FileHandleU64,
108 content: Vec<u8>,
109 verf: createverf3,
110 ) -> Self {
111 let current_time = current_time();
112 let attr = fattr3 {
113 type_: ftype3::NF3REG,
114 mode: 0o755,
115 nlink: 1,
116 uid: 507,
117 gid: 507,
118 size: content.len() as u64,
119 used: content.len() as u64,
120 rdev: specdata3::default(),
121 fsid: 0,
122 fileid: id.into(),
123 atime: current_time.clone(),
124 mtime: current_time.clone(),
125 ctime: current_time,
126 };
127 Self {
128 name,
129 attr,
130 content,
131 verf,
132 }
133 }
134
135 fn fileid(&self) -> FileHandleU64 {
136 self.attr.fileid.into()
137 }
138
139 fn resize(&mut self, size: u64) {
140 self.content
141 .resize(usize::try_from(size).expect("size is too large"), 0);
142 self.attr.size = size;
143 self.attr.used = size;
144 }
145
146 fn read(&self, offset: u64, count: u32) -> (Vec<u8>, bool) {
147 let mut start = usize::try_from(offset).unwrap_or(usize::MAX);
148 let mut end = start + count as usize;
149 let bytes = &self.content;
150 let eof = end >= bytes.len();
151 if start >= bytes.len() {
152 start = bytes.len();
153 }
154 if end > bytes.len() {
155 end = bytes.len();
156 }
157 (bytes[start..end].to_vec(), eof)
158 }
159
160 #[allow(clippy::cast_possible_truncation)]
161 fn write(&mut self, offset: u64, data: &[u8]) -> Result<fattr3, nfsstat3> {
162 if offset > self.content.len() as u64 {
163 return Err(nfsstat3::NFS3ERR_INVAL);
164 }
165
166 let offset = offset as usize;
167 let end_offset = offset + data.len();
168 if end_offset > self.content.len() {
169 self.resize(end_offset as u64);
170 }
171 self.content[offset..end_offset].copy_from_slice(data);
172 Ok(self.attr.clone())
173 }
174}
175
176fn current_time() -> nfstime3 {
177 let d = SystemTime::now()
178 .duration_since(SystemTime::UNIX_EPOCH)
179 .expect("failed to get current time");
180 nfstime3 {
181 seconds: u32::try_from(d.as_secs()).unwrap_or(u32::MAX),
182 nseconds: d.subsec_nanos(),
183 }
184}
185
186#[derive(Debug)]
187enum Entry {
188 File(File),
189 Dir(Dir),
190}
191
192impl Entry {
193 fn new_file(
194 name: filename3<'static>,
195 parent: FileHandleU64,
196 content: Vec<u8>,
197 verf: createverf3,
198 ) -> Self {
199 Self::File(File::new(name, parent, content, verf))
200 }
201
202 fn new_dir(name: filename3<'static>, id: FileHandleU64, parent: FileHandleU64) -> Self {
203 Self::Dir(Dir::new(name, id, parent))
204 }
205
206 const fn as_dir(&self) -> Result<&Dir, nfsstat3> {
207 match self {
208 Self::Dir(dir) => Ok(dir),
209 Self::File(_) => Err(nfsstat3::NFS3ERR_NOTDIR),
210 }
211 }
212
213 const fn as_dir_mut(&mut self) -> Result<&mut Dir, nfsstat3> {
214 match self {
215 Self::Dir(dir) => Ok(dir),
216 Self::File(_) => Err(nfsstat3::NFS3ERR_NOTDIR),
217 }
218 }
219
220 const fn as_file(&self) -> Result<&File, nfsstat3> {
221 match self {
222 Self::File(file) => Ok(file),
223 Self::Dir(_) => Err(nfsstat3::NFS3ERR_ISDIR),
224 }
225 }
226
227 const fn as_file_mut(&mut self) -> Result<&mut File, nfsstat3> {
228 match self {
229 Self::File(file) => Ok(file),
230 Self::Dir(_) => Err(nfsstat3::NFS3ERR_ISDIR),
231 }
232 }
233
234 fn fileid(&self) -> FileHandleU64 {
235 match self {
236 Self::File(file) => file.attr.fileid,
237 Self::Dir(dir) => dir.attr.fileid,
238 }
239 .into()
240 }
241
242 const fn name(&self) -> &filename3<'static> {
243 match self {
244 Self::File(file) => &file.name,
245 Self::Dir(dir) => &dir.name,
246 }
247 }
248
249 fn set_name(&mut self, name: filename3<'static>) {
250 match self {
251 Self::File(file) => file.name = name,
252 Self::Dir(dir) => dir.name = name,
253 }
254 }
255
256 const fn attr(&self) -> &fattr3 {
257 match self {
258 Self::File(file) => &file.attr,
259 Self::Dir(dir) => &dir.attr,
260 }
261 }
262
263 const fn attr_mut(&mut self) -> &mut fattr3 {
264 match self {
265 Self::File(file) => &mut file.attr,
266 Self::Dir(dir) => &mut dir.attr,
267 }
268 }
269
270 fn set_attr(&mut self, setattr: sattr3) {
271 {
272 let attr = self.attr_mut();
273 match setattr.atime {
274 nfs::set_atime::DONT_CHANGE => {}
275 nfs::set_atime::SET_TO_CLIENT_TIME(c) => {
276 attr.atime = c;
277 }
278 nfs::set_atime::SET_TO_SERVER_TIME => {
279 attr.atime = current_time();
280 }
281 }
282 match setattr.mtime {
283 nfs::set_mtime::DONT_CHANGE => {}
284 nfs::set_mtime::SET_TO_CLIENT_TIME(c) => {
285 attr.mtime = c;
286 }
287 nfs::set_mtime::SET_TO_SERVER_TIME => {
288 attr.mtime = current_time();
289 }
290 }
291 if let nfs::set_uid3::Some(u) = setattr.uid {
292 attr.uid = u;
293 }
294 if let nfs::set_gid3::Some(u) = setattr.gid {
295 attr.gid = u;
296 }
297 }
298 if let nfs::set_size3::Some(s) = setattr.size {
299 if let Self::File(file) = self {
300 file.resize(s);
301 }
302 }
303 }
304}
305
306#[derive(Debug)]
307struct Fs {
308 entries: HashMap<FileHandleU64, Entry>,
309 root: FileHandleU64,
310}
311
312impl Fs {
313 fn new() -> Self {
314 let root = Entry::Dir(Dir::root_dir());
315 let fileid = root.fileid();
316 let mut flat_list = HashMap::new();
317 flat_list.insert(fileid, root);
318 Self {
319 entries: flat_list,
320 root: fileid,
321 }
322 }
323
324 fn push(&mut self, parent: FileHandleU64, entry: Entry) -> Result<(), nfsstat3> {
325 use std::collections::hash_map::Entry as MapEntry;
326
327 let id = entry.fileid();
328
329 let map_entry = self.entries.entry(id);
330 match map_entry {
331 MapEntry::Occupied(_) => {
332 tracing::warn!("object with same id already exists: {id}");
333 return Err(nfsstat3::NFS3ERR_EXIST);
334 }
335 MapEntry::Vacant(v) => {
336 v.insert(entry);
337 }
338 }
339
340 let parent_entry = self.entries.get_mut(&parent);
341 match parent_entry {
342 None => {
343 tracing::warn!("parent not found: {parent}");
344 self.entries.remove(&id); Err(nfsstat3::NFS3ERR_NOENT)
346 }
347 Some(Entry::File(_)) => {
348 tracing::warn!("parent is not a directory: {parent}");
349 self.entries.remove(&id); Err(nfsstat3::NFS3ERR_NOTDIR)
351 }
352 Some(Entry::Dir(dir)) => {
353 let added = dir.add_entry(id);
354 assert!(added, "failed to add a new entry to directory");
355 Ok(())
356 }
357 }
358 }
359
360 fn remove(&mut self, dirid: FileHandleU64, filename: &filename3) -> Result<(), nfsstat3> {
361 if filename.as_ref() == b"." || filename.as_ref() == b".." {
362 return Err(nfsstat3::NFS3ERR_INVAL);
363 }
364
365 let object_id = {
366 let entry = self.entries.get(&dirid).ok_or(nfsstat3::NFS3ERR_NOENT)?;
367 let dir = entry.as_dir()?;
368 let id = dir
369 .content
370 .iter()
371 .find(|i| self.entries.get(i).is_some_and(|f| f.name() == filename));
372 id.copied().ok_or(nfsstat3::NFS3ERR_NOENT)?
373 };
374
375 let entry = self
376 .entries
377 .get(&object_id)
378 .ok_or(nfsstat3::NFS3ERR_NOENT)?;
379 if let Entry::Dir(dir) = entry {
380 if !dir.content.is_empty() {
381 return Err(nfsstat3::NFS3ERR_NOTEMPTY);
382 }
383 }
384
385 self.entries.remove(&object_id);
386 self.entries
387 .get_mut(&dirid)
388 .expect("entry not found")
389 .as_dir_mut()?
390 .content
391 .remove(&object_id);
392 Ok(())
393 }
394
395 fn get(&self, id: FileHandleU64) -> Option<&Entry> {
396 self.entries.get(&id)
397 }
398
399 fn get_mut(&mut self, id: FileHandleU64) -> Option<&mut Entry> {
400 self.entries.get_mut(&id)
401 }
402
403 fn lookup(&self, dirid: FileHandleU64, filename: &filename3) -> Result<&Entry, nfsstat3> {
404 let entry = self.get(dirid).ok_or(nfsstat3::NFS3ERR_NOENT)?;
405
406 if let Entry::File(_) = entry {
407 return Err(nfsstat3::NFS3ERR_NOTDIR);
408 } else if let Entry::Dir(dir) = &entry {
409 if filename.as_ref() == b"." {
411 return Ok(entry);
412 }
413 if filename.as_ref() == b".." {
415 let parent = self.get(dir.parent).ok_or(nfsstat3::NFS3ERR_SERVERFAULT)?;
416 return Ok(parent);
417 }
418 for i in dir.content.iter().copied() {
419 match self.get(i) {
420 None => {
421 tracing::error!("invalid entry: {i}");
422 return Err(nfsstat3::NFS3ERR_SERVERFAULT);
423 }
424 Some(f) => {
425 if f.name() == filename {
426 return Ok(f);
427 }
428 }
429 }
430 }
431 }
432 Err(nfsstat3::NFS3ERR_NOENT)
433 }
434
435 fn rename(
436 &mut self,
437 from_dirid: FileHandleU64,
438 from_filename: &filename3<'_>,
439 to_dirid: FileHandleU64,
440 to_filename: &filename3<'_>,
441 ) -> Result<(), nfsstat3> {
442 let from_entry = self.lookup(from_dirid, from_filename)?;
443 let from_id = from_entry.fileid();
444 let is_dir = matches!(from_entry, Entry::Dir(_));
445
446 if from_dirid == to_dirid && from_filename == to_filename {
449 return Ok(());
451 }
452
453 let to_entry = self.lookup(to_dirid, to_filename);
454
455 match (from_entry, to_entry) {
458 (_, Err(nfsstat3::NFS3ERR_NOENT)) => {
459 }
461 (Entry::File(_), Ok(Entry::File(_))) => {
462 self.remove(to_dirid, to_filename)?;
464 }
465 (Entry::Dir(_), Ok(Entry::Dir(tgt_dir))) => {
466 if !tgt_dir.content.is_empty() {
468 tracing::warn!("target directory is not empty");
469 return Err(nfsstat3::NFS3ERR_NOTEMPTY);
470 }
471 self.remove(to_dirid, to_filename)?;
472 }
473 (Entry::File(_), Ok(Entry::Dir(_))) => {
474 tracing::warn!("cannot rename file to directory");
476 return Err(nfsstat3::NFS3ERR_NOTDIR);
477 }
478 (Entry::Dir(_), Ok(Entry::File(_))) => {
479 tracing::warn!("cannot rename directory to file");
481 return Err(nfsstat3::NFS3ERR_NOTDIR);
482 }
483 (_, Err(e)) => {
484 return Err(e);
486 }
487 }
488
489 if is_dir {
493 let mut current = to_dirid;
494 loop {
495 if current == from_id {
496 tracing::warn!("cannot move a directory into its own subdirectory");
497 return Err(nfsstat3::NFS3ERR_INVAL);
498 }
499 let entry = self.get(current).ok_or(nfsstat3::NFS3ERR_NOENT)?;
500 match entry {
501 Entry::Dir(dir) => {
502 if current == self.root {
503 break;
505 }
506 current = dir.parent;
507 }
508 Entry::File(_) => {
509 tracing::error!("expected a directory, found a file");
510 return Err(nfsstat3::NFS3ERR_SERVERFAULT);
511 }
512 }
513 }
514 }
515
516 {
518 let from_dir = self
519 .get_mut(from_dirid)
520 .ok_or(nfsstat3::NFS3ERR_SERVERFAULT)?;
521 from_dir.as_dir_mut()?.content.remove(&from_id);
522 }
523
524 {
526 let to_dir = self
527 .get_mut(to_dirid)
528 .ok_or(nfsstat3::NFS3ERR_SERVERFAULT)?;
529 let added = to_dir.as_dir_mut()?.content.insert(from_id);
530 if !added {
531 tracing::error!("failed to add entry to target directory");
532 return Err(nfsstat3::NFS3ERR_SERVERFAULT);
533 }
534 }
535
536 let entry = self.get_mut(from_id).ok_or(nfsstat3::NFS3ERR_SERVERFAULT)?;
538 entry.set_name(to_filename.clone_to_owned());
539 if let Entry::Dir(dir) = entry {
540 dir.parent = to_dirid;
541 }
542
543 Ok(())
544 }
545}
546
547#[derive(Debug)]
551pub struct MemFs {
552 fs: Arc<RwLock<Fs>>,
553 rootdir: FileHandleU64,
554 nextid: AtomicU64,
555}
556
557impl Default for MemFs {
558 fn default() -> Self {
559 let root = Fs::new();
560 let rootdir = root.root;
561 let nextid = AtomicU64::new(rootdir.as_u64() + 1);
562 Self {
563 fs: Arc::new(RwLock::new(root)),
564 rootdir,
565 nextid,
566 }
567 }
568}
569
570impl MemFs {
571 pub fn new(config: MemFsConfig) -> Result<Self, nfsstat3> {
573 tracing::info!("creating memfs. Entries count: {}", config.entries.len());
574 let fs = Self::default();
575
576 for entry in config.entries {
577 let id = fs.path_to_id_impl(&entry.parent)?;
578 let name = filename3(Opaque::owned(entry.name.into_bytes()));
579 if entry.is_dir {
580 fs.add_dir(id, name)?;
581 } else {
582 fs.add_file(id, name, sattr3::default(), entry.content, None)?;
583 }
584 }
585
586 Ok(fs)
587 }
588
589 fn add_dir(
590 &self,
591 dirid: FileHandleU64,
592 dirname: filename3<'static>,
593 ) -> Result<(FileHandleU64, fattr3), nfsstat3> {
594 let newid: FileHandleU64 = self
595 .nextid
596 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
597 .into();
598
599 let dir = Entry::new_dir(dirname, newid, dirid);
600 let attr = dir.attr().clone();
601
602 self.fs
603 .write()
604 .expect("lock is poisoned")
605 .push(dirid, dir)?;
606
607 Ok((newid, attr))
608 }
609
610 fn add_file(
611 &self,
612 dirid: FileHandleU64,
613 filename: filename3<'static>,
614 attr: sattr3,
615 content: Vec<u8>,
616 verf: Option<createverf3>,
617 ) -> Result<(FileHandleU64, fattr3), nfsstat3> {
618 let newid: FileHandleU64 = self
619 .nextid
620 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
621 .into();
622
623 let mut file = Entry::new_file(filename, newid, content, verf.unwrap_or_default());
624 file.set_attr(attr);
625 let attr = file.attr().clone();
626
627 let mut fs_lock = self.fs.write().expect("lock is poisoned");
628 match fs_lock.lookup(dirid, file.name()) {
629 Err(nfsstat3::NFS3ERR_NOENT) => {
630 }
632 Ok(existing_file) => {
633 if let Entry::File(existing_file) = existing_file {
634 if verf.is_some_and(|v| v == existing_file.verf) {
635 return Ok((existing_file.fileid(), attr));
636 }
637 }
638 return Err(nfsstat3::NFS3ERR_EXIST);
639 }
640 Err(e) => {
641 return Err(e);
643 }
644 }
645 fs_lock.push(dirid, file)?;
646
647 Ok((newid, attr))
648 }
649
650 fn path_to_id_impl(&self, path: &str) -> Result<FileHandleU64, nfsstat3> {
651 let splits = path.split(DELIMITER);
652 let mut fid = self.root_dir();
653 let fs = self.fs.read().expect("lock is poisoned");
654 for component in splits {
655 if component.is_empty() {
656 continue;
657 }
658 let entry = fs.lookup(fid, &component.as_bytes().into())?;
659 fid = entry.fileid();
660 }
661 Ok(fid)
662 }
663
664 fn make_iter(
665 &self,
666 dirid: FileHandleU64,
667 start_after: cookie3,
668 ) -> Result<MemFsIterator, nfsstat3> {
669 let fs = self.fs.read().expect("lock is poisoned");
670 let entry = fs.get(dirid).ok_or(nfsstat3::NFS3ERR_NOENT)?;
671 let dir = entry.as_dir()?;
672
673 let mut iter = dir.content.iter();
674 if start_after != 0 {
675 let find_result = iter.find(|i| **i == start_after);
677 if find_result.is_none() {
678 return Err(nfsstat3::NFS3ERR_BAD_COOKIE);
679 }
680 }
681 let content: Vec<_> = iter.copied().collect();
682 Ok(MemFsIterator::new(self.fs.clone(), content))
683 }
684}
685
686impl NfsReadFileSystem for MemFs {
687 type Handle = FileHandleU64;
688
689 fn root_dir(&self) -> FileHandleU64 {
690 self.rootdir
691 }
692
693 async fn lookup(
694 &self,
695 dirid: &FileHandleU64,
696 filename: &filename3<'_>,
697 ) -> Result<FileHandleU64, nfsstat3> {
698 let fs = self.fs.read().expect("lock is poisoned");
699 fs.lookup(*dirid, filename).map(Entry::fileid)
700 }
701
702 async fn getattr(&self, id: &FileHandleU64) -> Result<fattr3, nfsstat3> {
703 let fs = self.fs.read().expect("lock is poisoned");
704 let entry = fs.get(*id).ok_or(nfsstat3::NFS3ERR_NOENT)?;
705 Ok(entry.attr().clone())
706 }
707
708 async fn read(
709 &self,
710 id: &FileHandleU64,
711 offset: u64,
712 count: u32,
713 ) -> Result<(Vec<u8>, bool), nfsstat3> {
714 let fs = self.fs.read().expect("lock is poisoned");
715 let entry = fs.get(*id).ok_or(nfsstat3::NFS3ERR_NOENT)?;
716 let file = entry.as_file()?;
717 Ok(file.read(offset, count))
718 }
719
720 async fn readdir(
721 &self,
722 dirid: &FileHandleU64,
723 cookie: u64,
724 ) -> Result<impl ReadDirIterator, nfsstat3> {
725 let iter = Self::make_iter(self, *dirid, cookie)?;
726 Ok(iter)
727 }
728
729 async fn readdirplus(
730 &self,
731 dirid: &FileHandleU64,
732 cookie: u64,
733 ) -> Result<impl ReadDirPlusIterator, nfsstat3> {
734 let iter = Self::make_iter(self, *dirid, cookie)?;
735 Ok(iter)
736 }
737
738 async fn readlink(&self, _id: &FileHandleU64) -> Result<nfspath3, nfsstat3> {
739 tracing::warn!("readlink not implemented");
740 Err(nfsstat3::NFS3ERR_NOTSUPP)
741 }
742
743 async fn lookup_by_path(&self, path: &str) -> Result<FileHandleU64, nfsstat3> {
744 self.path_to_id_impl(path)
745 }
746}
747
748impl NfsFileSystem for MemFs {
749 async fn setattr(&self, id: &FileHandleU64, setattr: sattr3) -> Result<fattr3, nfsstat3> {
750 let mut fs = self.fs.write().expect("lock is poisoned");
751 let entry = fs.get_mut(*id).ok_or(nfsstat3::NFS3ERR_NOENT)?;
752 entry.set_attr(setattr);
753 Ok(entry.attr().clone())
754 }
755
756 async fn write(
757 &self,
758 id: &FileHandleU64,
759 offset: u64,
760 data: &[u8],
761 ) -> Result<fattr3, nfsstat3> {
762 let mut fs = self.fs.write().expect("lock is poisoned");
763
764 let entry = fs.get_mut(*id).ok_or(nfsstat3::NFS3ERR_NOENT)?;
765 let file = entry.as_file_mut().map_err(|_| nfsstat3::NFS3ERR_INVAL)?;
766 file.write(offset, data)
767 }
768
769 async fn create(
770 &self,
771 dirid: &FileHandleU64,
772 filename: &filename3<'_>,
773 attr: sattr3,
774 ) -> Result<(FileHandleU64, fattr3), nfsstat3> {
775 self.add_file(*dirid, filename.clone_to_owned(), attr, Vec::new(), None)
776 }
777
778 async fn create_exclusive(
779 &self,
780 dirid: &FileHandleU64,
781 filename: &filename3<'_>,
782 createverf: nfs::createverf3,
783 ) -> Result<FileHandleU64, nfsstat3> {
784 self.add_file(
785 *dirid,
786 filename.clone_to_owned(),
787 sattr3::default(),
788 Vec::new(),
789 Some(createverf),
790 )
791 .map(|(id, _attr)| id)
792 }
793
794 async fn mkdir(
795 &self,
796 dirid: &FileHandleU64,
797 dirname: &filename3<'_>,
798 ) -> Result<(FileHandleU64, fattr3), nfsstat3> {
799 self.add_dir(*dirid, dirname.clone_to_owned())
800 }
801
802 async fn remove(
803 &self,
804 dirid: &FileHandleU64,
805 filename: &filename3<'_>,
806 ) -> Result<(), nfsstat3> {
807 self.fs
808 .write()
809 .expect("lock is poisoned")
810 .remove(*dirid, filename)
811 }
812
813 async fn rename<'a>(
814 &self,
815 from_dirid: &FileHandleU64,
816 from_filename: &filename3<'a>,
817 to_dirid: &FileHandleU64,
818 to_filename: &filename3<'a>,
819 ) -> Result<(), nfsstat3> {
820 let mut fs = self.fs.write().expect("lock is poisoned");
821 fs.rename(*from_dirid, from_filename, *to_dirid, to_filename)
822 }
823
824 async fn symlink<'a>(
825 &self,
826 _dirid: &FileHandleU64,
827 _linkname: &filename3<'a>,
828 _symlink: &nfspath3<'a>,
829 _attr: &sattr3,
830 ) -> Result<(FileHandleU64, fattr3), nfsstat3> {
831 tracing::warn!("symlink not implemented");
832 Err(nfsstat3::NFS3ERR_NOTSUPP)
833 }
834}
835
836struct MemFsIterator {
837 fs: Arc<RwLock<Fs>>,
838 entries: Vec<FileHandleU64>,
839 index: usize,
840}
841
842impl MemFsIterator {
843 const fn new(fs: Arc<RwLock<Fs>>, entries: Vec<FileHandleU64>) -> Self {
844 Self {
845 fs,
846 entries,
847 index: 0,
848 }
849 }
850}
851
852impl ReadDirPlusIterator for MemFsIterator {
853 async fn next(&mut self) -> NextResult<entryplus3<'static>> {
854 loop {
855 if self.index >= self.entries.len() {
856 return NextResult::Eof;
857 }
858 let id = self.entries[self.index];
859 self.index += 1;
860
861 let fs = self.fs.read().expect("lock is poisoned");
862 let entry = fs.get(id);
863 let Some(entry) = entry else {
864 tracing::warn!("entry not found: {id}");
866 continue;
867 };
868 let attr = entry.attr().clone();
869 return NextResult::Ok(entryplus3 {
871 fileid: id.into(),
872 name: entry.name().clone_to_owned(),
873 cookie: id.into(),
874 name_attributes: nfs::post_op_attr::Some(attr),
875 name_handle: nfs::Nfs3Option::None,
876 });
877 }
878 }
879}