fuse_backend_rs/api/vfs/
mod.rs

1// Copyright 2020 Ant Financial. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! A union file system which combines multiple backend file systems into one.
5//!
6//! A simple union file system with limited functionality, which
7//! 1. uses pseudo fs to maintain the directory structures
8//! 2. supports mounting a file system at "/" or and subdirectory
9//! 3. supports mounting multiple file systems at different paths
10//! 4. remounting another file system at the same path will evict the old one
11//! 5. doesn't support recursive mounts. If /a is a mounted file system, you can't
12//!    mount another file systems under /a.
13//!
14//! Its main usage is to avoid virtio-fs device hotplug. With this simple union fs,
15//! a new backend file system could be mounted onto a subdirectory, instead of hot-adding
16//! another virtio-fs device. This is very convenient to manage container images at runtime.
17
18use std::any::Any;
19use std::collections::HashMap;
20use std::ffi::CStr;
21use std::fmt;
22use std::io;
23use std::io::{Error, Result};
24use std::ops::Deref;
25use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
26use std::sync::{Arc, Mutex};
27use std::time::Duration;
28
29use arc_swap::ArcSwap;
30
31use crate::abi::fuse_abi::*;
32use crate::api::filesystem::*;
33use crate::api::pseudo_fs::PseudoFs;
34
35#[cfg(feature = "async-io")]
36mod async_io;
37mod sync_io;
38
39/// Current directory
40pub const CURRENT_DIR_CSTR: &[u8] = b".\0";
41/// Parent directory
42pub const PARENT_DIR_CSTR: &[u8] = b"..\0";
43/// Emptry CSTR
44pub const EMPTY_CSTR: &[u8] = b"\0";
45/// Proc fd directory
46pub const PROC_SELF_FD_CSTR: &[u8] = b"/proc/self/fd\0";
47/// ASCII for slash('/')
48pub const SLASH_ASCII: u8 = 47;
49
50/// Maximum inode number supported by the VFS for backend file system
51pub const VFS_MAX_INO: u64 = 0xff_ffff_ffff_ffff;
52
53// The 64bit inode number for VFS is divided into two parts:
54// 1. an 8-bit file-system index, to identify mounted backend file systems.
55// 2. the left bits are reserved for backend file systems, and it's limited to VFS_MAX_INO.
56const VFS_INDEX_SHIFT: u8 = 56;
57const VFS_PSEUDO_FS_IDX: VfsIndex = 0;
58
59type ArcBackFs = Arc<BackFileSystem>;
60type ArcSuperBlock = ArcSwap<Vec<Option<Arc<BackFileSystem>>>>;
61type VfsEitherFs<'a> = Either<&'a PseudoFs, ArcBackFs>;
62
63type VfsHandle = u64;
64/// Vfs backend file system index
65pub type VfsIndex = u8;
66
67// VfsIndex is type of 'u8', so maximum 256 entries.
68const MAX_VFS_INDEX: usize = 256;
69
70/// Data struct to store inode number for the VFS filesystem.
71#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
72pub struct VfsInode(u64);
73
74/// Vfs error definition
75#[derive(Debug)]
76pub enum VfsError {
77    /// Operation not supported
78    Unsupported,
79    /// Mount backend filesystem
80    Mount(Error),
81    /// Restore mount backend filesystem
82    RestoreMount(Error),
83    /// Illegal inode index is used
84    InodeIndex(String),
85    /// Filesystem index related. For example, an index can't be allocated.
86    FsIndex(Error),
87    /// Error happened when walking path
88    PathWalk(Error),
89    /// Entry can't be found
90    NotFound(String),
91    /// File system can't ba initialized
92    Initialize(String),
93    /// Error serializing or deserializing the vfs state
94    Persist(String),
95}
96
97impl fmt::Display for VfsError {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        use self::VfsError::*;
100        match self {
101            Unsupported => write!(f, "Vfs operation not supported"),
102            Mount(e) => write!(f, "Mount backend filesystem: {e}"),
103            RestoreMount(e) => write!(f, "Restore mount backend filesystem: {e}"),
104            InodeIndex(s) => write!(f, "Illegal inode index: {s}"),
105            FsIndex(e) => write!(f, "Filesystem index error: {e}"),
106            PathWalk(e) => write!(f, "Walking path error: {e}"),
107            NotFound(s) => write!(f, "Entry can't be found: {s}"),
108            Initialize(s) => write!(f, "File system can't be initialized: {s}"),
109            Persist(e) => write!(f, "Error serializing: {e}"),
110        }
111    }
112}
113
114impl std::error::Error for VfsError {}
115
116/// Vfs result
117pub type VfsResult<T> = std::result::Result<T, VfsError>;
118
119#[inline]
120fn is_dot_or_dotdot(name: &CStr) -> bool {
121    let bytes = name.to_bytes_with_nul();
122    bytes.starts_with(CURRENT_DIR_CSTR) || bytes.starts_with(PARENT_DIR_CSTR)
123}
124
125// Is `path` a single path component that is not "." or ".."?
126fn is_safe_path_component(name: &CStr) -> bool {
127    let bytes = name.to_bytes_with_nul();
128
129    if bytes.contains(&SLASH_ASCII) {
130        return false;
131    }
132    !is_dot_or_dotdot(name)
133}
134
135/// Validate a path component. A well behaved FUSE client should never send dot, dotdot and path
136/// components containing slash ('/'). The only exception is that LOOKUP might contain dot and
137/// dotdot to support NFS export.
138#[inline]
139pub fn validate_path_component(name: &CStr) -> io::Result<()> {
140    match is_safe_path_component(name) {
141        true => Ok(()),
142        false => Err(io::Error::from_raw_os_error(libc::EINVAL)),
143    }
144}
145
146impl VfsInode {
147    fn new(fs_idx: VfsIndex, ino: u64) -> Self {
148        assert_eq!(ino & !VFS_MAX_INO, 0);
149        VfsInode(((fs_idx as u64) << VFS_INDEX_SHIFT) | ino)
150    }
151
152    fn is_pseudo_fs(&self) -> bool {
153        (self.0 >> VFS_INDEX_SHIFT) as VfsIndex == VFS_PSEUDO_FS_IDX
154    }
155
156    fn fs_idx(&self) -> VfsIndex {
157        (self.0 >> VFS_INDEX_SHIFT) as VfsIndex
158    }
159
160    fn ino(&self) -> u64 {
161        self.0 & VFS_MAX_INO
162    }
163}
164
165impl From<u64> for VfsInode {
166    fn from(val: u64) -> Self {
167        VfsInode(val)
168    }
169}
170
171impl From<VfsInode> for u64 {
172    fn from(val: VfsInode) -> Self {
173        val.0
174    }
175}
176
177#[derive(Debug, Clone)]
178enum Either<A, B> {
179    /// First branch of the type
180    Left(A),
181    /// Second branch of the type
182    Right(B),
183}
184use Either::*;
185
186/// Type that implements BackendFileSystem and Sync and Send
187pub type BackFileSystem = Box<dyn BackendFileSystem<Inode = u64, Handle = u64> + Sync + Send>;
188
189#[cfg(not(feature = "async-io"))]
190/// BackendFileSystem abstracts all backend file systems under vfs
191pub trait BackendFileSystem: FileSystem {
192    /// mount returns the backend file system root inode entry and
193    /// the largest inode number it has.
194    fn mount(&self) -> Result<(Entry, u64)> {
195        Err(Error::from_raw_os_error(libc::ENOSYS))
196    }
197
198    /// Provides a reference to the Any trait. This is useful to let
199    /// the caller have access to the underlying type behind the
200    /// trait.
201    fn as_any(&self) -> &dyn Any;
202}
203
204#[cfg(feature = "async-io")]
205/// BackendFileSystem abstracts all backend file systems under vfs
206pub trait BackendFileSystem: AsyncFileSystem {
207    /// mount returns the backend file system root inode entry and
208    /// the largest inode number it has.
209    fn mount(&self) -> Result<(Entry, u64)> {
210        Err(Error::from_raw_os_error(libc::ENOSYS))
211    }
212
213    /// Provides a reference to the Any trait. This is useful to let
214    /// the caller have access to the underlying type behind the
215    /// trait.
216    fn as_any(&self) -> &dyn Any;
217}
218
219struct MountPointData {
220    fs_idx: VfsIndex,
221    ino: u64,
222    root_entry: Entry,
223    _path: String,
224}
225
226#[derive(Debug, Copy, Clone)]
227/// vfs init options
228pub struct VfsOptions {
229    /// Make readdir/readdirplus request return zero dirent even if dir has children.
230    pub no_readdir: bool,
231    /// Reject requests which will change the file size, or allocate file
232    /// blocks exceed file size.
233    pub seal_size: bool,
234    /// File system options passed in from client
235    pub in_opts: FsOptions,
236    /// File system options returned to client
237    pub out_opts: FsOptions,
238    /// Declaration of ID mapping, in the format (internal ID, external ID, range).
239    /// For example, (0, 1, 65536) represents mapping the external UID/GID range of `1~65536`
240    /// to the range of `0~65535` within the filesystem.
241    pub id_mapping: (u32, u32, u32),
242
243    /// Disable fuse open request handling. When enabled, fuse open
244    /// requests are always replied with ENOSYS.
245    #[cfg(target_os = "linux")]
246    pub no_open: bool,
247    /// Disable fuse opendir request handling. When enabled, fuse opendir
248    /// requests are always replied with ENOSYS.
249    #[cfg(target_os = "linux")]
250    pub no_opendir: bool,
251    /// Disable fuse WRITEBACK_CACHE option so that kernel will not cache
252    /// buffer writes.
253    #[cfg(target_os = "linux")]
254    pub no_writeback: bool,
255    /// Enable fuse killpriv_v2 support. When enabled, fuse file system makes sure
256    /// to remove security.capability xattr and setuid/setgid bits. See details in
257    /// comments for HANDLE_KILLPRIV_V2
258    #[cfg(target_os = "linux")]
259    pub killpriv_v2: bool,
260}
261
262impl VfsOptions {
263    fn new() -> Self {
264        VfsOptions::default()
265    }
266}
267
268impl Default for VfsOptions {
269    #[cfg(target_os = "linux")]
270    fn default() -> Self {
271        let out_opts = FsOptions::ASYNC_READ
272            | FsOptions::PARALLEL_DIROPS
273            | FsOptions::BIG_WRITES
274            | FsOptions::ASYNC_DIO
275            | FsOptions::AUTO_INVAL_DATA
276            | FsOptions::HAS_IOCTL_DIR
277            | FsOptions::WRITEBACK_CACHE
278            | FsOptions::ZERO_MESSAGE_OPEN
279            | FsOptions::MAX_PAGES
280            | FsOptions::ATOMIC_O_TRUNC
281            | FsOptions::CACHE_SYMLINKS
282            | FsOptions::DO_READDIRPLUS
283            | FsOptions::READDIRPLUS_AUTO
284            | FsOptions::EXPLICIT_INVAL_DATA
285            | FsOptions::ZERO_MESSAGE_OPENDIR
286            | FsOptions::HANDLE_KILLPRIV_V2
287            | FsOptions::PERFILE_DAX;
288        VfsOptions {
289            no_open: true,
290            no_opendir: true,
291            no_writeback: false,
292            no_readdir: false,
293            seal_size: false,
294            killpriv_v2: false,
295            in_opts: FsOptions::empty(),
296            out_opts,
297            id_mapping: (0, 0, 0),
298        }
299    }
300
301    #[cfg(target_os = "macos")]
302    fn default() -> Self {
303        let out_opts = FsOptions::ASYNC_READ | FsOptions::BIG_WRITES | FsOptions::ATOMIC_O_TRUNC;
304        VfsOptions {
305            no_readdir: false,
306            seal_size: false,
307            in_opts: FsOptions::empty(),
308            out_opts,
309            id_mapping: (0, 0, 0),
310        }
311    }
312}
313
314/// A union fs that combines multiple backend file systems.
315pub struct Vfs {
316    next_super: AtomicU8,
317    root: PseudoFs,
318    // mountpoints maps from pseudo fs inode to mounted fs mountpoint data
319    mountpoints: ArcSwap<HashMap<u64, Arc<MountPointData>>>,
320    // superblocks keeps track of all mounted file systems
321    superblocks: ArcSuperBlock,
322    opts: ArcSwap<VfsOptions>,
323    initialized: AtomicBool,
324    lock: Mutex<()>,
325    remove_pseudo_root: bool,
326    id_mapping: Option<(u32, u32, u32)>,
327}
328
329impl Default for Vfs {
330    fn default() -> Self {
331        Self::new(VfsOptions::new())
332    }
333}
334
335impl Vfs {
336    /// Create a new vfs instance
337    pub fn new(opts: VfsOptions) -> Self {
338        Vfs {
339            next_super: AtomicU8::new(VFS_PSEUDO_FS_IDX + 1),
340            mountpoints: ArcSwap::new(Arc::new(HashMap::new())),
341            superblocks: ArcSwap::new(Arc::new(vec![None; MAX_VFS_INDEX])),
342            root: PseudoFs::new(),
343            opts: ArcSwap::new(Arc::new(opts)),
344            lock: Mutex::new(()),
345            initialized: AtomicBool::new(false),
346            remove_pseudo_root: false,
347            id_mapping: match opts.id_mapping.2 {
348                0 => None,
349                _ => Some(opts.id_mapping),
350            },
351        }
352    }
353
354    /// mark remove pseudo root inode when umount
355    pub fn set_remove_pseudo_root(&mut self) {
356        self.remove_pseudo_root = true;
357    }
358
359    /// For sake of live-upgrade, only after negotiation is done, it's safe to persist
360    /// state of vfs.
361    pub fn initialized(&self) -> bool {
362        self.initialized.load(Ordering::Acquire)
363    }
364
365    /// Get a snapshot of the current vfs options.
366    pub fn options(&self) -> VfsOptions {
367        *self.opts.load_full()
368    }
369
370    fn insert_mount_locked(
371        &self,
372        fs: BackFileSystem,
373        mut entry: Entry,
374        fs_idx: VfsIndex,
375        path: &str,
376    ) -> Result<()> {
377        // The visibility of mountpoints and superblocks:
378        // superblock should be committed first because it won't be accessed until
379        // a lookup returns a cross mountpoint inode.
380        let mut superblocks = self.superblocks.load().deref().deref().clone();
381        let mut mountpoints = self.mountpoints.load().deref().deref().clone();
382        let inode = self.root.mount(path)?;
383        let real_root_ino = entry.inode;
384
385        self.convert_entry(fs_idx, entry.inode, &mut entry)?;
386
387        // Over mount would invalidate previous superblock inodes.
388        if let Some(mnt) = mountpoints.get(&inode) {
389            superblocks[mnt.fs_idx as usize] = None;
390        }
391        superblocks[fs_idx as usize] = Some(Arc::new(fs));
392        self.superblocks.store(Arc::new(superblocks));
393        trace!("fs_idx {} inode {}", fs_idx, inode);
394
395        let mountpoint = Arc::new(MountPointData {
396            fs_idx,
397            ino: real_root_ino,
398            root_entry: entry,
399            _path: path.to_string(),
400        });
401        mountpoints.insert(inode, mountpoint);
402        self.mountpoints.store(Arc::new(mountpoints));
403
404        Ok(())
405    }
406
407    /// Mount a backend file system to path
408    pub fn mount(&self, fs: BackFileSystem, path: &str) -> VfsResult<VfsIndex> {
409        let (entry, ino) = fs.mount().map_err(VfsError::Mount)?;
410        if ino > VFS_MAX_INO {
411            fs.destroy();
412            return Err(VfsError::InodeIndex(format!(
413                "Unsupported max inode number, requested {ino} supported {VFS_MAX_INO}"
414            )));
415        }
416
417        // Serialize mount operations. Do not expect poisoned lock here.
418        let _guard = self.lock.lock().unwrap();
419        if self.initialized() {
420            let opts = self.opts.load().deref().out_opts;
421            fs.init(opts).map_err(|e| {
422                VfsError::Initialize(format!("Can't initialize with opts {opts:?}, {e:?}"))
423            })?;
424        }
425        let index = self.allocate_fs_idx().map_err(VfsError::FsIndex)?;
426        self.insert_mount_locked(fs, entry, index, path)
427            .map_err(VfsError::Mount)?;
428
429        Ok(index)
430    }
431
432    /// Restore a backend file system to path
433    #[cfg(feature = "persist")]
434    pub fn restore_mount(&self, fs: BackFileSystem, fs_idx: VfsIndex, path: &str) -> Result<()> {
435        let (entry, ino) = fs.mount()?;
436        if ino > VFS_MAX_INO {
437            return Err(Error::other(format!(
438                "Unsupported max inode number, requested {} supported {}",
439                ino, VFS_MAX_INO
440            )));
441        }
442
443        let _guard = self.lock.lock().unwrap();
444        self.insert_mount_locked(fs, entry, fs_idx, path)
445    }
446
447    /// Umount a backend file system at path
448    pub fn umount(&self, path: &str) -> VfsResult<(u64, u64)> {
449        // Serialize mount operations. Do not expect poisoned lock here.
450        let _guard = self.lock.lock().unwrap();
451        let inode = self
452            .root
453            .path_walk(path)
454            .map_err(VfsError::PathWalk)?
455            .ok_or_else(|| VfsError::NotFound(path.to_string()))?;
456        let parent = self
457            .root
458            .get_parent_inode(inode)
459            .ok_or(VfsError::NotFound(format!(
460                "{}'s parent inode does not exist",
461                inode
462            )))?;
463        let mut mountpoints = self.mountpoints.load().deref().deref().clone();
464        let fs_idx = mountpoints
465            .get(&inode)
466            .cloned()
467            .map(|x| {
468                // Do not remove pseudofs inode. We keep all pseudofs inode so that
469                // 1. they can be reused later on
470                // 2. during live upgrade, it is easier reconstruct pseudofs inodes since
471                //    we do not have to track pseudofs deletions
472                // In order to make the hot upgrade of virtiofs easy, VFS will save pseudo
473                // inodes when umount for easy recovery. However, in the fuse scenario, if
474                // umount does not remove the pseudo inode, it will cause an invalid
475                // directory to be seen on the host, which is not friendly to users. So add
476                // this option to control this behavior.
477                if self.remove_pseudo_root {
478                    self.root.evict_inode(inode);
479                }
480                mountpoints.remove(&inode);
481                self.mountpoints.store(Arc::new(mountpoints));
482                x.fs_idx
483            })
484            .ok_or_else(|| {
485                error!("{} is not a mount point.", path);
486                VfsError::NotFound(path.to_string())
487            })?;
488
489        trace!("fs_idx {}", fs_idx);
490        let mut superblocks = self.superblocks.load().deref().deref().clone();
491        if let Some(fs) = superblocks[fs_idx as usize].take() {
492            fs.destroy();
493        }
494        self.superblocks.store(Arc::new(superblocks));
495
496        Ok((inode, parent))
497    }
498
499    /// Get the mounted backend file system and its fs_idx alongside the path if there's one.
500    pub fn get_rootfs(&self, path: &str) -> VfsResult<Option<(Arc<BackFileSystem>, u8)>> {
501        // Serialize mount operations. Do not expect poisoned lock here.
502        let _guard = self.lock.lock().unwrap();
503        let inode = match self.root.path_walk(path).map_err(VfsError::PathWalk)? {
504            Some(i) => i,
505            None => return Ok(None),
506        };
507
508        if let Some(mnt) = self.mountpoints.load().get(&inode) {
509            let fs = self
510                .get_fs_by_idx(mnt.fs_idx)
511                .map_err(|e| VfsError::NotFound(format!("fs index {}, {:?}", mnt.fs_idx, e)))?;
512            Ok(Some((fs, mnt.fs_idx)))
513        } else {
514            // Pseudo fs dir inode exists, but that no backend is ever mounted
515            // is a normal case.
516            Ok(None)
517        }
518    }
519
520    /// Get the root pseudo fs's reference in vfs
521    pub fn get_root_pseudofs(&self) -> &PseudoFs {
522        &self.root
523    }
524
525    // Inode converting rules:
526    // 1. Pseudo fs inode is not hashed
527    // 2. Index is always larger than 0 so that pseudo fs inodes are never affected
528    //    and can be found directly
529    // 3. Other inodes are hashed via (index << 56 | inode)
530    fn convert_inode(&self, fs_idx: VfsIndex, inode: u64) -> Result<u64> {
531        // Do not hash negative dentry
532        if inode == 0 {
533            return Ok(inode);
534        }
535        if inode > VFS_MAX_INO {
536            return Err(Error::other(format!(
537                "Inode number {inode} too large, max supported {VFS_MAX_INO}"
538            )));
539        }
540        let ino: u64 = ((fs_idx as u64) << VFS_INDEX_SHIFT) | inode;
541        trace!(
542            "fuse: vfs fs_idx {} inode {} fuse ino {:#x}",
543            fs_idx,
544            inode,
545            ino
546        );
547        Ok(ino)
548    }
549
550    fn convert_entry(&self, fs_idx: VfsIndex, inode: u64, entry: &mut Entry) -> Result<Entry> {
551        self.convert_inode(fs_idx, inode).map(|ino| {
552            entry.inode = ino;
553            entry.attr.st_ino = ino;
554            // If id_mapping is enabled, map the internal ID to the external ID.
555            if let Some((internal_id, external_id, range)) = self.id_mapping {
556                if entry.attr.st_uid >= internal_id && entry.attr.st_uid < internal_id + range {
557                    entry.attr.st_uid += external_id - internal_id;
558                }
559                if entry.attr.st_gid >= internal_id && entry.attr.st_gid < internal_id + range {
560                    entry.attr.st_gid += external_id - internal_id;
561                }
562            }
563            *entry
564        })
565    }
566
567    /// If id_mapping is enabled, remap the uid/gid in attributes.
568    ///
569    /// If `map_internal_to_external` is true, the IDs inside VFS will be mapped
570    /// to external IDs.
571    /// If `map_internal_to_external` is false, the external IDs will be mapped
572    /// to VFS internal IDs.
573    fn remap_attr_id(&self, map_internal_to_external: bool, attr: &mut stat64) {
574        if let Some((internal_id, external_id, range)) = self.id_mapping {
575            if map_internal_to_external
576                && attr.st_uid >= internal_id
577                && attr.st_uid < internal_id + range
578            {
579                attr.st_uid += external_id - internal_id;
580            }
581            if map_internal_to_external
582                && attr.st_gid >= internal_id
583                && attr.st_gid < internal_id + range
584            {
585                attr.st_gid += external_id - internal_id;
586            }
587            if !map_internal_to_external
588                && attr.st_uid >= external_id
589                && attr.st_uid < external_id + range
590            {
591                attr.st_uid += internal_id - external_id;
592            }
593            if !map_internal_to_external
594                && attr.st_gid >= external_id
595                && attr.st_gid < external_id + range
596            {
597                attr.st_gid += internal_id - external_id;
598            }
599        }
600    }
601
602    fn allocate_fs_idx(&self) -> Result<VfsIndex> {
603        let superblocks = self.superblocks.load().deref().deref().clone();
604        let start = self.next_super.load(Ordering::SeqCst);
605        let mut found = false;
606
607        loop {
608            let index = self.next_super.fetch_add(1, Ordering::Relaxed);
609            if index == start {
610                if found {
611                    // There's no available file system index
612                    break;
613                } else {
614                    found = true;
615                }
616            }
617
618            if index == VFS_PSEUDO_FS_IDX {
619                // Skip the pseudo fs index
620                continue;
621            }
622            if (index as usize) < superblocks.len() && superblocks[index as usize].is_some() {
623                // Skip if it's allocated
624                continue;
625            } else {
626                return Ok(index);
627            }
628        }
629
630        Err(Error::other("vfs maximum mountpoints reached"))
631    }
632
633    fn get_fs_by_idx(&self, fs_idx: VfsIndex) -> Result<Arc<BackFileSystem>> {
634        let superblocks = self.superblocks.load();
635
636        if let Some(fs) = &superblocks[fs_idx as usize] {
637            return Ok(fs.clone());
638        }
639
640        Err(Error::from_raw_os_error(libc::ENOENT))
641    }
642
643    fn get_real_rootfs(&self, inode: VfsInode) -> Result<(VfsEitherFs<'_>, VfsInode)> {
644        if inode.is_pseudo_fs() {
645            // ROOT_ID is special, we need to check if we have a mountpoint on the vfs root
646            if inode.ino() == ROOT_ID {
647                if let Some(mnt) = self.mountpoints.load().get(&inode.ino()).cloned() {
648                    let fs = self.get_fs_by_idx(mnt.fs_idx)?;
649                    return Ok((Right(fs), VfsInode::new(mnt.fs_idx, mnt.ino)));
650                }
651            }
652            Ok((Left(&self.root), inode))
653        } else {
654            let fs = self.get_fs_by_idx(inode.fs_idx())?;
655            Ok((Right(fs), inode))
656        }
657    }
658
659    fn lookup_pseudo(
660        &self,
661        fs: &PseudoFs,
662        idata: VfsInode,
663        ctx: &Context,
664        name: &CStr,
665    ) -> Result<Entry> {
666        trace!("lookup pseudo ino {} name {:?}", idata.ino(), name);
667        let mut entry = fs.lookup(ctx, idata.ino(), name)?;
668
669        match self.mountpoints.load().get(&entry.inode) {
670            Some(mnt) => {
671                // cross mountpoint, return mount root entry
672                entry = mnt.root_entry;
673                self.convert_entry(mnt.fs_idx, mnt.ino, &mut entry)?;
674                trace!(
675                    "vfs lookup cross mountpoint, return new mount fs_idx {} inode 0x{:x} fuse inode 0x{:x}, attr inode 0x{:x}",
676                    mnt.fs_idx,
677                    mnt.ino,
678                    entry.inode,
679                    entry.attr.st_ino,
680                );
681                Ok(entry)
682            }
683            None => self.convert_entry(idata.fs_idx(), entry.inode, &mut entry),
684        }
685    }
686}
687
688/// Sava and restore Vfs state.
689#[cfg(feature = "persist")]
690pub mod persist {
691    use std::{
692        ops::Deref,
693        sync::{atomic::Ordering, Arc},
694    };
695
696    use dbs_snapshot::Snapshot;
697    use versionize::{VersionMap, Versionize, VersionizeResult};
698    use versionize_derive::Versionize;
699
700    use crate::api::{
701        filesystem::FsOptions,
702        pseudo_fs::persist::PseudoFsState,
703        vfs::{VfsError, VfsResult},
704        Vfs, VfsOptions,
705    };
706
707    /// VfsState stores the state of the VFS.
708    #[derive(Versionize, Debug)]
709    struct VfsState {
710        /// Vfs options
711        options: VfsOptionsState,
712        /// Vfs root
713        root: Vec<u8>,
714        /// next super block index
715        next_super: u8,
716    }
717
718    #[derive(Versionize, Debug, Default)]
719    struct VfsOptionsState {
720        in_opts: u64,
721        out_opts: u64,
722        no_readdir: bool,
723        seal_size: bool,
724        id_mapping_internal: u32,
725        id_mapping_external: u32,
726        id_mapping_range: u32,
727
728        #[cfg(target_os = "linux")]
729        no_open: bool,
730        #[cfg(target_os = "linux")]
731        no_opendir: bool,
732        #[cfg(target_os = "linux")]
733        no_writeback: bool,
734        #[cfg(target_os = "linux")]
735        killpriv_v2: bool,
736    }
737
738    impl VfsOptions {
739        fn save(&self) -> VfsOptionsState {
740            VfsOptionsState {
741                in_opts: self.in_opts.bits(),
742                out_opts: self.out_opts.bits(),
743                no_readdir: self.no_readdir,
744                seal_size: self.seal_size,
745                id_mapping_internal: self.id_mapping.0,
746                id_mapping_external: self.id_mapping.1,
747                id_mapping_range: self.id_mapping.2,
748
749                #[cfg(target_os = "linux")]
750                no_open: self.no_open,
751                #[cfg(target_os = "linux")]
752                no_opendir: self.no_opendir,
753                #[cfg(target_os = "linux")]
754                no_writeback: self.no_writeback,
755                #[cfg(target_os = "linux")]
756                killpriv_v2: self.killpriv_v2,
757            }
758        }
759
760        fn restore(state: &VfsOptionsState) -> VfsResult<VfsOptions> {
761            Ok(VfsOptions {
762                in_opts: FsOptions::from_bits(state.in_opts).ok_or(VfsError::Persist(
763                    "Failed to restore VfsOptions.in_opts".to_owned(),
764                ))?,
765                out_opts: FsOptions::from_bits(state.out_opts).ok_or(VfsError::Persist(
766                    "Failed to restore VfsOptions.out_opts".to_owned(),
767                ))?,
768                no_readdir: state.no_readdir,
769                seal_size: state.seal_size,
770                id_mapping: (
771                    state.id_mapping_internal,
772                    state.id_mapping_external,
773                    state.id_mapping_range,
774                ),
775
776                #[cfg(target_os = "linux")]
777                no_open: state.no_open,
778                #[cfg(target_os = "linux")]
779                no_opendir: state.no_opendir,
780                #[cfg(target_os = "linux")]
781                no_writeback: state.no_writeback,
782                #[cfg(target_os = "linux")]
783                killpriv_v2: state.killpriv_v2,
784            })
785        }
786    }
787
788    impl Vfs {
789        fn get_version_map() -> versionize::VersionMap {
790            let mut version_map = VersionMap::new();
791            version_map
792                .set_type_version(VfsState::type_id(), 1)
793                .set_type_version(PseudoFsState::type_id(), 1)
794                .set_type_version(VfsOptionsState::type_id(), 1);
795
796            // more versions for the future
797
798            version_map
799        }
800
801        /// Saves part of the Vfs metadata into a byte array.
802        /// The upper layer caller can use this method to save
803        /// and transfer metadata for the reloading in the future.
804        ///
805        /// Note! This function does not save the information
806        /// of the Backend FileSystem mounted by VFS,
807        /// which means that when the caller restores VFS,
808        /// in addition to restoring the information in the byte array
809        /// returned by this function,
810        /// it also needs to manually remount each Backend FileSystem
811        /// according to the Index obtained from the previous mount,
812        /// the method `restore_mount` may be help to do this.
813        ///
814        /// # Example
815        ///
816        /// The following example shows how the function is used in conjunction with
817        /// `restore_from_bytes` to implement the serialization and deserialization of VFS.
818        ///
819        /// ```
820        /// use fuse_backend_rs::api::{Vfs, VfsIndex, VfsOptions};
821        /// use fuse_backend_rs::passthrough::{Config, PassthroughFs};
822        ///
823        /// let new_backend_fs = || {
824        ///     let fs_cfg = Config::default();
825        ///     let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
826        ///     fs.import().unwrap();
827        ///     Box::new(fs)
828        /// };
829        ///
830        /// // create new vfs
831        /// let vfs = &Vfs::new(VfsOptions::default());
832        /// let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
833        /// // record the backend fs and their VfsIndexes
834        /// let backend_fs_list: Vec<(&str, VfsIndex)> = paths
835        ///     .iter()
836        ///     .map(|path| {
837        ///         let fs = new_backend_fs();
838        ///         let idx = vfs.mount(fs, path).unwrap();
839        ///
840        ///         (path.to_owned(), idx)
841        ///     })
842        ///     .collect();
843        ///
844        /// // save the vfs state
845        /// let mut buf = vfs.save_to_bytes().unwrap();
846        ///
847        /// // restore the vfs state
848        /// let restored_vfs = &Vfs::new(VfsOptions::default());
849        /// restored_vfs.restore_from_bytes(&mut buf).unwrap();
850        ///
851        /// // mount the backend fs
852        /// backend_fs_list.into_iter().for_each(|(path, idx)| {
853        ///     let fs = new_backend_fs();
854        ///     vfs.restore_mount(fs, idx, path).unwrap();
855        /// });
856        /// ```
857        pub fn save_to_bytes(&self) -> VfsResult<Vec<u8>> {
858            let root_state = self
859                .root
860                .save_to_bytes()
861                .map_err(|e| VfsError::Persist(format!("Failed to save Vfs root: {:?}", e)))?;
862            let vfs_state = VfsState {
863                options: self.opts.load().deref().deref().save(),
864                root: root_state,
865                next_super: self.next_super.load(Ordering::SeqCst),
866            };
867
868            let vm = Vfs::get_version_map();
869            let target_version = vm.latest_version();
870            let mut s = Snapshot::new(vm, target_version);
871            let mut buf = Vec::new();
872            s.save(&mut buf, &vfs_state).map_err(|e| {
873                VfsError::Persist(format!("Failed to save Vfs using snapshot: {:?}", e))
874            })?;
875
876            Ok(buf)
877        }
878
879        /// Restores part of the Vfs metadata from a byte array.
880        /// For more information, see the example of `save_to_bytes`.
881        pub fn restore_from_bytes(&self, buf: &mut Vec<u8>) -> VfsResult<()> {
882            let mut state: VfsState =
883                Snapshot::load(&mut buf.as_slice(), buf.len(), Vfs::get_version_map())
884                    .map_err(|e| {
885                        VfsError::Persist(format!("Failed to load Vfs using snapshot: {:?}", e))
886                    })?
887                    .0;
888            let opts = VfsOptions::restore(&state.options)?;
889            self.initialized
890                .store(!opts.in_opts.is_empty(), Ordering::Release);
891            self.opts.store(Arc::new(opts));
892
893            self.next_super.store(state.next_super, Ordering::SeqCst);
894            self.root
895                .restore_from_bytes(&mut state.root)
896                .map_err(|e| VfsError::Persist(format!("Failed to restore Vfs root: {:?}", e)))?;
897
898            Ok(())
899        }
900    }
901
902    mod test {
903
904        // This test is to make sure that VfsState can be serialized and deserialized
905        #[test]
906        fn test_vfs_save_restore_simple() {
907            use crate::api::{Vfs, VfsOptions};
908
909            // create new vfs
910            let vfs = &Vfs::new(VfsOptions::default());
911
912            // save the vfs state using Snapshot
913            let mut buf = vfs.save_to_bytes().unwrap();
914
915            // restore the vfs state
916            let vfs = &Vfs::new(VfsOptions::default());
917            vfs.restore_from_bytes(&mut buf).unwrap();
918            assert_eq!(vfs.next_super.load(std::sync::atomic::Ordering::SeqCst), 1);
919        }
920
921        #[test]
922        fn test_vfs_save_restore_with_backend_fs() {
923            use crate::api::{Vfs, VfsIndex, VfsOptions};
924            use crate::passthrough::{Config, PassthroughFs};
925
926            let new_backend_fs = || {
927                let fs_cfg = Config::default();
928                let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
929                fs.import().unwrap();
930                Box::new(fs)
931            };
932
933            // create new vfs
934            let vfs = &Vfs::new(VfsOptions::default());
935            let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
936            let backend_fs_list: Vec<(&str, VfsIndex)> = paths
937                .iter()
938                .map(|path| {
939                    let fs = new_backend_fs();
940                    let idx = vfs.mount(fs, path).unwrap();
941
942                    (path.to_owned(), idx)
943                })
944                .collect();
945
946            // save the vfs state using Snapshot
947            let mut buf = vfs.save_to_bytes().unwrap();
948
949            // restore the vfs state
950            let restored_vfs = &Vfs::new(VfsOptions::default());
951            restored_vfs.restore_from_bytes(&mut buf).unwrap();
952            // restore the backend fs
953            backend_fs_list.into_iter().for_each(|(path, idx)| {
954                let fs = new_backend_fs();
955                vfs.restore_mount(fs, idx, path).unwrap();
956            });
957
958            // check the vfs and restored_vfs
959            assert_eq!(
960                vfs.next_super.load(std::sync::atomic::Ordering::SeqCst),
961                restored_vfs
962                    .next_super
963                    .load(std::sync::atomic::Ordering::SeqCst)
964            );
965            assert_eq!(
966                vfs.initialized.load(std::sync::atomic::Ordering::SeqCst),
967                restored_vfs
968                    .initialized
969                    .load(std::sync::atomic::Ordering::SeqCst)
970            );
971            for path in paths.iter() {
972                let inode = vfs.root.path_walk(path).unwrap();
973                let restored_inode = restored_vfs.root.path_walk(path).unwrap();
974                assert_eq!(inode, restored_inode);
975            }
976        }
977
978        #[test]
979        fn test_vfs_save_restore_with_backend_fs_with_initialized() {
980            use crate::api::filesystem::{FileSystem, FsOptions};
981            use crate::api::{Vfs, VfsIndex, VfsOptions};
982            use crate::passthrough::{Config, PassthroughFs};
983            use std::sync::atomic::Ordering;
984
985            let new_backend_fs = || {
986                let fs_cfg = Config::default();
987                let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
988                fs.import().unwrap();
989                Box::new(fs)
990            };
991
992            // create new vfs
993            let vfs = &Vfs::new(VfsOptions::default());
994            let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
995            let backend_fs_list: Vec<(&str, VfsIndex)> = paths
996                .iter()
997                .map(|path| {
998                    let fs = new_backend_fs();
999                    let idx = vfs.mount(fs, path).unwrap();
1000
1001                    (path.to_owned(), idx)
1002                })
1003                .collect();
1004            vfs.init(FsOptions::ASYNC_READ).unwrap();
1005            assert!(vfs.initialized.load(Ordering::Acquire));
1006
1007            // save the vfs state using Snapshot
1008            let mut buf = vfs.save_to_bytes().unwrap();
1009
1010            // restore the vfs state
1011            let restored_vfs = &Vfs::new(VfsOptions::default());
1012            restored_vfs.restore_from_bytes(&mut buf).unwrap();
1013
1014            // restore the backend fs
1015            backend_fs_list.into_iter().for_each(|(path, idx)| {
1016                let fs = new_backend_fs();
1017                vfs.restore_mount(fs, idx, path).unwrap();
1018            });
1019
1020            // check the vfs and restored_vfs
1021            assert_eq!(
1022                vfs.next_super.load(std::sync::atomic::Ordering::SeqCst),
1023                restored_vfs
1024                    .next_super
1025                    .load(std::sync::atomic::Ordering::SeqCst)
1026            );
1027            assert_eq!(
1028                vfs.initialized.load(std::sync::atomic::Ordering::Acquire),
1029                restored_vfs
1030                    .initialized
1031                    .load(std::sync::atomic::Ordering::Acquire)
1032            );
1033            for path in paths.iter() {
1034                let inode = vfs.root.path_walk(path).unwrap();
1035                let restored_inode = restored_vfs.root.path_walk(path).unwrap();
1036                assert_eq!(inode, restored_inode);
1037            }
1038        }
1039    }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044    use super::*;
1045    use crate::api::Vfs;
1046    use std::ffi::CString;
1047    use std::io::{Error, ErrorKind};
1048
1049    pub(crate) struct FakeFileSystemOne {}
1050    impl FileSystem for FakeFileSystemOne {
1051        type Inode = u64;
1052        type Handle = u64;
1053        fn lookup(&self, _: &Context, _: Self::Inode, _: &CStr) -> Result<Entry> {
1054            Ok(Entry::default())
1055        }
1056        fn getattr(
1057            &self,
1058            _ctx: &Context,
1059            _inode: Self::Inode,
1060            _handle: Option<Self::Handle>,
1061        ) -> Result<(stat64, Duration)> {
1062            let mut attr = Attr {
1063                ..Default::default()
1064            };
1065            attr.ino = 1;
1066            Ok((attr.into(), Duration::from_secs(1)))
1067        }
1068    }
1069
1070    pub(crate) struct FakeFileSystemTwo {}
1071    impl FileSystem for FakeFileSystemTwo {
1072        type Inode = u64;
1073        type Handle = u64;
1074        fn lookup(&self, _: &Context, _: Self::Inode, _: &CStr) -> Result<Entry> {
1075            Ok(Entry {
1076                inode: 1,
1077                ..Default::default()
1078            })
1079        }
1080    }
1081
1082    #[test]
1083    fn test_is_safe_path_component() {
1084        let name = CStr::from_bytes_with_nul(b"normal\0").unwrap();
1085        assert!(is_safe_path_component(name), "\"{:?}\"", name);
1086
1087        let name = CStr::from_bytes_with_nul(b".a\0").unwrap();
1088        assert!(is_safe_path_component(name));
1089
1090        let name = CStr::from_bytes_with_nul(b"a.a\0").unwrap();
1091        assert!(is_safe_path_component(name));
1092
1093        let name = CStr::from_bytes_with_nul(b"a.a\0").unwrap();
1094        assert!(is_safe_path_component(name));
1095
1096        let name = CStr::from_bytes_with_nul(b"/\0").unwrap();
1097        assert!(!is_safe_path_component(name));
1098
1099        let name = CStr::from_bytes_with_nul(b"/a\0").unwrap();
1100        assert!(!is_safe_path_component(name));
1101
1102        let name = CStr::from_bytes_with_nul(b".\0").unwrap();
1103        assert!(!is_safe_path_component(name));
1104
1105        let name = CStr::from_bytes_with_nul(b"..\0").unwrap();
1106        assert!(!is_safe_path_component(name));
1107
1108        let name = CStr::from_bytes_with_nul(b"../.\0").unwrap();
1109        assert!(!is_safe_path_component(name));
1110
1111        let name = CStr::from_bytes_with_nul(b"a/b\0").unwrap();
1112        assert!(!is_safe_path_component(name));
1113
1114        let name = CStr::from_bytes_with_nul(b"./../a\0").unwrap();
1115        assert!(!is_safe_path_component(name));
1116    }
1117
1118    #[test]
1119    fn test_is_dot_or_dotdot() {
1120        let name = CStr::from_bytes_with_nul(b"..\0").unwrap();
1121        assert!(is_dot_or_dotdot(name));
1122
1123        let name = CStr::from_bytes_with_nul(b".\0").unwrap();
1124        assert!(is_dot_or_dotdot(name));
1125
1126        let name = CStr::from_bytes_with_nul(b"...\0").unwrap();
1127        assert!(!is_dot_or_dotdot(name));
1128
1129        let name = CStr::from_bytes_with_nul(b"./.\0").unwrap();
1130        assert!(!is_dot_or_dotdot(name));
1131
1132        let name = CStr::from_bytes_with_nul(b"a\0").unwrap();
1133        assert!(!is_dot_or_dotdot(name));
1134
1135        let name = CStr::from_bytes_with_nul(b"aa\0").unwrap();
1136        assert!(!is_dot_or_dotdot(name));
1137
1138        let name = CStr::from_bytes_with_nul(b"/a\0").unwrap();
1139        assert!(!is_dot_or_dotdot(name));
1140
1141        let name = CStr::from_bytes_with_nul(b"a/\0").unwrap();
1142        assert!(!is_dot_or_dotdot(name));
1143    }
1144
1145    #[cfg(feature = "async-io")]
1146    mod async_io {
1147        use super::*;
1148        use crate::abi::fuse_abi::{OpenOptions, SetattrValid};
1149        use async_trait::async_trait;
1150
1151        #[allow(unused_variables)]
1152        #[async_trait]
1153        impl AsyncFileSystem for FakeFileSystemOne {
1154            async fn async_lookup(
1155                &self,
1156                ctx: &Context,
1157                parent: <Self as FileSystem>::Inode,
1158                name: &CStr,
1159            ) -> Result<Entry> {
1160                Ok(Entry::default())
1161            }
1162
1163            async fn async_getattr(
1164                &self,
1165                ctx: &Context,
1166                inode: <Self as FileSystem>::Inode,
1167                handle: Option<<Self as FileSystem>::Handle>,
1168            ) -> Result<(libc::stat64, Duration)> {
1169                unimplemented!()
1170            }
1171
1172            async fn async_setattr(
1173                &self,
1174                ctx: &Context,
1175                inode: <Self as FileSystem>::Inode,
1176                attr: libc::stat64,
1177                handle: Option<<Self as FileSystem>::Handle>,
1178                valid: SetattrValid,
1179            ) -> Result<(libc::stat64, Duration)> {
1180                unimplemented!()
1181            }
1182
1183            async fn async_open(
1184                &self,
1185                ctx: &Context,
1186                inode: <Self as FileSystem>::Inode,
1187                flags: u32,
1188                fuse_flags: u32,
1189            ) -> Result<(Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1190                unimplemented!()
1191            }
1192
1193            async fn async_create(
1194                &self,
1195                ctx: &Context,
1196                parent: <Self as FileSystem>::Inode,
1197                name: &CStr,
1198                args: CreateIn,
1199            ) -> Result<(Entry, Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1200                unimplemented!()
1201            }
1202
1203            async fn async_read(
1204                &self,
1205                ctx: &Context,
1206                inode: <Self as FileSystem>::Inode,
1207                handle: <Self as FileSystem>::Handle,
1208                w: &mut (dyn AsyncZeroCopyWriter + Send),
1209                size: u32,
1210                offset: u64,
1211                lock_owner: Option<u64>,
1212                flags: u32,
1213            ) -> Result<usize> {
1214                unimplemented!()
1215            }
1216
1217            async fn async_write(
1218                &self,
1219                ctx: &Context,
1220                inode: <Self as FileSystem>::Inode,
1221                handle: <Self as FileSystem>::Handle,
1222                r: &mut (dyn AsyncZeroCopyReader + Send),
1223                size: u32,
1224                offset: u64,
1225                lock_owner: Option<u64>,
1226                delayed_write: bool,
1227                flags: u32,
1228                fuse_flags: u32,
1229            ) -> Result<usize> {
1230                unimplemented!()
1231            }
1232
1233            async fn async_fsync(
1234                &self,
1235                ctx: &Context,
1236                inode: <Self as FileSystem>::Inode,
1237                datasync: bool,
1238                handle: <Self as FileSystem>::Handle,
1239            ) -> Result<()> {
1240                unimplemented!()
1241            }
1242
1243            async fn async_fallocate(
1244                &self,
1245                ctx: &Context,
1246                inode: <Self as FileSystem>::Inode,
1247                handle: <Self as FileSystem>::Handle,
1248                mode: u32,
1249                offset: u64,
1250                length: u64,
1251            ) -> Result<()> {
1252                unimplemented!()
1253            }
1254
1255            async fn async_fsyncdir(
1256                &self,
1257                ctx: &Context,
1258                inode: <Self as FileSystem>::Inode,
1259                datasync: bool,
1260                handle: <Self as FileSystem>::Handle,
1261            ) -> Result<()> {
1262                unimplemented!()
1263            }
1264        }
1265
1266        impl BackendFileSystem for FakeFileSystemOne {
1267            fn mount(&self) -> Result<(Entry, u64)> {
1268                Ok((
1269                    Entry {
1270                        inode: 1,
1271                        ..Default::default()
1272                    },
1273                    0,
1274                ))
1275            }
1276
1277            fn as_any(&self) -> &dyn Any {
1278                self
1279            }
1280        }
1281
1282        #[allow(unused_variables)]
1283        #[async_trait]
1284        impl AsyncFileSystem for FakeFileSystemTwo {
1285            async fn async_lookup(
1286                &self,
1287                ctx: &Context,
1288                parent: <Self as FileSystem>::Inode,
1289                name: &CStr,
1290            ) -> Result<Entry> {
1291                Err(std::io::Error::from_raw_os_error(libc::EINVAL))
1292            }
1293
1294            async fn async_getattr(
1295                &self,
1296                ctx: &Context,
1297                inode: <Self as FileSystem>::Inode,
1298                handle: Option<<Self as FileSystem>::Handle>,
1299            ) -> Result<(libc::stat64, Duration)> {
1300                unimplemented!()
1301            }
1302
1303            async fn async_setattr(
1304                &self,
1305                ctx: &Context,
1306                inode: <Self as FileSystem>::Inode,
1307                attr: libc::stat64,
1308                handle: Option<<Self as FileSystem>::Handle>,
1309                valid: SetattrValid,
1310            ) -> Result<(libc::stat64, Duration)> {
1311                unimplemented!()
1312            }
1313
1314            async fn async_open(
1315                &self,
1316                ctx: &Context,
1317                inode: <Self as FileSystem>::Inode,
1318                flags: u32,
1319                fuse_flags: u32,
1320            ) -> Result<(Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1321                unimplemented!()
1322            }
1323
1324            async fn async_create(
1325                &self,
1326                ctx: &Context,
1327                parent: <Self as FileSystem>::Inode,
1328                name: &CStr,
1329                args: CreateIn,
1330            ) -> Result<(Entry, Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1331                unimplemented!()
1332            }
1333
1334            async fn async_read(
1335                &self,
1336                ctx: &Context,
1337                inode: <Self as FileSystem>::Inode,
1338                handle: <Self as FileSystem>::Handle,
1339                w: &mut (dyn AsyncZeroCopyWriter + Send),
1340                size: u32,
1341                offset: u64,
1342                lock_owner: Option<u64>,
1343                flags: u32,
1344            ) -> Result<usize> {
1345                unimplemented!()
1346            }
1347
1348            async fn async_write(
1349                &self,
1350                ctx: &Context,
1351                inode: <Self as FileSystem>::Inode,
1352                handle: <Self as FileSystem>::Handle,
1353                r: &mut (dyn AsyncZeroCopyReader + Send),
1354                size: u32,
1355                offset: u64,
1356                lock_owner: Option<u64>,
1357                delayed_write: bool,
1358                flags: u32,
1359                fuse_flags: u32,
1360            ) -> Result<usize> {
1361                unimplemented!()
1362            }
1363
1364            async fn async_fsync(
1365                &self,
1366                ctx: &Context,
1367                inode: <Self as FileSystem>::Inode,
1368                datasync: bool,
1369                handle: <Self as FileSystem>::Handle,
1370            ) -> Result<()> {
1371                unimplemented!()
1372            }
1373
1374            async fn async_fallocate(
1375                &self,
1376                ctx: &Context,
1377                inode: <Self as FileSystem>::Inode,
1378                handle: <Self as FileSystem>::Handle,
1379                mode: u32,
1380                offset: u64,
1381                length: u64,
1382            ) -> Result<()> {
1383                unimplemented!()
1384            }
1385
1386            async fn async_fsyncdir(
1387                &self,
1388                ctx: &Context,
1389                inode: <Self as FileSystem>::Inode,
1390                datasync: bool,
1391                handle: <Self as FileSystem>::Handle,
1392            ) -> Result<()> {
1393                unimplemented!()
1394            }
1395        }
1396
1397        impl BackendFileSystem for FakeFileSystemTwo {
1398            fn mount(&self) -> Result<(Entry, u64)> {
1399                Ok((
1400                    Entry {
1401                        inode: 1,
1402                        ..Default::default()
1403                    },
1404                    0,
1405                ))
1406            }
1407            fn as_any(&self) -> &dyn Any {
1408                self
1409            }
1410        }
1411    }
1412
1413    #[cfg(not(feature = "async-io"))]
1414    impl BackendFileSystem for FakeFileSystemOne {
1415        fn mount(&self) -> Result<(Entry, u64)> {
1416            Ok((
1417                Entry {
1418                    inode: 1,
1419                    ..Default::default()
1420                },
1421                0,
1422            ))
1423        }
1424
1425        fn as_any(&self) -> &dyn Any {
1426            self
1427        }
1428    }
1429
1430    #[cfg(not(feature = "async-io"))]
1431    impl BackendFileSystem for FakeFileSystemTwo {
1432        fn mount(&self) -> Result<(Entry, u64)> {
1433            Ok((
1434                Entry {
1435                    inode: 1,
1436                    ..Default::default()
1437                },
1438                0,
1439            ))
1440        }
1441        fn as_any(&self) -> &dyn Any {
1442            self
1443        }
1444    }
1445
1446    #[test]
1447    fn test_vfs_init() {
1448        let vfs = Vfs::default();
1449        assert_eq!(vfs.initialized(), false);
1450
1451        let opts = vfs.opts.load();
1452        let out_opts = opts.out_opts;
1453
1454        #[cfg(target_os = "linux")]
1455        {
1456            assert_eq!(opts.no_open, true);
1457            assert_eq!(opts.no_opendir, true);
1458            assert_eq!(opts.no_writeback, false);
1459            assert_eq!(opts.killpriv_v2, false);
1460        }
1461        assert_eq!(opts.no_readdir, false);
1462        assert_eq!(opts.seal_size, false);
1463        assert_eq!(opts.in_opts.is_empty(), true);
1464
1465        vfs.init(FsOptions::ASYNC_READ).unwrap();
1466        assert_eq!(vfs.initialized(), true);
1467
1468        let opts = vfs.opts.load();
1469        #[cfg(target_os = "linux")]
1470        {
1471            assert_eq!(opts.no_open, false);
1472            assert_eq!(opts.no_opendir, false);
1473            assert_eq!(opts.no_writeback, false);
1474            assert_eq!(opts.killpriv_v2, false);
1475        }
1476        assert_eq!(opts.no_readdir, false);
1477        assert_eq!(opts.seal_size, false);
1478
1479        vfs.destroy();
1480        assert_eq!(vfs.initialized(), false);
1481
1482        let vfs = Vfs::default();
1483        #[cfg(target_os = "linux")]
1484        let in_opts =
1485            FsOptions::ASYNC_READ | FsOptions::ZERO_MESSAGE_OPEN | FsOptions::ZERO_MESSAGE_OPENDIR;
1486        #[cfg(target_os = "macos")]
1487        let in_opts = FsOptions::ASYNC_READ;
1488        vfs.init(in_opts).unwrap();
1489        let opts = vfs.opts.load();
1490        #[cfg(target_os = "linux")]
1491        {
1492            assert_eq!(opts.no_open, true);
1493            assert_eq!(opts.no_opendir, true);
1494            assert_eq!(opts.no_writeback, false);
1495            assert_eq!(opts.killpriv_v2, false);
1496        }
1497        assert_eq!(opts.out_opts, out_opts & in_opts);
1498    }
1499
1500    #[test]
1501    fn test_vfs_lookup() {
1502        let vfs = Vfs::new(VfsOptions::default());
1503        let fs = FakeFileSystemOne {};
1504        let ctx = Context::new();
1505
1506        assert!(vfs.mount(Box::new(fs), "/x/y").is_ok());
1507
1508        // Lookup inode on pseudo file system.
1509        let entry1 = vfs
1510            .lookup(&ctx, ROOT_ID.into(), CString::new("x").unwrap().as_c_str())
1511            .unwrap();
1512        assert_eq!(entry1.inode, 0x2);
1513
1514        // Lookup inode on mounted file system.
1515        let entry2 = vfs
1516            .lookup(
1517                &ctx,
1518                entry1.inode.into(),
1519                CString::new("y").unwrap().as_c_str(),
1520            )
1521            .unwrap();
1522        assert_eq!(entry2.inode, 0x100_0000_0000_0001);
1523
1524        // lookup for negative result.
1525        let entry3 = vfs
1526            .lookup(
1527                &ctx,
1528                entry2.inode.into(),
1529                CString::new("z").unwrap().as_c_str(),
1530            )
1531            .unwrap();
1532        assert_eq!(entry3.inode, 0);
1533
1534        let (stat, _) = vfs
1535            .getattr(&ctx, VfsInode(0x100_0000_0000_0001), None)
1536            .unwrap();
1537        assert_eq!(stat.st_ino, 0x100_0000_0000_0001);
1538    }
1539
1540    #[test]
1541    fn test_mount_different_fs_types() {
1542        let vfs = Vfs::new(VfsOptions::default());
1543        let fs1 = FakeFileSystemOne {};
1544        let fs2 = FakeFileSystemTwo {};
1545        assert!(vfs.mount(Box::new(fs1), "/foo").is_ok());
1546        assert!(vfs.mount(Box::new(fs2), "/bar").is_ok());
1547
1548        // Lookup inode on pseudo file system.
1549        let ctx = Context::new();
1550        let entry1 = vfs
1551            .lookup(
1552                &ctx,
1553                ROOT_ID.into(),
1554                CString::new("bar").unwrap().as_c_str(),
1555            )
1556            .unwrap();
1557        assert_eq!(entry1.inode, 0x200_0000_0000_0001);
1558        assert_eq!(entry1.attr.st_ino, 0x200_0000_0000_0001);
1559    }
1560
1561    #[test]
1562    fn test_umount() {
1563        let vfs = Vfs::new(VfsOptions::default());
1564        let fs1 = FakeFileSystemOne {};
1565        let fs2 = FakeFileSystemOne {};
1566        assert!(vfs.mount(Box::new(fs1), "/foo").is_ok());
1567        assert!(vfs.umount("/foo").is_ok());
1568
1569        assert!(vfs.mount(Box::new(fs2), "/x/y").is_ok());
1570
1571        match vfs.umount("/x") {
1572            Err(VfsError::NotFound(_e)) => {}
1573            _ => panic!("expect VfsError::NotFound(/x)"),
1574        }
1575    }
1576
1577    #[test]
1578    fn test_umount_overlap() {
1579        let vfs = Vfs::new(VfsOptions::default());
1580        let fs1 = FakeFileSystemOne {};
1581        let fs2 = FakeFileSystemTwo {};
1582
1583        assert!(vfs.mount(Box::new(fs1), "/x/y/z").is_ok());
1584        assert!(vfs.mount(Box::new(fs2), "/x/y").is_ok());
1585
1586        let (m1, _) = vfs.get_rootfs("/x/y/z").unwrap().unwrap();
1587        assert!(m1.as_any().is::<FakeFileSystemOne>());
1588        let (m2, _) = vfs.get_rootfs("/x/y").unwrap().unwrap();
1589        assert!(m2.as_any().is::<FakeFileSystemTwo>());
1590
1591        assert!(vfs.umount("/x/y/z").is_ok());
1592        assert!(vfs.umount("/x/y").is_ok());
1593
1594        match vfs.umount("/x/y/z") {
1595            Err(VfsError::NotFound(_e)) => {}
1596            _ => panic!("expect VfsError::NotFound(/x/y/z)"),
1597        }
1598    }
1599
1600    #[test]
1601    fn test_umount_same() {
1602        let vfs = Vfs::new(VfsOptions::default());
1603        let fs1 = FakeFileSystemOne {};
1604        let fs2 = FakeFileSystemTwo {};
1605
1606        assert!(vfs.mount(Box::new(fs1), "/x/y").is_ok());
1607        assert!(vfs.mount(Box::new(fs2), "/x/y").is_ok());
1608
1609        let (m1, _) = vfs.get_rootfs("/x/y").unwrap().unwrap();
1610        assert!(m1.as_any().is::<FakeFileSystemTwo>());
1611
1612        assert!(vfs.umount("/x/y").is_ok());
1613
1614        match vfs.umount("/x/y") {
1615            Err(VfsError::NotFound(_e)) => {}
1616            _ => panic!("expect VfsError::NotFound(/x/y)"),
1617        }
1618    }
1619
1620    #[test]
1621    #[should_panic]
1622    fn test_invalid_inode() {
1623        let _ = VfsInode::new(1, VFS_MAX_INO + 1);
1624    }
1625
1626    #[test]
1627    fn test_inode() {
1628        let inode = VfsInode::new(2, VFS_MAX_INO);
1629
1630        assert_eq!(inode.fs_idx(), 2);
1631        assert_eq!(inode.ino(), VFS_MAX_INO);
1632        assert!(!inode.is_pseudo_fs());
1633        assert_eq!(u64::from(inode), 0x200_0000_0000_0000u64 + VFS_MAX_INO);
1634    }
1635
1636    #[test]
1637    fn test_allocate_fs_idx() {
1638        let vfs = Vfs::new(VfsOptions::default());
1639        let _guard = vfs.lock.lock().unwrap();
1640
1641        // Test case: allocate all available fs idx
1642        for _ in 0..255 {
1643            let fs = FakeFileSystemOne {};
1644            let index = vfs.allocate_fs_idx().unwrap();
1645            let mut superblocks = vfs.superblocks.load().deref().deref().clone();
1646
1647            superblocks[index as usize] = Some(Arc::new(Box::new(fs)));
1648            vfs.superblocks.store(Arc::new(superblocks));
1649        }
1650
1651        // Test case: fail to allocate more fs idx if all have been allocated
1652        for _ in 0..=256 {
1653            vfs.allocate_fs_idx().unwrap_err();
1654        }
1655    }
1656
1657    #[test]
1658    fn test_fmt_vfs_error() {
1659        assert_eq!(
1660            format!("{}", VfsError::Unsupported),
1661            "Vfs operation not supported".to_string()
1662        );
1663        assert_eq!(
1664            format!(
1665                "{}",
1666                VfsError::Mount(Error::new(ErrorKind::Other, "mount".to_string()),)
1667            ),
1668            "Mount backend filesystem: mount".to_string()
1669        );
1670        assert_eq!(
1671            format!("{}", VfsError::InodeIndex("inode index".to_string())),
1672            "Illegal inode index: inode index".to_string()
1673        );
1674        assert_eq!(
1675            format!(
1676                "{}",
1677                VfsError::FsIndex(Error::new(ErrorKind::Other, "fs index".to_string()),)
1678            ),
1679            "Filesystem index error: fs index".to_string()
1680        );
1681        assert_eq!(
1682            format!(
1683                "{}",
1684                VfsError::PathWalk(Error::new(ErrorKind::Other, "path walk".to_string()),)
1685            ),
1686            "Walking path error: path walk".to_string()
1687        );
1688        assert_eq!(
1689            format!("{}", VfsError::NotFound("not found".to_string())),
1690            "Entry can't be found: not found".to_string()
1691        );
1692        assert_eq!(
1693            format!("{}", VfsError::Initialize("initialize".to_string())),
1694            "File system can't be initialized: initialize".to_string()
1695        );
1696    }
1697}