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, ErrorKind, 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::new(
438                ErrorKind::Other,
439                format!(
440                    "Unsupported max inode number, requested {} supported {}",
441                    ino, VFS_MAX_INO
442                ),
443            ));
444        }
445
446        let _guard = self.lock.lock().unwrap();
447        self.insert_mount_locked(fs, entry, fs_idx, path)
448    }
449
450    /// Umount a backend file system at path
451    pub fn umount(&self, path: &str) -> VfsResult<(u64, u64)> {
452        // Serialize mount operations. Do not expect poisoned lock here.
453        let _guard = self.lock.lock().unwrap();
454        let inode = self
455            .root
456            .path_walk(path)
457            .map_err(VfsError::PathWalk)?
458            .ok_or_else(|| VfsError::NotFound(path.to_string()))?;
459        let parent = self
460            .root
461            .get_parent_inode(inode)
462            .ok_or(VfsError::NotFound(format!(
463                "{}'s parent inode does not exist",
464                inode
465            )))?;
466        let mut mountpoints = self.mountpoints.load().deref().deref().clone();
467        let fs_idx = mountpoints
468            .get(&inode)
469            .map(Arc::clone)
470            .map(|x| {
471                // Do not remove pseudofs inode. We keep all pseudofs inode so that
472                // 1. they can be reused later on
473                // 2. during live upgrade, it is easier reconstruct pseudofs inodes since
474                //    we do not have to track pseudofs deletions
475                // In order to make the hot upgrade of virtiofs easy, VFS will save pseudo
476                // inodes when umount for easy recovery. However, in the fuse scenario, if
477                // umount does not remove the pseudo inode, it will cause an invalid
478                // directory to be seen on the host, which is not friendly to users. So add
479                // this option to control this behavior.
480                if self.remove_pseudo_root {
481                    self.root.evict_inode(inode);
482                }
483                mountpoints.remove(&inode);
484                self.mountpoints.store(Arc::new(mountpoints));
485                x.fs_idx
486            })
487            .ok_or_else(|| {
488                error!("{} is not a mount point.", path);
489                VfsError::NotFound(path.to_string())
490            })?;
491
492        trace!("fs_idx {}", fs_idx);
493        let mut superblocks = self.superblocks.load().deref().deref().clone();
494        if let Some(fs) = superblocks[fs_idx as usize].take() {
495            fs.destroy();
496        }
497        self.superblocks.store(Arc::new(superblocks));
498
499        Ok((inode, parent))
500    }
501
502    /// Get the mounted backend file system alongside the path if there's one.
503    pub fn get_rootfs(&self, path: &str) -> VfsResult<Option<Arc<BackFileSystem>>> {
504        // Serialize mount operations. Do not expect poisoned lock here.
505        let _guard = self.lock.lock().unwrap();
506        let inode = match self.root.path_walk(path).map_err(VfsError::PathWalk)? {
507            Some(i) => i,
508            None => return Ok(None),
509        };
510
511        if let Some(mnt) = self.mountpoints.load().get(&inode) {
512            Ok(Some(self.get_fs_by_idx(mnt.fs_idx).map_err(|e| {
513                VfsError::NotFound(format!("fs index {}, {:?}", mnt.fs_idx, e))
514            })?))
515        } else {
516            // Pseudo fs dir inode exists, but that no backend is ever mounted
517            // is a normal case.
518            Ok(None)
519        }
520    }
521
522    /// Get the root pseudo fs's reference in vfs
523    pub fn get_root_pseudofs(&self) -> &PseudoFs {
524        &self.root
525    }
526
527    // Inode converting rules:
528    // 1. Pseudo fs inode is not hashed
529    // 2. Index is always larger than 0 so that pseudo fs inodes are never affected
530    //    and can be found directly
531    // 3. Other inodes are hashed via (index << 56 | inode)
532    fn convert_inode(&self, fs_idx: VfsIndex, inode: u64) -> Result<u64> {
533        // Do not hash negative dentry
534        if inode == 0 {
535            return Ok(inode);
536        }
537        if inode > VFS_MAX_INO {
538            return Err(Error::new(
539                ErrorKind::Other,
540                format!("Inode number {inode} too large, max supported {VFS_MAX_INO}"),
541            ));
542        }
543        let ino: u64 = ((fs_idx as u64) << VFS_INDEX_SHIFT) | inode;
544        trace!(
545            "fuse: vfs fs_idx {} inode {} fuse ino {:#x}",
546            fs_idx,
547            inode,
548            ino
549        );
550        Ok(ino)
551    }
552
553    fn convert_entry(&self, fs_idx: VfsIndex, inode: u64, entry: &mut Entry) -> Result<Entry> {
554        self.convert_inode(fs_idx, inode).map(|ino| {
555            entry.inode = ino;
556            entry.attr.st_ino = ino;
557            // If id_mapping is enabled, map the internal ID to the external ID.
558            if let Some((internal_id, external_id, range)) = self.id_mapping {
559                if entry.attr.st_uid >= internal_id && entry.attr.st_uid < internal_id + range {
560                    entry.attr.st_uid += external_id - internal_id;
561                }
562                if entry.attr.st_gid >= internal_id && entry.attr.st_gid < internal_id + range {
563                    entry.attr.st_gid += external_id - internal_id;
564                }
565            }
566            *entry
567        })
568    }
569
570    /// If id_mapping is enabled, remap the uid/gid in attributes.
571    ///
572    /// If `map_internal_to_external` is true, the IDs inside VFS will be mapped
573    /// to external IDs.
574    /// If `map_internal_to_external` is false, the external IDs will be mapped
575    /// to VFS internal IDs.
576    fn remap_attr_id(&self, map_internal_to_external: bool, attr: &mut stat64) {
577        if let Some((internal_id, external_id, range)) = self.id_mapping {
578            if map_internal_to_external
579                && attr.st_uid >= internal_id
580                && attr.st_uid < internal_id + range
581            {
582                attr.st_uid += external_id - internal_id;
583            }
584            if map_internal_to_external
585                && attr.st_gid >= internal_id
586                && attr.st_gid < internal_id + range
587            {
588                attr.st_gid += external_id - internal_id;
589            }
590            if !map_internal_to_external
591                && attr.st_uid >= external_id
592                && attr.st_uid < external_id + range
593            {
594                attr.st_uid += internal_id - external_id;
595            }
596            if !map_internal_to_external
597                && attr.st_gid >= external_id
598                && attr.st_gid < external_id + range
599            {
600                attr.st_gid += internal_id - external_id;
601            }
602        }
603    }
604
605    fn allocate_fs_idx(&self) -> Result<VfsIndex> {
606        let superblocks = self.superblocks.load().deref().deref().clone();
607        let start = self.next_super.load(Ordering::SeqCst);
608        let mut found = false;
609
610        loop {
611            let index = self.next_super.fetch_add(1, Ordering::Relaxed);
612            if index == start {
613                if found {
614                    // There's no available file system index
615                    break;
616                } else {
617                    found = true;
618                }
619            }
620
621            if index == VFS_PSEUDO_FS_IDX {
622                // Skip the pseudo fs index
623                continue;
624            }
625            if (index as usize) < superblocks.len() && superblocks[index as usize].is_some() {
626                // Skip if it's allocated
627                continue;
628            } else {
629                return Ok(index);
630            }
631        }
632
633        Err(Error::new(
634            ErrorKind::Other,
635            "vfs maximum mountpoints reached",
636        ))
637    }
638
639    fn get_fs_by_idx(&self, fs_idx: VfsIndex) -> Result<Arc<BackFileSystem>> {
640        let superblocks = self.superblocks.load();
641
642        if let Some(fs) = &superblocks[fs_idx as usize] {
643            return Ok(fs.clone());
644        }
645
646        Err(Error::from_raw_os_error(libc::ENOENT))
647    }
648
649    fn get_real_rootfs(&self, inode: VfsInode) -> Result<(VfsEitherFs<'_>, VfsInode)> {
650        if inode.is_pseudo_fs() {
651            // ROOT_ID is special, we need to check if we have a mountpoint on the vfs root
652            if inode.ino() == ROOT_ID {
653                if let Some(mnt) = self.mountpoints.load().get(&inode.ino()).map(Arc::clone) {
654                    let fs = self.get_fs_by_idx(mnt.fs_idx)?;
655                    return Ok((Right(fs), VfsInode::new(mnt.fs_idx, mnt.ino)));
656                }
657            }
658            Ok((Left(&self.root), inode))
659        } else {
660            let fs = self.get_fs_by_idx(inode.fs_idx())?;
661            Ok((Right(fs), inode))
662        }
663    }
664
665    fn lookup_pseudo(
666        &self,
667        fs: &PseudoFs,
668        idata: VfsInode,
669        ctx: &Context,
670        name: &CStr,
671    ) -> Result<Entry> {
672        trace!("lookup pseudo ino {} name {:?}", idata.ino(), name);
673        let mut entry = fs.lookup(ctx, idata.ino(), name)?;
674
675        match self.mountpoints.load().get(&entry.inode) {
676            Some(mnt) => {
677                // cross mountpoint, return mount root entry
678                entry = mnt.root_entry;
679                self.convert_entry(mnt.fs_idx, mnt.ino, &mut entry)?;
680                trace!(
681                    "vfs lookup cross mountpoint, return new mount fs_idx {} inode 0x{:x} fuse inode 0x{:x}, attr inode 0x{:x}",
682                    mnt.fs_idx,
683                    mnt.ino,
684                    entry.inode,
685                    entry.attr.st_ino,
686                );
687                Ok(entry)
688            }
689            None => self.convert_entry(idata.fs_idx(), entry.inode, &mut entry),
690        }
691    }
692}
693
694/// Sava and restore Vfs state.
695#[cfg(feature = "persist")]
696pub mod persist {
697    use std::{
698        ops::Deref,
699        sync::{atomic::Ordering, Arc},
700    };
701
702    use dbs_snapshot::Snapshot;
703    use versionize::{VersionMap, Versionize, VersionizeResult};
704    use versionize_derive::Versionize;
705
706    use crate::api::{
707        filesystem::FsOptions,
708        pseudo_fs::persist::PseudoFsState,
709        vfs::{VfsError, VfsResult},
710        Vfs, VfsOptions,
711    };
712
713    /// VfsState stores the state of the VFS.
714    #[derive(Versionize, Debug)]
715    struct VfsState {
716        /// Vfs options
717        options: VfsOptionsState,
718        /// Vfs root
719        root: Vec<u8>,
720        /// next super block index
721        next_super: u8,
722    }
723
724    #[derive(Versionize, Debug, Default)]
725    struct VfsOptionsState {
726        in_opts: u64,
727        out_opts: u64,
728        no_readdir: bool,
729        seal_size: bool,
730        id_mapping_internal: u32,
731        id_mapping_external: u32,
732        id_mapping_range: u32,
733
734        #[cfg(target_os = "linux")]
735        no_open: bool,
736        #[cfg(target_os = "linux")]
737        no_opendir: bool,
738        #[cfg(target_os = "linux")]
739        no_writeback: bool,
740        #[cfg(target_os = "linux")]
741        killpriv_v2: bool,
742    }
743
744    impl VfsOptions {
745        fn save(&self) -> VfsOptionsState {
746            VfsOptionsState {
747                in_opts: self.in_opts.bits(),
748                out_opts: self.out_opts.bits(),
749                no_readdir: self.no_readdir,
750                seal_size: self.seal_size,
751                id_mapping_internal: self.id_mapping.0,
752                id_mapping_external: self.id_mapping.1,
753                id_mapping_range: self.id_mapping.2,
754
755                #[cfg(target_os = "linux")]
756                no_open: self.no_open,
757                #[cfg(target_os = "linux")]
758                no_opendir: self.no_opendir,
759                #[cfg(target_os = "linux")]
760                no_writeback: self.no_writeback,
761                #[cfg(target_os = "linux")]
762                killpriv_v2: self.killpriv_v2,
763            }
764        }
765
766        fn restore(state: &VfsOptionsState) -> VfsResult<VfsOptions> {
767            Ok(VfsOptions {
768                in_opts: FsOptions::from_bits(state.in_opts).ok_or(VfsError::Persist(
769                    "Failed to restore VfsOptions.in_opts".to_owned(),
770                ))?,
771                out_opts: FsOptions::from_bits(state.out_opts).ok_or(VfsError::Persist(
772                    "Failed to restore VfsOptions.out_opts".to_owned(),
773                ))?,
774                no_readdir: state.no_readdir,
775                seal_size: state.seal_size,
776                id_mapping: (
777                    state.id_mapping_internal,
778                    state.id_mapping_external,
779                    state.id_mapping_range,
780                ),
781
782                #[cfg(target_os = "linux")]
783                no_open: state.no_open,
784                #[cfg(target_os = "linux")]
785                no_opendir: state.no_opendir,
786                #[cfg(target_os = "linux")]
787                no_writeback: state.no_writeback,
788                #[cfg(target_os = "linux")]
789                killpriv_v2: state.killpriv_v2,
790            })
791        }
792    }
793
794    impl Vfs {
795        fn get_version_map() -> versionize::VersionMap {
796            let mut version_map = VersionMap::new();
797            version_map
798                .set_type_version(VfsState::type_id(), 1)
799                .set_type_version(PseudoFsState::type_id(), 1)
800                .set_type_version(VfsOptionsState::type_id(), 1);
801
802            // more versions for the future
803
804            version_map
805        }
806
807        /// Saves part of the Vfs metadata into a byte array.
808        /// The upper layer caller can use this method to save
809        /// and transfer metadata for the reloading in the future.
810        ///
811        /// Note! This function does not save the information
812        /// of the Backend FileSystem mounted by VFS,
813        /// which means that when the caller restores VFS,
814        /// in addition to restoring the information in the byte array
815        /// returned by this function,
816        /// it also needs to manually remount each Backend FileSystem
817        /// according to the Index obtained from the previous mount,
818        /// the method `restore_mount` may be help to do this.
819        ///
820        /// # Example
821        ///
822        /// The following example shows how the function is used in conjunction with
823        /// `restore_from_bytes` to implement the serialization and deserialization of VFS.
824        ///
825        /// ```
826        /// use fuse_backend_rs::api::{Vfs, VfsIndex, VfsOptions};
827        /// use fuse_backend_rs::passthrough::{Config, PassthroughFs};
828        ///
829        /// let new_backend_fs = || {
830        ///     let fs_cfg = Config::default();
831        ///     let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
832        ///     fs.import().unwrap();
833        ///     Box::new(fs)
834        /// };
835        ///
836        /// // create new vfs
837        /// let vfs = &Vfs::new(VfsOptions::default());
838        /// let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
839        /// // record the backend fs and their VfsIndexes
840        /// let backend_fs_list: Vec<(&str, VfsIndex)> = paths
841        ///     .iter()
842        ///     .map(|path| {
843        ///         let fs = new_backend_fs();
844        ///         let idx = vfs.mount(fs, path).unwrap();
845        ///
846        ///         (path.to_owned(), idx)
847        ///     })
848        ///     .collect();
849        ///
850        /// // save the vfs state
851        /// let mut buf = vfs.save_to_bytes().unwrap();
852        ///
853        /// // restore the vfs state
854        /// let restored_vfs = &Vfs::new(VfsOptions::default());
855        /// restored_vfs.restore_from_bytes(&mut buf).unwrap();
856        ///
857        /// // mount the backend fs
858        /// backend_fs_list.into_iter().for_each(|(path, idx)| {
859        ///     let fs = new_backend_fs();
860        ///     vfs.restore_mount(fs, idx, path).unwrap();
861        /// });
862        /// ```
863        pub fn save_to_bytes(&self) -> VfsResult<Vec<u8>> {
864            let root_state = self
865                .root
866                .save_to_bytes()
867                .map_err(|e| VfsError::Persist(format!("Failed to save Vfs root: {:?}", e)))?;
868            let vfs_state = VfsState {
869                options: self.opts.load().deref().deref().save(),
870                root: root_state,
871                next_super: self.next_super.load(Ordering::SeqCst),
872            };
873
874            let vm = Vfs::get_version_map();
875            let target_version = vm.latest_version();
876            let mut s = Snapshot::new(vm, target_version);
877            let mut buf = Vec::new();
878            s.save(&mut buf, &vfs_state).map_err(|e| {
879                VfsError::Persist(format!("Failed to save Vfs using snapshot: {:?}", e))
880            })?;
881
882            Ok(buf)
883        }
884
885        /// Restores part of the Vfs metadata from a byte array.
886        /// For more information, see the example of `save_to_bytes`.
887        pub fn restore_from_bytes(&self, buf: &mut Vec<u8>) -> VfsResult<()> {
888            let mut state: VfsState =
889                Snapshot::load(&mut buf.as_slice(), buf.len(), Vfs::get_version_map())
890                    .map_err(|e| {
891                        VfsError::Persist(format!("Failed to load Vfs using snapshot: {:?}", e))
892                    })?
893                    .0;
894            let opts = VfsOptions::restore(&state.options)?;
895            self.initialized
896                .store(!opts.in_opts.is_empty(), Ordering::Release);
897            self.opts.store(Arc::new(opts));
898
899            self.next_super.store(state.next_super, Ordering::SeqCst);
900            self.root
901                .restore_from_bytes(&mut state.root)
902                .map_err(|e| VfsError::Persist(format!("Failed to restore Vfs root: {:?}", e)))?;
903
904            Ok(())
905        }
906    }
907
908    mod test {
909
910        // This test is to make sure that VfsState can be serialized and deserialized
911        #[test]
912        fn test_vfs_save_restore_simple() {
913            use crate::api::{Vfs, VfsOptions};
914
915            // create new vfs
916            let vfs = &Vfs::new(VfsOptions::default());
917
918            // save the vfs state using Snapshot
919            let mut buf = vfs.save_to_bytes().unwrap();
920
921            // restore the vfs state
922            let vfs = &Vfs::new(VfsOptions::default());
923            vfs.restore_from_bytes(&mut buf).unwrap();
924            assert_eq!(vfs.next_super.load(std::sync::atomic::Ordering::SeqCst), 1);
925        }
926
927        #[test]
928        fn test_vfs_save_restore_with_backend_fs() {
929            use crate::api::{Vfs, VfsIndex, VfsOptions};
930            use crate::passthrough::{Config, PassthroughFs};
931
932            let new_backend_fs = || {
933                let fs_cfg = Config::default();
934                let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
935                fs.import().unwrap();
936                Box::new(fs)
937            };
938
939            // create new vfs
940            let vfs = &Vfs::new(VfsOptions::default());
941            let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
942            let backend_fs_list: Vec<(&str, VfsIndex)> = paths
943                .iter()
944                .map(|path| {
945                    let fs = new_backend_fs();
946                    let idx = vfs.mount(fs, path).unwrap();
947
948                    (path.to_owned(), idx)
949                })
950                .collect();
951
952            // save the vfs state using Snapshot
953            let mut buf = vfs.save_to_bytes().unwrap();
954
955            // restore the vfs state
956            let restored_vfs = &Vfs::new(VfsOptions::default());
957            restored_vfs.restore_from_bytes(&mut buf).unwrap();
958            // restore the backend fs
959            backend_fs_list.into_iter().for_each(|(path, idx)| {
960                let fs = new_backend_fs();
961                vfs.restore_mount(fs, idx, path).unwrap();
962            });
963
964            // check the vfs and restored_vfs
965            assert_eq!(
966                vfs.next_super.load(std::sync::atomic::Ordering::SeqCst),
967                restored_vfs
968                    .next_super
969                    .load(std::sync::atomic::Ordering::SeqCst)
970            );
971            assert_eq!(
972                vfs.initialized.load(std::sync::atomic::Ordering::SeqCst),
973                restored_vfs
974                    .initialized
975                    .load(std::sync::atomic::Ordering::SeqCst)
976            );
977            for path in paths.iter() {
978                let inode = vfs.root.path_walk(path).unwrap();
979                let restored_inode = restored_vfs.root.path_walk(path).unwrap();
980                assert_eq!(inode, restored_inode);
981            }
982        }
983
984        #[test]
985        fn test_vfs_save_restore_with_backend_fs_with_initialized() {
986            use crate::api::filesystem::{FileSystem, FsOptions};
987            use crate::api::{Vfs, VfsIndex, VfsOptions};
988            use crate::passthrough::{Config, PassthroughFs};
989            use std::sync::atomic::Ordering;
990
991            let new_backend_fs = || {
992                let fs_cfg = Config::default();
993                let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
994                fs.import().unwrap();
995                Box::new(fs)
996            };
997
998            // create new vfs
999            let vfs = &Vfs::new(VfsOptions::default());
1000            let paths = vec!["/a", "/a/b", "/a/b/c", "/b", "/b/a/c", "/d"];
1001            let backend_fs_list: Vec<(&str, VfsIndex)> = paths
1002                .iter()
1003                .map(|path| {
1004                    let fs = new_backend_fs();
1005                    let idx = vfs.mount(fs, path).unwrap();
1006
1007                    (path.to_owned(), idx)
1008                })
1009                .collect();
1010            vfs.init(FsOptions::ASYNC_READ).unwrap();
1011            assert!(vfs.initialized.load(Ordering::Acquire));
1012
1013            // save the vfs state using Snapshot
1014            let mut buf = vfs.save_to_bytes().unwrap();
1015
1016            // restore the vfs state
1017            let restored_vfs = &Vfs::new(VfsOptions::default());
1018            restored_vfs.restore_from_bytes(&mut buf).unwrap();
1019
1020            // restore the backend fs
1021            backend_fs_list.into_iter().for_each(|(path, idx)| {
1022                let fs = new_backend_fs();
1023                vfs.restore_mount(fs, idx, path).unwrap();
1024            });
1025
1026            // check the vfs and restored_vfs
1027            assert_eq!(
1028                vfs.next_super.load(std::sync::atomic::Ordering::SeqCst),
1029                restored_vfs
1030                    .next_super
1031                    .load(std::sync::atomic::Ordering::SeqCst)
1032            );
1033            assert_eq!(
1034                vfs.initialized.load(std::sync::atomic::Ordering::Acquire),
1035                restored_vfs
1036                    .initialized
1037                    .load(std::sync::atomic::Ordering::Acquire)
1038            );
1039            for path in paths.iter() {
1040                let inode = vfs.root.path_walk(path).unwrap();
1041                let restored_inode = restored_vfs.root.path_walk(path).unwrap();
1042                assert_eq!(inode, restored_inode);
1043            }
1044        }
1045    }
1046}
1047
1048#[cfg(test)]
1049mod tests {
1050    use super::*;
1051    use crate::api::Vfs;
1052    use std::ffi::CString;
1053    use std::io::{Error, ErrorKind};
1054
1055    pub(crate) struct FakeFileSystemOne {}
1056    impl FileSystem for FakeFileSystemOne {
1057        type Inode = u64;
1058        type Handle = u64;
1059        fn lookup(&self, _: &Context, _: Self::Inode, _: &CStr) -> Result<Entry> {
1060            Ok(Entry::default())
1061        }
1062        fn getattr(
1063            &self,
1064            _ctx: &Context,
1065            _inode: Self::Inode,
1066            _handle: Option<Self::Handle>,
1067        ) -> Result<(stat64, Duration)> {
1068            let mut attr = Attr {
1069                ..Default::default()
1070            };
1071            attr.ino = 1;
1072            Ok((attr.into(), Duration::from_secs(1)))
1073        }
1074    }
1075
1076    pub(crate) struct FakeFileSystemTwo {}
1077    impl FileSystem for FakeFileSystemTwo {
1078        type Inode = u64;
1079        type Handle = u64;
1080        fn lookup(&self, _: &Context, _: Self::Inode, _: &CStr) -> Result<Entry> {
1081            Ok(Entry {
1082                inode: 1,
1083                ..Default::default()
1084            })
1085        }
1086    }
1087
1088    #[test]
1089    fn test_is_safe_path_component() {
1090        let name = CStr::from_bytes_with_nul(b"normal\0").unwrap();
1091        assert!(is_safe_path_component(name), "\"{:?}\"", name);
1092
1093        let name = CStr::from_bytes_with_nul(b".a\0").unwrap();
1094        assert!(is_safe_path_component(name));
1095
1096        let name = CStr::from_bytes_with_nul(b"a.a\0").unwrap();
1097        assert!(is_safe_path_component(name));
1098
1099        let name = CStr::from_bytes_with_nul(b"a.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"/a\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"..\0").unwrap();
1112        assert!(!is_safe_path_component(name));
1113
1114        let name = CStr::from_bytes_with_nul(b"../.\0").unwrap();
1115        assert!(!is_safe_path_component(name));
1116
1117        let name = CStr::from_bytes_with_nul(b"a/b\0").unwrap();
1118        assert!(!is_safe_path_component(name));
1119
1120        let name = CStr::from_bytes_with_nul(b"./../a\0").unwrap();
1121        assert!(!is_safe_path_component(name));
1122    }
1123
1124    #[test]
1125    fn test_is_dot_or_dotdot() {
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"...\0").unwrap();
1133        assert!(!is_dot_or_dotdot(name));
1134
1135        let name = CStr::from_bytes_with_nul(b"./.\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"aa\0").unwrap();
1142        assert!(!is_dot_or_dotdot(name));
1143
1144        let name = CStr::from_bytes_with_nul(b"/a\0").unwrap();
1145        assert!(!is_dot_or_dotdot(name));
1146
1147        let name = CStr::from_bytes_with_nul(b"a/\0").unwrap();
1148        assert!(!is_dot_or_dotdot(name));
1149    }
1150
1151    #[cfg(feature = "async-io")]
1152    mod async_io {
1153        use super::*;
1154        use crate::abi::fuse_abi::{OpenOptions, SetattrValid};
1155        use async_trait::async_trait;
1156
1157        #[allow(unused_variables)]
1158        #[async_trait]
1159        impl AsyncFileSystem for FakeFileSystemOne {
1160            async fn async_lookup(
1161                &self,
1162                ctx: &Context,
1163                parent: <Self as FileSystem>::Inode,
1164                name: &CStr,
1165            ) -> Result<Entry> {
1166                Ok(Entry::default())
1167            }
1168
1169            async fn async_getattr(
1170                &self,
1171                ctx: &Context,
1172                inode: <Self as FileSystem>::Inode,
1173                handle: Option<<Self as FileSystem>::Handle>,
1174            ) -> Result<(libc::stat64, Duration)> {
1175                unimplemented!()
1176            }
1177
1178            async fn async_setattr(
1179                &self,
1180                ctx: &Context,
1181                inode: <Self as FileSystem>::Inode,
1182                attr: libc::stat64,
1183                handle: Option<<Self as FileSystem>::Handle>,
1184                valid: SetattrValid,
1185            ) -> Result<(libc::stat64, Duration)> {
1186                unimplemented!()
1187            }
1188
1189            async fn async_open(
1190                &self,
1191                ctx: &Context,
1192                inode: <Self as FileSystem>::Inode,
1193                flags: u32,
1194                fuse_flags: u32,
1195            ) -> Result<(Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1196                unimplemented!()
1197            }
1198
1199            async fn async_create(
1200                &self,
1201                ctx: &Context,
1202                parent: <Self as FileSystem>::Inode,
1203                name: &CStr,
1204                args: CreateIn,
1205            ) -> Result<(Entry, Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1206                unimplemented!()
1207            }
1208
1209            async fn async_read(
1210                &self,
1211                ctx: &Context,
1212                inode: <Self as FileSystem>::Inode,
1213                handle: <Self as FileSystem>::Handle,
1214                w: &mut (dyn AsyncZeroCopyWriter + Send),
1215                size: u32,
1216                offset: u64,
1217                lock_owner: Option<u64>,
1218                flags: u32,
1219            ) -> Result<usize> {
1220                unimplemented!()
1221            }
1222
1223            async fn async_write(
1224                &self,
1225                ctx: &Context,
1226                inode: <Self as FileSystem>::Inode,
1227                handle: <Self as FileSystem>::Handle,
1228                r: &mut (dyn AsyncZeroCopyReader + Send),
1229                size: u32,
1230                offset: u64,
1231                lock_owner: Option<u64>,
1232                delayed_write: bool,
1233                flags: u32,
1234                fuse_flags: u32,
1235            ) -> Result<usize> {
1236                unimplemented!()
1237            }
1238
1239            async fn async_fsync(
1240                &self,
1241                ctx: &Context,
1242                inode: <Self as FileSystem>::Inode,
1243                datasync: bool,
1244                handle: <Self as FileSystem>::Handle,
1245            ) -> Result<()> {
1246                unimplemented!()
1247            }
1248
1249            async fn async_fallocate(
1250                &self,
1251                ctx: &Context,
1252                inode: <Self as FileSystem>::Inode,
1253                handle: <Self as FileSystem>::Handle,
1254                mode: u32,
1255                offset: u64,
1256                length: u64,
1257            ) -> Result<()> {
1258                unimplemented!()
1259            }
1260
1261            async fn async_fsyncdir(
1262                &self,
1263                ctx: &Context,
1264                inode: <Self as FileSystem>::Inode,
1265                datasync: bool,
1266                handle: <Self as FileSystem>::Handle,
1267            ) -> Result<()> {
1268                unimplemented!()
1269            }
1270        }
1271
1272        impl BackendFileSystem for FakeFileSystemOne {
1273            fn mount(&self) -> Result<(Entry, u64)> {
1274                Ok((
1275                    Entry {
1276                        inode: 1,
1277                        ..Default::default()
1278                    },
1279                    0,
1280                ))
1281            }
1282
1283            fn as_any(&self) -> &dyn Any {
1284                self
1285            }
1286        }
1287
1288        #[allow(unused_variables)]
1289        #[async_trait]
1290        impl AsyncFileSystem for FakeFileSystemTwo {
1291            async fn async_lookup(
1292                &self,
1293                ctx: &Context,
1294                parent: <Self as FileSystem>::Inode,
1295                name: &CStr,
1296            ) -> Result<Entry> {
1297                Err(std::io::Error::from_raw_os_error(libc::EINVAL))
1298            }
1299
1300            async fn async_getattr(
1301                &self,
1302                ctx: &Context,
1303                inode: <Self as FileSystem>::Inode,
1304                handle: Option<<Self as FileSystem>::Handle>,
1305            ) -> Result<(libc::stat64, Duration)> {
1306                unimplemented!()
1307            }
1308
1309            async fn async_setattr(
1310                &self,
1311                ctx: &Context,
1312                inode: <Self as FileSystem>::Inode,
1313                attr: libc::stat64,
1314                handle: Option<<Self as FileSystem>::Handle>,
1315                valid: SetattrValid,
1316            ) -> Result<(libc::stat64, Duration)> {
1317                unimplemented!()
1318            }
1319
1320            async fn async_open(
1321                &self,
1322                ctx: &Context,
1323                inode: <Self as FileSystem>::Inode,
1324                flags: u32,
1325                fuse_flags: u32,
1326            ) -> Result<(Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1327                unimplemented!()
1328            }
1329
1330            async fn async_create(
1331                &self,
1332                ctx: &Context,
1333                parent: <Self as FileSystem>::Inode,
1334                name: &CStr,
1335                args: CreateIn,
1336            ) -> Result<(Entry, Option<<Self as FileSystem>::Handle>, OpenOptions)> {
1337                unimplemented!()
1338            }
1339
1340            async fn async_read(
1341                &self,
1342                ctx: &Context,
1343                inode: <Self as FileSystem>::Inode,
1344                handle: <Self as FileSystem>::Handle,
1345                w: &mut (dyn AsyncZeroCopyWriter + Send),
1346                size: u32,
1347                offset: u64,
1348                lock_owner: Option<u64>,
1349                flags: u32,
1350            ) -> Result<usize> {
1351                unimplemented!()
1352            }
1353
1354            async fn async_write(
1355                &self,
1356                ctx: &Context,
1357                inode: <Self as FileSystem>::Inode,
1358                handle: <Self as FileSystem>::Handle,
1359                r: &mut (dyn AsyncZeroCopyReader + Send),
1360                size: u32,
1361                offset: u64,
1362                lock_owner: Option<u64>,
1363                delayed_write: bool,
1364                flags: u32,
1365                fuse_flags: u32,
1366            ) -> Result<usize> {
1367                unimplemented!()
1368            }
1369
1370            async fn async_fsync(
1371                &self,
1372                ctx: &Context,
1373                inode: <Self as FileSystem>::Inode,
1374                datasync: bool,
1375                handle: <Self as FileSystem>::Handle,
1376            ) -> Result<()> {
1377                unimplemented!()
1378            }
1379
1380            async fn async_fallocate(
1381                &self,
1382                ctx: &Context,
1383                inode: <Self as FileSystem>::Inode,
1384                handle: <Self as FileSystem>::Handle,
1385                mode: u32,
1386                offset: u64,
1387                length: u64,
1388            ) -> Result<()> {
1389                unimplemented!()
1390            }
1391
1392            async fn async_fsyncdir(
1393                &self,
1394                ctx: &Context,
1395                inode: <Self as FileSystem>::Inode,
1396                datasync: bool,
1397                handle: <Self as FileSystem>::Handle,
1398            ) -> Result<()> {
1399                unimplemented!()
1400            }
1401        }
1402
1403        impl BackendFileSystem for FakeFileSystemTwo {
1404            fn mount(&self) -> Result<(Entry, u64)> {
1405                Ok((
1406                    Entry {
1407                        inode: 1,
1408                        ..Default::default()
1409                    },
1410                    0,
1411                ))
1412            }
1413            fn as_any(&self) -> &dyn Any {
1414                self
1415            }
1416        }
1417    }
1418
1419    #[cfg(not(feature = "async-io"))]
1420    impl BackendFileSystem for FakeFileSystemOne {
1421        fn mount(&self) -> Result<(Entry, u64)> {
1422            Ok((
1423                Entry {
1424                    inode: 1,
1425                    ..Default::default()
1426                },
1427                0,
1428            ))
1429        }
1430
1431        fn as_any(&self) -> &dyn Any {
1432            self
1433        }
1434    }
1435
1436    #[cfg(not(feature = "async-io"))]
1437    impl BackendFileSystem for FakeFileSystemTwo {
1438        fn mount(&self) -> Result<(Entry, u64)> {
1439            Ok((
1440                Entry {
1441                    inode: 1,
1442                    ..Default::default()
1443                },
1444                0,
1445            ))
1446        }
1447        fn as_any(&self) -> &dyn Any {
1448            self
1449        }
1450    }
1451
1452    #[test]
1453    fn test_vfs_init() {
1454        let vfs = Vfs::default();
1455        assert_eq!(vfs.initialized(), false);
1456
1457        let opts = vfs.opts.load();
1458        let out_opts = opts.out_opts;
1459
1460        #[cfg(target_os = "linux")]
1461        {
1462            assert_eq!(opts.no_open, true);
1463            assert_eq!(opts.no_opendir, true);
1464            assert_eq!(opts.no_writeback, false);
1465            assert_eq!(opts.killpriv_v2, false);
1466        }
1467        assert_eq!(opts.no_readdir, false);
1468        assert_eq!(opts.seal_size, false);
1469        assert_eq!(opts.in_opts.is_empty(), true);
1470
1471        vfs.init(FsOptions::ASYNC_READ).unwrap();
1472        assert_eq!(vfs.initialized(), true);
1473
1474        let opts = vfs.opts.load();
1475        #[cfg(target_os = "linux")]
1476        {
1477            assert_eq!(opts.no_open, false);
1478            assert_eq!(opts.no_opendir, false);
1479            assert_eq!(opts.no_writeback, false);
1480            assert_eq!(opts.killpriv_v2, false);
1481        }
1482        assert_eq!(opts.no_readdir, false);
1483        assert_eq!(opts.seal_size, false);
1484
1485        vfs.destroy();
1486        assert_eq!(vfs.initialized(), false);
1487
1488        let vfs = Vfs::default();
1489        #[cfg(target_os = "linux")]
1490        let in_opts =
1491            FsOptions::ASYNC_READ | FsOptions::ZERO_MESSAGE_OPEN | FsOptions::ZERO_MESSAGE_OPENDIR;
1492        #[cfg(target_os = "macos")]
1493        let in_opts = FsOptions::ASYNC_READ;
1494        vfs.init(in_opts).unwrap();
1495        let opts = vfs.opts.load();
1496        #[cfg(target_os = "linux")]
1497        {
1498            assert_eq!(opts.no_open, true);
1499            assert_eq!(opts.no_opendir, true);
1500            assert_eq!(opts.no_writeback, false);
1501            assert_eq!(opts.killpriv_v2, false);
1502        }
1503        assert_eq!(opts.out_opts, out_opts & in_opts);
1504    }
1505
1506    #[test]
1507    fn test_vfs_lookup() {
1508        let vfs = Vfs::new(VfsOptions::default());
1509        let fs = FakeFileSystemOne {};
1510        let ctx = Context::new();
1511
1512        assert!(vfs.mount(Box::new(fs), "/x/y").is_ok());
1513
1514        // Lookup inode on pseudo file system.
1515        let entry1 = vfs
1516            .lookup(&ctx, ROOT_ID.into(), CString::new("x").unwrap().as_c_str())
1517            .unwrap();
1518        assert_eq!(entry1.inode, 0x2);
1519
1520        // Lookup inode on mounted file system.
1521        let entry2 = vfs
1522            .lookup(
1523                &ctx,
1524                entry1.inode.into(),
1525                CString::new("y").unwrap().as_c_str(),
1526            )
1527            .unwrap();
1528        assert_eq!(entry2.inode, 0x100_0000_0000_0001);
1529
1530        // lookup for negative result.
1531        let entry3 = vfs
1532            .lookup(
1533                &ctx,
1534                entry2.inode.into(),
1535                CString::new("z").unwrap().as_c_str(),
1536            )
1537            .unwrap();
1538        assert_eq!(entry3.inode, 0);
1539
1540        let (stat, _) = vfs
1541            .getattr(&ctx, VfsInode(0x100_0000_0000_0001), None)
1542            .unwrap();
1543        assert_eq!(stat.st_ino, 0x100_0000_0000_0001);
1544    }
1545
1546    #[test]
1547    fn test_mount_different_fs_types() {
1548        let vfs = Vfs::new(VfsOptions::default());
1549        let fs1 = FakeFileSystemOne {};
1550        let fs2 = FakeFileSystemTwo {};
1551        assert!(vfs.mount(Box::new(fs1), "/foo").is_ok());
1552        assert!(vfs.mount(Box::new(fs2), "/bar").is_ok());
1553
1554        // Lookup inode on pseudo file system.
1555        let ctx = Context::new();
1556        let entry1 = vfs
1557            .lookup(
1558                &ctx,
1559                ROOT_ID.into(),
1560                CString::new("bar").unwrap().as_c_str(),
1561            )
1562            .unwrap();
1563        assert_eq!(entry1.inode, 0x200_0000_0000_0001);
1564        assert_eq!(entry1.attr.st_ino, 0x200_0000_0000_0001);
1565    }
1566
1567    #[test]
1568    fn test_umount() {
1569        let vfs = Vfs::new(VfsOptions::default());
1570        let fs1 = FakeFileSystemOne {};
1571        let fs2 = FakeFileSystemOne {};
1572        assert!(vfs.mount(Box::new(fs1), "/foo").is_ok());
1573        assert!(vfs.umount("/foo").is_ok());
1574
1575        assert!(vfs.mount(Box::new(fs2), "/x/y").is_ok());
1576
1577        match vfs.umount("/x") {
1578            Err(VfsError::NotFound(_e)) => {}
1579            _ => panic!("expect VfsError::NotFound(/x)"),
1580        }
1581    }
1582
1583    #[test]
1584    fn test_umount_overlap() {
1585        let vfs = Vfs::new(VfsOptions::default());
1586        let fs1 = FakeFileSystemOne {};
1587        let fs2 = FakeFileSystemTwo {};
1588
1589        assert!(vfs.mount(Box::new(fs1), "/x/y/z").is_ok());
1590        assert!(vfs.mount(Box::new(fs2), "/x/y").is_ok());
1591
1592        let m1 = vfs.get_rootfs("/x/y/z").unwrap().unwrap();
1593        assert!(m1.as_any().is::<FakeFileSystemOne>());
1594        let m2 = vfs.get_rootfs("/x/y").unwrap().unwrap();
1595        assert!(m2.as_any().is::<FakeFileSystemTwo>());
1596
1597        assert!(vfs.umount("/x/y/z").is_ok());
1598        assert!(vfs.umount("/x/y").is_ok());
1599
1600        match vfs.umount("/x/y/z") {
1601            Err(VfsError::NotFound(_e)) => {}
1602            _ => panic!("expect VfsError::NotFound(/x/y/z)"),
1603        }
1604    }
1605
1606    #[test]
1607    fn test_umount_same() {
1608        let vfs = Vfs::new(VfsOptions::default());
1609        let fs1 = FakeFileSystemOne {};
1610        let fs2 = FakeFileSystemTwo {};
1611
1612        assert!(vfs.mount(Box::new(fs1), "/x/y").is_ok());
1613        assert!(vfs.mount(Box::new(fs2), "/x/y").is_ok());
1614
1615        let m1 = vfs.get_rootfs("/x/y").unwrap().unwrap();
1616        assert!(m1.as_any().is::<FakeFileSystemTwo>());
1617
1618        assert!(vfs.umount("/x/y").is_ok());
1619
1620        match vfs.umount("/x/y") {
1621            Err(VfsError::NotFound(_e)) => {}
1622            _ => panic!("expect VfsError::NotFound(/x/y)"),
1623        }
1624    }
1625
1626    #[test]
1627    #[should_panic]
1628    fn test_invalid_inode() {
1629        let _ = VfsInode::new(1, VFS_MAX_INO + 1);
1630    }
1631
1632    #[test]
1633    fn test_inode() {
1634        let inode = VfsInode::new(2, VFS_MAX_INO);
1635
1636        assert_eq!(inode.fs_idx(), 2);
1637        assert_eq!(inode.ino(), VFS_MAX_INO);
1638        assert!(!inode.is_pseudo_fs());
1639        assert_eq!(u64::from(inode), 0x200_0000_0000_0000u64 + VFS_MAX_INO);
1640    }
1641
1642    #[test]
1643    fn test_allocate_fs_idx() {
1644        let vfs = Vfs::new(VfsOptions::default());
1645        let _guard = vfs.lock.lock().unwrap();
1646
1647        // Test case: allocate all available fs idx
1648        for _ in 0..255 {
1649            let fs = FakeFileSystemOne {};
1650            let index = vfs.allocate_fs_idx().unwrap();
1651            let mut superblocks = vfs.superblocks.load().deref().deref().clone();
1652
1653            superblocks[index as usize] = Some(Arc::new(Box::new(fs)));
1654            vfs.superblocks.store(Arc::new(superblocks));
1655        }
1656
1657        // Test case: fail to allocate more fs idx if all have been allocated
1658        for _ in 0..=256 {
1659            vfs.allocate_fs_idx().unwrap_err();
1660        }
1661    }
1662
1663    #[test]
1664    fn test_fmt_vfs_error() {
1665        assert_eq!(
1666            format!("{}", VfsError::Unsupported),
1667            "Vfs operation not supported".to_string()
1668        );
1669        assert_eq!(
1670            format!(
1671                "{}",
1672                VfsError::Mount(Error::new(ErrorKind::Other, "mount".to_string()),)
1673            ),
1674            "Mount backend filesystem: mount".to_string()
1675        );
1676        assert_eq!(
1677            format!("{}", VfsError::InodeIndex("inode index".to_string())),
1678            "Illegal inode index: inode index".to_string()
1679        );
1680        assert_eq!(
1681            format!(
1682                "{}",
1683                VfsError::FsIndex(Error::new(ErrorKind::Other, "fs index".to_string()),)
1684            ),
1685            "Filesystem index error: fs index".to_string()
1686        );
1687        assert_eq!(
1688            format!(
1689                "{}",
1690                VfsError::PathWalk(Error::new(ErrorKind::Other, "path walk".to_string()),)
1691            ),
1692            "Walking path error: path walk".to_string()
1693        );
1694        assert_eq!(
1695            format!("{}", VfsError::NotFound("not found".to_string())),
1696            "Entry can't be found: not found".to_string()
1697        );
1698        assert_eq!(
1699            format!("{}", VfsError::Initialize("initialize".to_string())),
1700            "File system can't be initialized: initialize".to_string()
1701        );
1702    }
1703}