fuse_backend_rs/api/
pseudo_fs.rs

1// Copyright 2020 Ant Financial. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! A pseudo fs for path walking to other real filesystems
5//!
6//! There are several assumptions adopted when designing the PseudoFs:
7//! - The PseudoFs is used to mount other filesystems, so it only supports directories.
8//! - There won't be too much directories/sub-directories managed by a PseudoFs instance, so linear
9//!   search is used when searching for child inodes.
10//! - Inodes managed by the PseudoFs is readonly, even for the permission bits.
11
12use arc_swap::ArcSwap;
13use std::collections::HashMap;
14use std::ffi::CStr;
15use std::io::{Error, Result};
16use std::ops::Deref;
17use std::path::{Component, Path};
18use std::sync::atomic::{AtomicU64, Ordering};
19use std::sync::{Arc, Mutex};
20use std::time::{Duration, SystemTime};
21
22use crate::abi::fuse_abi::{stat64, Attr};
23use crate::api::filesystem::*;
24
25// ID 0 is reserved for invalid entry, and ID 1 is used for ROOT_ID.
26const PSEUDOFS_NEXT_INODE: u64 = 2;
27const PSEUDOFS_DEFAULT_ATTR_TIMEOUT: u64 = 1 << 32;
28const PSEUDOFS_DEFAULT_ENTRY_TIMEOUT: u64 = PSEUDOFS_DEFAULT_ATTR_TIMEOUT;
29
30type Inode = u64;
31type Handle = u64;
32
33struct PseudoInode {
34    ino: u64,
35    parent: u64,
36    children: ArcSwap<Vec<Arc<PseudoInode>>>,
37    name: String,
38}
39
40impl PseudoInode {
41    fn new(ino: u64, parent: u64, name: String) -> Self {
42        PseudoInode {
43            ino,
44            parent,
45            children: ArcSwap::new(Arc::new(Vec::new())),
46            name,
47        }
48    }
49
50    // It's protected by Pseudofs.lock.
51    fn insert_child(&self, child: Arc<PseudoInode>) {
52        let mut children = self.children.load().deref().deref().clone();
53
54        children.push(child);
55
56        self.children.store(Arc::new(children));
57    }
58
59    fn remove_child(&self, child: Arc<PseudoInode>) {
60        let mut children = self.children.load().deref().deref().clone();
61
62        children
63            .iter()
64            .position(|x| x.name == child.name)
65            .map(|pos| children.remove(pos))
66            .unwrap();
67
68        self.children.store(Arc::new(children));
69    }
70}
71
72pub struct PseudoFs {
73    next_inode: AtomicU64,
74    root_inode: Arc<PseudoInode>,
75    inodes: ArcSwap<HashMap<u64, Arc<PseudoInode>>>,
76    lock: Mutex<()>, // Write protect PseudoFs.inodes and PseudoInode.children
77}
78
79impl PseudoFs {
80    pub fn new() -> Self {
81        let root_inode = Arc::new(PseudoInode::new(ROOT_ID, ROOT_ID, String::from("/")));
82        let fs = PseudoFs {
83            next_inode: AtomicU64::new(PSEUDOFS_NEXT_INODE),
84            root_inode: root_inode.clone(),
85            inodes: ArcSwap::new(Arc::new(HashMap::new())),
86            lock: Mutex::new(()),
87        };
88
89        // Create the root inode. We have just created the lock, so it should be safe to unwrap().
90        let _guard = fs.lock.lock().unwrap();
91        fs.insert_inode(root_inode);
92        drop(_guard);
93
94        fs
95    }
96
97    // mount creates path walk nodes all the way from root
98    // to @path, and returns pseudo fs inode number for the path
99    pub fn mount(&self, mountpoint: &str) -> Result<u64> {
100        let path = Path::new(mountpoint);
101        if !path.has_root() {
102            error!("pseudo fs mount failure: invalid mount path {}", mountpoint);
103            return Err(Error::from_raw_os_error(libc::EINVAL));
104        }
105
106        let mut inodes = self.inodes.load();
107        let mut inode = &self.root_inode;
108
109        'outer: for component in path.components() {
110            trace!("pseudo fs mount iterate {:?}", component.as_os_str());
111            match component {
112                Component::RootDir => continue,
113                Component::CurDir => continue,
114                Component::ParentDir => inode = inodes.get(&inode.parent).unwrap(),
115                Component::Prefix(_) => {
116                    error!("unsupported path: {}", mountpoint);
117                    return Err(Error::from_raw_os_error(libc::EINVAL));
118                }
119                Component::Normal(path) => {
120                    let name = path.to_str().unwrap();
121
122                    // Optimistic check without lock.
123                    for child in inode.children.load().iter() {
124                        if child.name == name {
125                            inode = inodes.get(&child.ino).unwrap();
126                            continue 'outer;
127                        }
128                    }
129
130                    // Double check with writer lock held.
131                    let _guard = self.lock.lock();
132                    for child in inode.children.load().iter() {
133                        if child.name == name {
134                            inode = inodes.get(&child.ino).unwrap();
135                            continue 'outer;
136                        }
137                    }
138
139                    let new_node = self.create_inode(name, inode);
140                    inodes = self.inodes.load();
141                    inode = inodes.get(&new_node.ino).unwrap();
142                }
143            }
144        }
145
146        // Now we have all path components exist, return the last one
147        Ok(inode.ino)
148    }
149
150    pub fn path_walk(&self, mountpoint: &str) -> Result<Option<u64>> {
151        let path = Path::new(mountpoint);
152        if !path.has_root() {
153            error!("pseudo fs walk failure: invalid path {}", mountpoint);
154            return Err(Error::from_raw_os_error(libc::EINVAL));
155        }
156
157        let inodes = self.inodes.load();
158        let mut inode = &self.root_inode;
159
160        'outer: for component in path.components() {
161            debug!("pseudo fs iterate {:?}", component.as_os_str());
162            match component {
163                Component::RootDir => continue,
164                Component::CurDir => continue,
165                Component::ParentDir => inode = inodes.get(&inode.parent).unwrap(),
166                Component::Prefix(_) => {
167                    error!("unsupported path: {}", mountpoint);
168                    return Err(Error::from_raw_os_error(libc::EINVAL));
169                }
170                Component::Normal(path) => {
171                    let name = path.to_str().ok_or_else(|| {
172                        error!("Path {:?} can't be converted safely", path);
173                        Error::from_raw_os_error(libc::EINVAL)
174                    })?;
175
176                    // Optimistic check without lock.
177                    for child in inode.children.load().iter() {
178                        if child.name == name {
179                            inode = inodes.get(&child.ino).unwrap();
180                            continue 'outer;
181                        }
182                    }
183
184                    // Double check with writer lock held.
185                    let _guard = self.lock.lock();
186                    for child in inode.children.load().iter() {
187                        if child.name == name {
188                            inode = inodes.get(&child.ino).unwrap();
189                            continue 'outer;
190                        }
191                    }
192
193                    debug!("name {} is not found, path is {}", name, mountpoint);
194                    return Ok(None);
195                }
196            }
197        }
198
199        // let _guard = self.lock.lock();
200        // self.evict_inode(&inode);
201        // Now we have all path components exist, return the last one
202        Ok(Some(inode.ino))
203    }
204
205    fn new_inode(&self, parent: u64, name: &str) -> Arc<PseudoInode> {
206        let ino = self.next_inode.fetch_add(1, Ordering::Relaxed);
207
208        Arc::new(PseudoInode::new(ino, parent, name.to_owned()))
209    }
210
211    // Caller must hold PseudoFs.lock.
212    fn insert_inode(&self, inode: Arc<PseudoInode>) {
213        let mut hashmap = self.inodes.load().deref().deref().clone();
214
215        hashmap.insert(inode.ino, inode);
216
217        self.inodes.store(Arc::new(hashmap));
218    }
219
220    // Caller must hold PseudoFs.lock.
221    fn create_inode(&self, name: &str, parent: &Arc<PseudoInode>) -> Arc<PseudoInode> {
222        let inode = self.new_inode(parent.ino, name);
223
224        self.insert_inode(inode.clone());
225        parent.insert_child(inode.clone());
226
227        inode
228    }
229
230    fn remove_inode(&self, inode: &Arc<PseudoInode>) {
231        let mut hashmap = self.inodes.load().deref().deref().clone();
232
233        hashmap.remove(&inode.ino);
234
235        self.inodes.store(Arc::new(hashmap));
236    }
237
238    pub fn get_parent_inode(&self, ino: u64) -> Option<u64> {
239        let _guard = self.lock.lock();
240        let inodes = self.inodes.load();
241        inodes.get(&ino).map(|o| o.parent)
242    }
243
244    #[allow(dead_code)]
245    pub fn evict_inode(&self, ino: u64) {
246        let _guard = self.lock.lock();
247        let inodes = self.inodes.load();
248
249        let inode = inodes.get(&ino).unwrap();
250        // ino == inode.parent means it is pseudo fs root inode.
251        // Do not evict it.
252        if ino == inode.parent {
253            return;
254        }
255
256        let parent = inodes.get(&inode.parent).unwrap();
257        parent.remove_child(inode.clone());
258
259        self.remove_inode(inode);
260    }
261
262    fn get_entry(&self, ino: u64) -> Entry {
263        let mut attr = Attr {
264            ..Default::default()
265        };
266        attr.ino = ino;
267        #[cfg(target_os = "linux")]
268        {
269            attr.mode = libc::S_IFDIR | libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO;
270        }
271        #[cfg(target_os = "macos")]
272        {
273            attr.mode = (libc::S_IFDIR | libc::S_IRWXU | libc::S_IRWXG | libc::S_IRWXO) as u32;
274        }
275        let now = SystemTime::now();
276        attr.ctime = now
277            .duration_since(SystemTime::UNIX_EPOCH)
278            .unwrap()
279            .as_secs();
280        attr.mtime = attr.ctime;
281        attr.atime = attr.ctime;
282        attr.blksize = 4096;
283        Entry {
284            inode: ino,
285            generation: 0,
286            attr: attr.into(),
287            attr_flags: 0,
288            attr_timeout: Duration::from_secs(PSEUDOFS_DEFAULT_ATTR_TIMEOUT),
289            entry_timeout: Duration::from_secs(PSEUDOFS_DEFAULT_ENTRY_TIMEOUT),
290        }
291    }
292
293    fn do_readdir(
294        &self,
295        parent: u64,
296        size: u32,
297        offset: u64,
298        add_entry: &mut dyn FnMut(DirEntry) -> Result<usize>,
299    ) -> Result<()> {
300        if size == 0 {
301            return Ok(());
302        }
303
304        let inodes = self.inodes.load();
305        let inode = inodes
306            .get(&parent)
307            .ok_or_else(|| Error::from_raw_os_error(libc::ENOENT))?;
308        let mut next = offset + 1;
309        let children = inode.children.load();
310
311        if offset >= children.len() as u64 {
312            return Ok(());
313        }
314
315        for child in children[offset as usize..].iter() {
316            match add_entry(DirEntry {
317                ino: child.ino,
318                offset: next,
319                type_: 0,
320                name: child.name.clone().as_bytes(),
321            }) {
322                Ok(0) => break,
323                Ok(_) => next += 1,
324                Err(r) => return Err(r),
325            }
326        }
327
328        Ok(())
329    }
330}
331
332impl Default for PseudoFs {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338impl FileSystem for PseudoFs {
339    type Inode = Inode;
340    type Handle = Handle;
341
342    fn lookup(&self, _: &Context, parent: u64, name: &CStr) -> Result<Entry> {
343        let inodes = self.inodes.load();
344        let pinode = inodes
345            .get(&parent)
346            .ok_or_else(|| Error::from_raw_os_error(libc::ENOENT))?;
347        let child_name = name
348            .to_str()
349            .map_err(|_| Error::from_raw_os_error(libc::EINVAL))?;
350        let mut ino: u64 = 0;
351        if child_name == "." {
352            ino = pinode.ino;
353        } else if child_name == ".." {
354            ino = pinode.parent;
355        } else {
356            for child in pinode.children.load().iter() {
357                if child.name == child_name {
358                    ino = child.ino;
359                    break;
360                }
361            }
362        }
363
364        if ino == 0 {
365            // not found
366            Err(Error::from_raw_os_error(libc::ENOENT))
367        } else {
368            Ok(self.get_entry(ino))
369        }
370    }
371
372    fn getattr(&self, _: &Context, inode: u64, _: Option<u64>) -> Result<(stat64, Duration)> {
373        let ino = self
374            .inodes
375            .load()
376            .get(&inode)
377            .map(|inode| inode.ino)
378            .ok_or_else(|| Error::from_raw_os_error(libc::ENOENT))?;
379        let entry = self.get_entry(ino);
380
381        Ok((entry.attr, entry.attr_timeout))
382    }
383
384    fn readdir(
385        &self,
386        _ctx: &Context,
387        inode: u64,
388        _: u64,
389        size: u32,
390        offset: u64,
391        add_entry: &mut dyn FnMut(DirEntry) -> Result<usize>,
392    ) -> Result<()> {
393        self.do_readdir(inode, size, offset, add_entry)
394    }
395
396    fn readdirplus(
397        &self,
398        _ctx: &Context,
399        inode: u64,
400        _handle: u64,
401        size: u32,
402        offset: u64,
403        add_entry: &mut dyn FnMut(DirEntry, Entry) -> Result<usize>,
404    ) -> Result<()> {
405        self.do_readdir(inode, size, offset, &mut |dir_entry| {
406            let entry = self.get_entry(dir_entry.ino);
407            add_entry(dir_entry, entry)
408        })
409    }
410
411    fn access(&self, _ctx: &Context, _inode: u64, _mask: u32) -> Result<()> {
412        Ok(())
413    }
414}
415
416/// Save and restore PseudoFs state.
417#[cfg(feature = "persist")]
418pub mod persist {
419    use std::collections::HashMap;
420    use std::io::{Error as IoError, ErrorKind, Result};
421    use std::sync::atomic::Ordering;
422    use std::sync::Arc;
423
424    use dbs_snapshot::Snapshot;
425    use versionize::{VersionMap, Versionize, VersionizeResult};
426    use versionize_derive::Versionize;
427
428    use super::{PseudoFs, PseudoInode};
429    use crate::api::filesystem::ROOT_ID;
430
431    #[derive(Versionize, PartialEq, Debug, Default, Clone)]
432    struct PseudoInodeState {
433        ino: u64,
434        parent: u64,
435        name: String,
436    }
437
438    #[derive(Versionize, PartialEq, Debug, Default)]
439    pub struct PseudoFsState {
440        next_inode: u64,
441        inodes: Vec<PseudoInodeState>,
442    }
443
444    impl PseudoFs {
445        fn get_version_map() -> VersionMap {
446            let mut vm = VersionMap::new();
447            vm.set_type_version(PseudoFsState::type_id(), 1);
448
449            // more versions for the future
450
451            vm
452        }
453
454        /// Saves part of the PseudoFs into a byte array.
455        /// The upper layer caller can use this method to save
456        /// and transfer metadata for the reloading in the future.
457        pub fn save_to_bytes(&self) -> Result<Vec<u8>> {
458            let mut inodes = Vec::new();
459            let next_inode = self.next_inode.load(Ordering::Relaxed);
460
461            let _guard = self.lock.lock().unwrap();
462            for inode in self.inodes.load().values() {
463                if inode.ino == ROOT_ID {
464                    // no need to save the root inode
465                    continue;
466                }
467
468                inodes.push(PseudoInodeState {
469                    ino: inode.ino,
470                    parent: inode.parent,
471                    name: inode.name.clone(),
472                });
473            }
474            let state = PseudoFsState { next_inode, inodes };
475
476            let vm = PseudoFs::get_version_map();
477            let target_version = vm.latest_version();
478            let mut s = Snapshot::new(vm, target_version);
479            let mut buf = Vec::new();
480            s.save(&mut buf, &state).map_err(|e| {
481                IoError::new(
482                    ErrorKind::Other,
483                    format!("Failed to save PseudoFs to bytes: {:?}", e),
484                )
485            })?;
486
487            Ok(buf)
488        }
489
490        /// Restores the PseudoFs from a byte array.
491        pub fn restore_from_bytes(&self, buf: &mut Vec<u8>) -> Result<()> {
492            let state: PseudoFsState =
493                Snapshot::load(&mut buf.as_slice(), buf.len(), PseudoFs::get_version_map())
494                    .map_err(|e| {
495                        IoError::new(
496                            ErrorKind::Other,
497                            format!("Failed to load PseudoFs from bytes: {:?}", e),
498                        )
499                    })?
500                    .0;
501            self.restore_from_state(&state)
502        }
503
504        fn restore_from_state(&self, state: &PseudoFsState) -> Result<()> {
505            // first, reconstruct all the inodes
506            let mut inode_map = HashMap::new();
507            let mut state_inodes = state.inodes.clone();
508            for inode in state_inodes.iter() {
509                let inode = Arc::new(PseudoInode::new(
510                    inode.ino,
511                    inode.parent,
512                    inode.name.clone(),
513                ));
514                inode_map.insert(inode.ino, inode);
515            }
516
517            // insert root inode to make sure the others inodes can find their parents
518            inode_map.insert(self.root_inode.ino, self.root_inode.clone());
519
520            // then, connect the inodes
521            state_inodes.sort_by(|a, b| a.ino.cmp(&b.ino));
522            for inode in state_inodes.iter() {
523                let inode = inode_map
524                    .get(&inode.ino)
525                    .ok_or_else(|| {
526                        IoError::new(
527                            ErrorKind::InvalidData,
528                            format!("invalid inode {}", inode.ino),
529                        )
530                    })?
531                    .clone();
532                let parent = inode_map.get_mut(&inode.parent).ok_or_else(|| {
533                    IoError::new(
534                        ErrorKind::InvalidData,
535                        format!(
536                            "invalid parent inode {} for inode {}",
537                            inode.parent, inode.ino
538                        ),
539                    )
540                })?;
541                parent.insert_child(inode);
542            }
543            self.inodes.store(Arc::new(inode_map));
544
545            // last, restore next_inode
546            self.next_inode.store(state.next_inode, Ordering::Relaxed);
547
548            Ok(())
549        }
550    }
551
552    mod test {
553
554        #[test]
555        fn save_restore_test() {
556            use crate::api::pseudo_fs::PseudoFs;
557
558            let fs = &PseudoFs::new();
559            let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
560
561            for path in paths.iter() {
562                fs.mount(path).unwrap();
563            }
564
565            // save fs
566            let mut buf = fs.save_to_bytes().unwrap();
567
568            // restore fs
569            let restored_fs = &PseudoFs::new();
570            restored_fs.restore_from_bytes(&mut buf).unwrap();
571
572            // check fs and restored_fs
573            let next_inode = fs.next_inode.load(std::sync::atomic::Ordering::Relaxed);
574            let restored_next_inode = restored_fs
575                .next_inode
576                .load(std::sync::atomic::Ordering::Relaxed);
577            assert_eq!(next_inode, restored_next_inode);
578
579            for path in paths.iter() {
580                let inode = fs.path_walk(path).unwrap();
581                let restored_inode = restored_fs.path_walk(path).unwrap();
582                assert_eq!(inode, restored_inode);
583            }
584        }
585    }
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591    use std::ffi::CString;
592
593    fn create_fuse_context() -> Context {
594        Context::new()
595    }
596
597    #[test]
598    fn test_pseudofs_new() {
599        let fs = PseudoFs::new();
600
601        assert_eq!(fs.next_inode.load(Ordering::Relaxed), 2);
602        assert_eq!(fs.root_inode.ino, ROOT_ID);
603        assert_eq!(fs.root_inode.children.load().len(), 0);
604        assert_eq!(fs.inodes.load().len(), 1);
605    }
606
607    #[test]
608    fn test_pseudofs_mount() {
609        let fs = PseudoFs::new();
610
611        assert_eq!(
612            fs.mount("test").unwrap_err().raw_os_error().unwrap(),
613            libc::EINVAL
614        );
615
616        let a1 = fs.mount("/a").unwrap();
617        let a2 = fs.mount("/a").unwrap();
618        assert_eq!(a1, a2);
619        let a3 = fs.mount("/./a").unwrap();
620        assert_eq!(a1, a3);
621        let a4 = fs.mount("/../a").unwrap();
622        assert_eq!(a1, a4);
623        let a5 = fs.mount("/../../a").unwrap();
624        assert_eq!(a1, a5);
625
626        let c1 = fs.mount("/a/b/c").unwrap();
627        let c1_i = fs.inodes.load().get(&c1).unwrap().clone();
628        let b1 = fs.mount("/a/b").unwrap();
629        assert_eq!(c1, c1_i.ino);
630        assert_eq!(c1_i.parent, b1);
631
632        let _e1 = fs.mount("/a/b/c/d/e").unwrap();
633    }
634
635    #[test]
636    fn test_pseudofs_lookup() {
637        let fs = PseudoFs::new();
638        let a1 = fs.mount("/a").unwrap();
639        let b1 = fs.mount("/a/b").unwrap();
640        let c1 = fs.mount("/a/b/c").unwrap();
641
642        assert!(fs
643            .lookup(
644                &create_fuse_context(),
645                0x1000_0000,
646                &CString::new(".").unwrap()
647            )
648            .is_err());
649        assert_eq!(
650            fs.lookup(
651                &create_fuse_context(),
652                ROOT_ID,
653                &CString::new("..").unwrap()
654            )
655            .unwrap()
656            .inode,
657            ROOT_ID
658        );
659        assert_eq!(
660            fs.lookup(&create_fuse_context(), ROOT_ID, &CString::new(".").unwrap())
661                .unwrap()
662                .inode,
663            ROOT_ID
664        );
665        assert_eq!(
666            fs.lookup(&create_fuse_context(), ROOT_ID, &CString::new("a").unwrap())
667                .unwrap()
668                .inode,
669            a1
670        );
671        assert!(fs
672            .lookup(
673                &create_fuse_context(),
674                ROOT_ID,
675                &CString::new("a_no").unwrap()
676            )
677            .is_err());
678        assert_eq!(
679            fs.lookup(&create_fuse_context(), a1, &CString::new("b").unwrap())
680                .unwrap()
681                .inode,
682            b1
683        );
684        assert!(fs
685            .lookup(&create_fuse_context(), a1, &CString::new("b_no").unwrap())
686            .is_err());
687        assert_eq!(
688            fs.lookup(&create_fuse_context(), b1, &CString::new("c").unwrap())
689                .unwrap()
690                .inode,
691            c1
692        );
693        assert!(fs
694            .lookup(&create_fuse_context(), b1, &CString::new("c_no").unwrap())
695            .is_err());
696
697        assert_eq!(fs.path_walk("/a").unwrap(), Some(a1));
698        assert_eq!(fs.path_walk("/a/b").unwrap(), Some(b1));
699        assert_eq!(fs.path_walk("/a/b/c").unwrap(), Some(c1));
700        assert_eq!(fs.path_walk("/a/b/d").unwrap(), None);
701        assert_eq!(fs.path_walk("/a/b/c/d").unwrap(), None);
702
703        fs.evict_inode(b1);
704        fs.evict_inode(a1);
705    }
706
707    #[test]
708    fn test_pseudofs_getattr() {
709        let fs = PseudoFs::new();
710        let a1 = fs.mount("/a").unwrap();
711
712        fs.getattr(&create_fuse_context(), ROOT_ID, None).unwrap();
713        fs.getattr(&create_fuse_context(), a1, None).unwrap();
714        assert!(fs.getattr(&create_fuse_context(), 0x1000, None).is_err());
715
716        fs.evict_inode(a1);
717        fs.evict_inode(ROOT_ID);
718    }
719
720    #[test]
721    fn test_pseudofs_readdir() {
722        let fs = PseudoFs::new();
723        let _ = fs.mount("/a").unwrap();
724        let _ = fs.mount("/b").unwrap();
725
726        fs.readdir(&create_fuse_context(), ROOT_ID, 0, 0, 0, &mut |_| Ok(1))
727            .unwrap();
728        fs.readdir(&create_fuse_context(), ROOT_ID, 0, 1, 0, &mut |_| Ok(1))
729            .unwrap();
730        fs.readdir(&create_fuse_context(), ROOT_ID, 0, 1, 1, &mut |_| Ok(1))
731            .unwrap();
732        fs.readdir(&create_fuse_context(), ROOT_ID, 0, 2, 0, &mut |_| Ok(1))
733            .unwrap();
734        fs.readdir(&create_fuse_context(), ROOT_ID, 0, 3, 0, &mut |_| Ok(1))
735            .unwrap();
736        fs.readdir(&create_fuse_context(), ROOT_ID, 0, 3, 3, &mut |_| Ok(1))
737            .unwrap();
738        assert!(fs
739            .readdir(&create_fuse_context(), 0x1000, 0, 3, 0, &mut |_| Ok(1))
740            .is_err());
741    }
742
743    #[test]
744    fn test_pseudofs_readdir_plus() {
745        let fs = PseudoFs::new();
746        let _ = fs.mount("/a").unwrap();
747        let _ = fs.mount("/b").unwrap();
748
749        fs.readdirplus(&create_fuse_context(), ROOT_ID, 0, 0, 0, &mut |_, _| Ok(1))
750            .unwrap();
751        fs.readdirplus(&create_fuse_context(), ROOT_ID, 0, 1, 0, &mut |_, _| Ok(1))
752            .unwrap();
753        fs.readdirplus(&create_fuse_context(), ROOT_ID, 0, 1, 1, &mut |_, _| Ok(1))
754            .unwrap();
755        fs.readdirplus(&create_fuse_context(), ROOT_ID, 0, 2, 0, &mut |_, _| Ok(1))
756            .unwrap();
757        fs.readdirplus(&create_fuse_context(), ROOT_ID, 0, 3, 0, &mut |_, _| Ok(1))
758            .unwrap();
759        fs.readdirplus(&create_fuse_context(), ROOT_ID, 0, 3, 3, &mut |_, _| Ok(1))
760            .unwrap();
761        assert!(fs
762            .readdirplus(&create_fuse_context(), 0x1000, 0, 3, 0, &mut |_, _| Ok(1))
763            .is_err());
764    }
765
766    #[test]
767    fn test_pseudofs_access() {
768        let fs = PseudoFs::new();
769        let a1 = fs.mount("/a").unwrap();
770        let ctx = create_fuse_context();
771
772        fs.access(&ctx, a1, 0).unwrap();
773    }
774}