fuse_backend_rs/passthrough/
mod.rs

1// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Fuse passthrough file system, mirroring an existing FS hierarchy.
5//!
6//! This file system mirrors the existing file system hierarchy of the system, starting at the
7//! root file system. This is implemented by just "passing through" all requests to the
8//! corresponding underlying file system.
9//!
10//! The code is derived from the
11//! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project,
12//! with heavy modification/enhancements from Alibaba Cloud OS team.
13
14use std::any::Any;
15use std::collections::{btree_map, BTreeMap};
16use std::ffi::{CStr, CString, OsString};
17use std::fs::File;
18use std::io;
19use std::marker::PhantomData;
20use std::ops::{Deref, DerefMut};
21use std::os::fd::{AsFd, BorrowedFd};
22use std::os::unix::ffi::OsStringExt;
23use std::os::unix::io::{AsRawFd, RawFd};
24use std::path::PathBuf;
25use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
26use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard};
27use std::time::Duration;
28
29use vm_memory::{bitmap::BitmapSlice, ByteValued};
30
31pub use self::config::{CachePolicy, Config};
32use self::file_handle::{FileHandle, OpenableFileHandle};
33use self::inode_store::{InodeId, InodeStore};
34use self::mount_fd::MountFds;
35use self::statx::{statx, StatExt};
36use self::util::{
37    ebadf, einval, enosys, eperm, is_dir, is_safe_inode, openat, reopen_fd_through_proc, stat_fd,
38    UniqueInodeGenerator,
39};
40use crate::abi::fuse_abi as fuse;
41use crate::abi::fuse_abi::Opcode;
42use crate::api::filesystem::Entry;
43use crate::api::{
44    validate_path_component, BackendFileSystem, CURRENT_DIR_CSTR, EMPTY_CSTR, PARENT_DIR_CSTR,
45    PROC_SELF_FD_CSTR, SLASH_ASCII, VFS_MAX_INO,
46};
47
48#[cfg(feature = "async-io")]
49mod async_io;
50mod config;
51mod file_handle;
52mod inode_store;
53mod mount_fd;
54mod os_compat;
55mod overlay;
56mod statx;
57mod sync_io;
58mod util;
59
60type Inode = u64;
61type Handle = u64;
62
63/// Maximum host inode number supported by passthroughfs
64const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff;
65
66/**
67 * Represents the file associated with an inode (`InodeData`).
68 *
69 * When obtaining such a file, it may either be a new file (the `Owned` variant), in which case the
70 * object's lifetime is static, or it may reference `InodeData.file` (the `Ref` variant), in which
71 * case the object's lifetime is that of the respective `InodeData` object.
72 */
73#[derive(Debug)]
74enum InodeFile<'a> {
75    Owned(File),
76    Ref(&'a File),
77}
78
79impl AsRawFd for InodeFile<'_> {
80    /// Return a file descriptor for this file
81    /// Note: This fd is only valid as long as the `InodeFile` exists.
82    fn as_raw_fd(&self) -> RawFd {
83        match self {
84            Self::Owned(file) => file.as_raw_fd(),
85            Self::Ref(file_ref) => file_ref.as_raw_fd(),
86        }
87    }
88}
89
90impl AsFd for InodeFile<'_> {
91    fn as_fd(&self) -> BorrowedFd<'_> {
92        match self {
93            Self::Owned(file) => file.as_fd(),
94            Self::Ref(file_ref) => file_ref.as_fd(),
95        }
96    }
97}
98
99#[derive(Debug)]
100enum InodeHandle {
101    File(File),
102    Handle(Arc<OpenableFileHandle>),
103}
104
105impl InodeHandle {
106    fn file_handle(&self) -> Option<&FileHandle> {
107        match self {
108            InodeHandle::File(_) => None,
109            InodeHandle::Handle(h) => Some(h.file_handle().deref()),
110        }
111    }
112
113    fn get_file(&self) -> io::Result<InodeFile<'_>> {
114        match self {
115            InodeHandle::File(f) => Ok(InodeFile::Ref(f)),
116            InodeHandle::Handle(h) => {
117                let f = h.open(libc::O_PATH)?;
118                Ok(InodeFile::Owned(f))
119            }
120        }
121    }
122
123    fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result<File> {
124        match self {
125            InodeHandle::File(f) => reopen_fd_through_proc(f, flags, proc_self_fd),
126            InodeHandle::Handle(h) => h.open(flags),
127        }
128    }
129
130    fn stat(&self) -> io::Result<libc::stat64> {
131        match self {
132            InodeHandle::File(f) => stat_fd(f, None),
133            InodeHandle::Handle(_h) => {
134                let file = self.get_file()?;
135                stat_fd(&file, None)
136            }
137        }
138    }
139}
140
141/// Represents an inode in `PassthroughFs`.
142#[derive(Debug)]
143pub struct InodeData {
144    inode: Inode,
145    // Most of these aren't actually files but ¯\_(ツ)_/¯.
146    handle: InodeHandle,
147    id: InodeId,
148    refcount: AtomicU64,
149    // File type and mode
150    mode: u32,
151}
152
153impl InodeData {
154    fn new(inode: Inode, f: InodeHandle, refcount: u64, id: InodeId, mode: u32) -> Self {
155        InodeData {
156            inode,
157            handle: f,
158            id,
159            refcount: AtomicU64::new(refcount),
160            mode,
161        }
162    }
163
164    fn get_file(&self) -> io::Result<InodeFile<'_>> {
165        self.handle.get_file()
166    }
167
168    fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result<File> {
169        self.handle.open_file(flags, proc_self_fd)
170    }
171}
172
173/// Data structures to manage accessed inodes.
174struct InodeMap {
175    inodes: RwLock<InodeStore>,
176}
177
178impl InodeMap {
179    fn new() -> Self {
180        InodeMap {
181            inodes: RwLock::new(Default::default()),
182        }
183    }
184
185    fn clear(&self) {
186        // Do not expect poisoned lock here, so safe to unwrap().
187        self.inodes.write().unwrap().clear();
188    }
189
190    fn get(&self, inode: Inode) -> io::Result<Arc<InodeData>> {
191        // Do not expect poisoned lock here, so safe to unwrap().
192        self.inodes
193            .read()
194            .unwrap()
195            .get(&inode)
196            .cloned()
197            .ok_or_else(ebadf)
198    }
199
200    fn get_inode_locked(
201        inodes: &InodeStore,
202        id: &InodeId,
203        handle: Option<&FileHandle>,
204    ) -> Option<Inode> {
205        match handle {
206            Some(h) => inodes.inode_by_handle(h).copied(),
207            None => inodes.inode_by_id(id).copied(),
208        }
209    }
210
211    fn get_alt(&self, id: &InodeId, handle: Option<&FileHandle>) -> Option<Arc<InodeData>> {
212        // Do not expect poisoned lock here, so safe to unwrap().
213        let inodes = self.inodes.read().unwrap();
214
215        Self::get_alt_locked(inodes.deref(), id, handle)
216    }
217
218    fn get_alt_locked(
219        inodes: &InodeStore,
220        id: &InodeId,
221        handle: Option<&FileHandle>,
222    ) -> Option<Arc<InodeData>> {
223        handle
224            .and_then(|h| inodes.get_by_handle(h))
225            .or_else(|| {
226                inodes.get_by_id(id).filter(|data| {
227                    // When we have to fall back to looking up an inode by its IDs, ensure that
228                    // we hit an entry that does not have a file handle.  Entries with file
229                    // handles must also have a handle alt key, so if we have not found it by
230                    // that handle alt key, we must have found an entry with a mismatching
231                    // handle; i.e. an entry for a different file, even though it has the same
232                    // inode ID.
233                    // (This can happen when we look up a new file that has reused the inode ID
234                    // of some previously unlinked inode we still have in `.inodes`.)
235                    handle.is_none() || data.handle.file_handle().is_none()
236                })
237            })
238            .cloned()
239    }
240
241    fn get_map_mut(&self) -> RwLockWriteGuard<'_, InodeStore> {
242        // Do not expect poisoned lock here, so safe to unwrap().
243        self.inodes.write().unwrap()
244    }
245
246    fn insert(&self, data: Arc<InodeData>) {
247        let mut inodes = self.get_map_mut();
248
249        Self::insert_locked(inodes.deref_mut(), data)
250    }
251
252    fn insert_locked(inodes: &mut InodeStore, data: Arc<InodeData>) {
253        inodes.insert(data);
254    }
255}
256
257struct HandleData {
258    inode: Inode,
259    file: File,
260    lock: Mutex<()>,
261    open_flags: AtomicU32,
262}
263
264impl HandleData {
265    fn new(inode: Inode, file: File, flags: u32) -> Self {
266        HandleData {
267            inode,
268            file,
269            lock: Mutex::new(()),
270            open_flags: AtomicU32::new(flags),
271        }
272    }
273
274    fn get_file(&self) -> &File {
275        &self.file
276    }
277
278    fn get_file_mut(&self) -> (MutexGuard<'_, ()>, &File) {
279        (self.lock.lock().unwrap(), &self.file)
280    }
281
282    fn borrow_fd(&self) -> BorrowedFd<'_> {
283        self.file.as_fd()
284    }
285
286    fn get_flags(&self) -> u32 {
287        self.open_flags.load(Ordering::Relaxed)
288    }
289
290    fn set_flags(&self, flags: u32) {
291        self.open_flags.store(flags, Ordering::Relaxed);
292    }
293}
294
295struct HandleMap {
296    handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>,
297}
298
299impl HandleMap {
300    fn new() -> Self {
301        HandleMap {
302            handles: RwLock::new(BTreeMap::new()),
303        }
304    }
305
306    fn clear(&self) {
307        // Do not expect poisoned lock here, so safe to unwrap().
308        self.handles.write().unwrap().clear();
309    }
310
311    fn insert(&self, handle: Handle, data: HandleData) {
312        // Do not expect poisoned lock here, so safe to unwrap().
313        self.handles.write().unwrap().insert(handle, Arc::new(data));
314    }
315
316    fn release(&self, handle: Handle, inode: Inode) -> io::Result<()> {
317        // Do not expect poisoned lock here, so safe to unwrap().
318        let mut handles = self.handles.write().unwrap();
319
320        if let btree_map::Entry::Occupied(e) = handles.entry(handle) {
321            if e.get().inode == inode {
322                // We don't need to close the file here because that will happen automatically when
323                // the last `Arc` is dropped.
324                e.remove();
325                return Ok(());
326            }
327        }
328
329        Err(ebadf())
330    }
331
332    fn get(&self, handle: Handle, inode: Inode) -> io::Result<Arc<HandleData>> {
333        // Do not expect poisoned lock here, so safe to unwrap().
334        self.handles
335            .read()
336            .unwrap()
337            .get(&handle)
338            .filter(|hd| hd.inode == inode)
339            .cloned()
340            .ok_or_else(ebadf)
341    }
342}
343
344/// A file system that simply "passes through" all requests it receives to the underlying file
345/// system.
346///
347/// To keep the implementation simple it servers the contents of its root directory. Users
348/// that wish to serve only a specific directory should set up the environment so that that
349/// directory ends up as the root of the file system process. One way to accomplish this is via a
350/// combination of mount namespaces and the pivot_root system call.
351pub struct PassthroughFs<S: BitmapSlice + Send + Sync = ()> {
352    // File descriptors for various points in the file system tree. These fds are always opened with
353    // the `O_PATH` option so they cannot be used for reading or writing any data. See the
354    // documentation of the `O_PATH` flag in `open(2)` for more details on what one can and cannot
355    // do with an fd opened with this flag.
356    inode_map: InodeMap,
357    next_inode: AtomicU64,
358
359    // File descriptors for open files and directories. Unlike the fds in `inodes`, these _can_ be
360    // used for reading and writing data.
361    handle_map: HandleMap,
362    next_handle: AtomicU64,
363
364    // Use to generate unique inode
365    ino_allocator: UniqueInodeGenerator,
366    // Maps mount IDs to an open FD on the respective ID for the purpose of open_by_handle_at().
367    mount_fds: MountFds,
368
369    // File descriptor pointing to the `/proc/self/fd` directory. This is used to convert an fd from
370    // `inodes` into one that can go into `handles`. This is accomplished by reading the
371    // `/proc/self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are meant
372    // to be serving doesn't have access to `/proc/self/fd`.
373    proc_self_fd: File,
374
375    // Whether writeback caching is enabled for this directory. This will only be true when
376    // `cfg.writeback` is true and `init` was called with `FsOptions::WRITEBACK_CACHE`.
377    writeback: AtomicBool,
378
379    // Whether no_open is enabled.
380    no_open: AtomicBool,
381
382    // Whether no_opendir is enabled.
383    no_opendir: AtomicBool,
384
385    // Whether kill_priv_v2 is enabled.
386    killpriv_v2: AtomicBool,
387
388    // Whether no_readdir is enabled.
389    no_readdir: AtomicBool,
390
391    // Whether seal_size is enabled.
392    seal_size: AtomicBool,
393
394    // Whether per-file DAX feature is enabled.
395    // Init from guest kernel Init cmd of fuse fs.
396    perfile_dax: AtomicBool,
397
398    dir_entry_timeout: Duration,
399    dir_attr_timeout: Duration,
400
401    cfg: Config,
402
403    phantom: PhantomData<S>,
404}
405
406impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
407    /// Create a Passthrough file system instance.
408    pub fn new(mut cfg: Config) -> io::Result<PassthroughFs<S>> {
409        if cfg.no_open && cfg.cache_policy != CachePolicy::Always {
410            warn!("passthroughfs: no_open only work with cache=always, reset to open mode");
411            cfg.no_open = false;
412        }
413        if cfg.writeback && cfg.cache_policy == CachePolicy::Never {
414            warn!(
415                "passthroughfs: writeback cache conflicts with cache=none, reset to no_writeback"
416            );
417            cfg.writeback = false;
418        }
419
420        // Safe because this is a constant value and a valid C string.
421        let proc_self_fd_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_SELF_FD_CSTR) };
422        let proc_self_fd = Self::open_file(
423            &libc::AT_FDCWD,
424            proc_self_fd_cstr,
425            libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
426            0,
427        )?;
428
429        let (dir_entry_timeout, dir_attr_timeout) =
430            match (cfg.dir_entry_timeout, cfg.dir_attr_timeout) {
431                (Some(e), Some(a)) => (e, a),
432                (Some(e), None) => (e, cfg.attr_timeout),
433                (None, Some(a)) => (cfg.entry_timeout, a),
434                (None, None) => (cfg.entry_timeout, cfg.attr_timeout),
435            };
436
437        let mount_fds = MountFds::new(None)?;
438
439        Ok(PassthroughFs {
440            inode_map: InodeMap::new(),
441            next_inode: AtomicU64::new(fuse::ROOT_ID + 1),
442            ino_allocator: UniqueInodeGenerator::new(),
443
444            handle_map: HandleMap::new(),
445            next_handle: AtomicU64::new(1),
446
447            mount_fds,
448            proc_self_fd,
449
450            writeback: AtomicBool::new(false),
451            no_open: AtomicBool::new(false),
452            no_opendir: AtomicBool::new(false),
453            killpriv_v2: AtomicBool::new(false),
454            no_readdir: AtomicBool::new(cfg.no_readdir),
455            seal_size: AtomicBool::new(cfg.seal_size),
456            perfile_dax: AtomicBool::new(false),
457            dir_entry_timeout,
458            dir_attr_timeout,
459            cfg,
460
461            phantom: PhantomData,
462        })
463    }
464
465    /// Initialize the Passthrough file system.
466    pub fn import(&self) -> io::Result<()> {
467        let root = CString::new(self.cfg.root_dir.as_str()).expect("CString::new failed");
468
469        let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &libc::AT_FDCWD, &root)
470            .map_err(|e| {
471                error!("fuse: import: failed to get file or handle: {:?}", e);
472                e
473            })?;
474        let id = InodeId::from_stat(&st);
475        let handle = if let Some(h) = handle_opt {
476            InodeHandle::Handle(self.to_openable_handle(h)?)
477        } else {
478            InodeHandle::File(path_fd)
479        };
480
481        // Safe because this doesn't modify any memory and there is no need to check the return
482        // value because this system call always succeeds. We need to clear the umask here because
483        // we want the client to be able to set all the bits in the mode.
484        unsafe { libc::umask(0o000) };
485
486        // Not sure why the root inode gets a refcount of 2 but that's what libfuse does.
487        self.inode_map.insert(Arc::new(InodeData::new(
488            fuse::ROOT_ID,
489            handle,
490            2,
491            id,
492            st.st.st_mode,
493        )));
494
495        Ok(())
496    }
497
498    /// Get the list of file descriptors which should be reserved across live upgrade.
499    pub fn keep_fds(&self) -> Vec<RawFd> {
500        vec![self.proc_self_fd.as_raw_fd()]
501    }
502
503    fn readlinkat(dfd: i32, pathname: &CStr) -> io::Result<PathBuf> {
504        let mut buf = Vec::with_capacity(libc::PATH_MAX as usize);
505
506        // Safe because the kernel will only write data to buf and we check the return value
507        let buf_read = unsafe {
508            libc::readlinkat(
509                dfd,
510                pathname.as_ptr(),
511                buf.as_mut_ptr() as *mut libc::c_char,
512                buf.capacity(),
513            )
514        };
515        if buf_read < 0 {
516            error!("fuse: readlinkat error");
517            return Err(io::Error::last_os_error());
518        }
519
520        // Safe because we trust the value returned by kernel.
521        unsafe { buf.set_len(buf_read as usize) };
522        buf.shrink_to_fit();
523
524        // Be careful:
525        // - readlink() does not append a terminating null byte to buf
526        // - OsString instances are not NUL terminated
527        Ok(PathBuf::from(OsString::from_vec(buf)))
528    }
529
530    /// Get the file pathname corresponding to the Inode
531    /// This function is used by Nydus blobfs
532    pub fn readlinkat_proc_file(&self, inode: Inode) -> io::Result<PathBuf> {
533        let data = self.inode_map.get(inode)?;
534        let file = data.get_file()?;
535        let pathname = CString::new(format!("{}", file.as_raw_fd()))
536            .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
537
538        Self::readlinkat(self.proc_self_fd.as_raw_fd(), &pathname)
539    }
540
541    fn create_file_excl(
542        dir: &impl AsRawFd,
543        pathname: &CStr,
544        flags: i32,
545        mode: u32,
546    ) -> io::Result<Option<File>> {
547        match openat(dir, pathname, flags | libc::O_CREAT | libc::O_EXCL, mode) {
548            Ok(file) => Ok(Some(file)),
549            Err(err) => {
550                // Ignore the error if the file exists and O_EXCL is not present in `flags`.
551                if err.kind() == io::ErrorKind::AlreadyExists {
552                    if (flags & libc::O_EXCL) != 0 {
553                        return Err(err);
554                    }
555                    return Ok(None);
556                }
557                Err(err)
558            }
559        }
560    }
561
562    fn open_file(dfd: &impl AsRawFd, pathname: &CStr, flags: i32, mode: u32) -> io::Result<File> {
563        openat(dfd, pathname, flags, mode)
564    }
565
566    fn open_file_restricted(
567        &self,
568        dir: &impl AsRawFd,
569        pathname: &CStr,
570        flags: i32,
571        mode: u32,
572    ) -> io::Result<File> {
573        let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags;
574
575        // TODO
576        //if self.os_facts.has_openat2 {
577        //    oslib::do_open_relative_to(dir, pathname, flags, mode)
578        //} else {
579        openat(dir, pathname, flags, mode)
580        //}
581    }
582
583    /// Create a File or File Handle for `name` under directory `dir_fd` to support `lookup()`.
584    fn open_file_and_handle(
585        &self,
586        dir: &impl AsRawFd,
587        name: &CStr,
588    ) -> io::Result<(File, Option<FileHandle>, StatExt)> {
589        let path_file = self.open_file_restricted(dir, name, libc::O_PATH, 0)?;
590        let st = statx(&path_file, None)?;
591        let handle = if self.cfg.inode_file_handles {
592            FileHandle::from_fd(&path_file)?
593        } else {
594            None
595        };
596
597        Ok((path_file, handle, st))
598    }
599
600    fn to_openable_handle(&self, fh: FileHandle) -> io::Result<Arc<OpenableFileHandle>> {
601        fh.into_openable(&self.mount_fds, |fd, flags, _mode| {
602            reopen_fd_through_proc(&fd, flags, &self.proc_self_fd)
603        })
604        .map(Arc::new)
605        .map_err(|e| {
606            if !e.silent() {
607                error!("{}", e);
608            }
609            e.into_inner()
610        })
611    }
612
613    fn allocate_inode(
614        &self,
615        inodes: &InodeStore,
616        id: &InodeId,
617        handle_opt: Option<&FileHandle>,
618    ) -> io::Result<Inode> {
619        if !self.cfg.use_host_ino {
620            // If the inode has already been assigned before, the new inode is not reassigned,
621            // ensuring that the same file is always the same inode
622            Ok(InodeMap::get_inode_locked(inodes, id, handle_opt)
623                .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed)))
624        } else {
625            let inode = if id.ino > MAX_HOST_INO {
626                // Prefer looking for previous mappings from memory
627                match InodeMap::get_inode_locked(inodes, id, handle_opt) {
628                    Some(ino) => ino,
629                    None => self.ino_allocator.get_unique_inode(id)?,
630                }
631            } else {
632                self.ino_allocator.get_unique_inode(id)?
633            };
634
635            Ok(inode)
636        }
637    }
638
639    fn do_lookup(&self, parent: Inode, name: &CStr) -> io::Result<Entry> {
640        let name =
641            if parent == fuse::ROOT_ID && name.to_bytes_with_nul().starts_with(PARENT_DIR_CSTR) {
642                // Safe as this is a constant value and a valid C string.
643                CStr::from_bytes_with_nul(CURRENT_DIR_CSTR).unwrap()
644            } else {
645                name
646            };
647
648        let dir = self.inode_map.get(parent)?;
649        let dir_file = dir.get_file()?;
650        let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &dir_file, name)?;
651        let id = InodeId::from_stat(&st);
652
653        let mut found = None;
654        'search: loop {
655            match self.inode_map.get_alt(&id, handle_opt.as_ref()) {
656                // No existing entry found
657                None => break 'search,
658                Some(data) => {
659                    let curr = data.refcount.load(Ordering::Acquire);
660                    // forgot_one() has just destroyed the entry, retry...
661                    if curr == 0 {
662                        continue 'search;
663                    }
664
665                    // Saturating add to avoid integer overflow, it's not realistic to saturate u64.
666                    let new = curr.saturating_add(1);
667
668                    // Synchronizes with the forgot_one()
669                    if data
670                        .refcount
671                        .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
672                        .is_ok()
673                    {
674                        found = Some(data.inode);
675                        break;
676                    }
677                }
678            }
679        }
680
681        let inode = if let Some(v) = found {
682            v
683        } else {
684            let handle = if let Some(h) = handle_opt.clone() {
685                InodeHandle::Handle(self.to_openable_handle(h)?)
686            } else {
687                InodeHandle::File(path_fd)
688            };
689
690            // Write guard get_alt_locked() and insert_lock() to avoid race conditions.
691            let mut inodes = self.inode_map.get_map_mut();
692
693            // Lookup inode_map again after acquiring the inode_map lock, as there might be another
694            // racing thread already added an inode with the same id while we're not holding
695            // the lock. If so just use the newly added inode, otherwise the inode will be replaced
696            // and results in EBADF.
697            match InodeMap::get_alt_locked(inodes.deref(), &id, handle_opt.as_ref()) {
698                Some(data) => {
699                    // An inode was added concurrently while we did not hold a lock on
700                    // `self.inodes_map`, so we use that instead. `handle` will be dropped.
701                    data.refcount.fetch_add(1, Ordering::Relaxed);
702                    data.inode
703                }
704                None => {
705                    let inode = self.allocate_inode(inodes.deref(), &id, handle_opt.as_ref())?;
706
707                    if inode > VFS_MAX_INO {
708                        error!("fuse: max inode number reached: {}", VFS_MAX_INO);
709                        return Err(io::Error::other(format!(
710                            "max inode number reached: {VFS_MAX_INO}"
711                        )));
712                    }
713
714                    InodeMap::insert_locked(
715                        inodes.deref_mut(),
716                        Arc::new(InodeData::new(inode, handle, 1, id, st.st.st_mode)),
717                    );
718                    inode
719                }
720            }
721        };
722
723        let (entry_timeout, attr_timeout) = if is_dir(st.st.st_mode) {
724            (self.dir_entry_timeout, self.dir_attr_timeout)
725        } else {
726            (self.cfg.entry_timeout, self.cfg.attr_timeout)
727        };
728
729        // Whether to enable file DAX according to the value of dax_file_size
730        let mut attr_flags: u32 = 0;
731        if let Some(dax_file_size) = self.cfg.dax_file_size {
732            // st.stat.st_size is i64
733            if self.perfile_dax.load(Ordering::Relaxed)
734                && st.st.st_size >= 0x0
735                && st.st.st_size as u64 >= dax_file_size
736            {
737                attr_flags |= fuse::FUSE_ATTR_DAX;
738            }
739        }
740
741        Ok(Entry {
742            inode,
743            generation: 0,
744            attr: st.st,
745            attr_flags,
746            attr_timeout,
747            entry_timeout,
748        })
749    }
750
751    fn forget_one(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
752        // ROOT_ID should not be forgotten, or we're not able to access to files any more.
753        if inode == fuse::ROOT_ID {
754            return;
755        }
756
757        if let Some(data) = inodes.get(&inode) {
758            // Acquiring the write lock on the inode map prevents new lookups from incrementing the
759            // refcount but there is the possibility that a previous lookup already acquired a
760            // reference to the inode data and is in the process of updating the refcount so we need
761            // to loop here until we can decrement successfully.
762            loop {
763                let curr = data.refcount.load(Ordering::Acquire);
764
765                // Saturating sub because it doesn't make sense for a refcount to go below zero and
766                // we don't want misbehaving clients to cause integer overflow.
767                let new = curr.saturating_sub(count);
768
769                // Synchronizes with the acquire load in `do_lookup`.
770                if data
771                    .refcount
772                    .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
773                    .is_ok()
774                {
775                    if new == 0 {
776                        // We just removed the last refcount for this inode.
777                        // The allocated inode number should be kept in the map when use_host_ino
778                        // is false or host inode(don't use the virtual 56bit inode) is bigger than MAX_HOST_INO.
779                        let keep_mapping = !self.cfg.use_host_ino || data.id.ino > MAX_HOST_INO;
780                        inodes.remove(&inode, keep_mapping);
781                    }
782                    break;
783                }
784            }
785        }
786    }
787
788    fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
789        self.handle_map.release(handle, inode)
790    }
791
792    // Validate a path component, same as the one in vfs layer, but only do the validation if this
793    // passthroughfs is used without vfs layer, to avoid double validation.
794    fn validate_path_component(&self, name: &CStr) -> io::Result<()> {
795        // !self.cfg.do_import means we're under vfs, and vfs has already done the validation
796        if !self.cfg.do_import {
797            return Ok(());
798        }
799        validate_path_component(name)
800    }
801
802    // When seal_size is set, we don't allow operations that could change file size nor allocate
803    // space beyond EOF
804    fn seal_size_check(
805        &self,
806        opcode: Opcode,
807        file_size: u64,
808        offset: u64,
809        size: u64,
810        mode: i32,
811    ) -> io::Result<()> {
812        if offset.checked_add(size).is_none() {
813            error!(
814                "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX",
815                opcode, offset, size
816            );
817            return Err(einval());
818        }
819
820        match opcode {
821            // write should not exceed the file size.
822            Opcode::Write => {
823                if size + offset > file_size {
824                    return Err(eperm());
825                }
826            }
827
828            Opcode::Fallocate => {
829                let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE);
830                match op {
831                    // Allocate, punch and zero, must not change file size.
832                    0 | libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_ZERO_RANGE => {
833                        if size + offset > file_size {
834                            return Err(eperm());
835                        }
836                    }
837                    // collapse and insert will change file size, forbid.
838                    libc::FALLOC_FL_COLLAPSE_RANGE | libc::FALLOC_FL_INSERT_RANGE => {
839                        return Err(eperm());
840                    }
841                    // Invalid operation
842                    _ => return Err(einval()),
843                }
844            }
845
846            // setattr operation should be handled in setattr handler.
847            _ => return Err(enosys()),
848        }
849
850        Ok(())
851    }
852
853    fn get_writeback_open_flags(&self, flags: i32) -> i32 {
854        let mut new_flags = flags;
855        let writeback = self.writeback.load(Ordering::Relaxed);
856
857        // When writeback caching is enabled, the kernel may send read requests even if the
858        // userspace program opened the file write-only. So we need to ensure that we have opened
859        // the file for reading as well as writing.
860        if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
861            new_flags &= !libc::O_ACCMODE;
862            new_flags |= libc::O_RDWR;
863        }
864
865        // When writeback caching is enabled the kernel is responsible for handling `O_APPEND`.
866        // However, this breaks atomicity as the file may have changed on disk, invalidating the
867        // cached copy of the data in the kernel and the offset that the kernel thinks is the end of
868        // the file. Just allow this for now as it is the user's responsibility to enable writeback
869        // caching only for directories that are not shared. It also means that we need to clear the
870        // `O_APPEND` flag.
871        if writeback && flags & libc::O_APPEND != 0 {
872            new_flags &= !libc::O_APPEND;
873        }
874
875        new_flags
876    }
877}
878
879#[cfg(not(feature = "async-io"))]
880impl<S: BitmapSlice + Send + Sync + 'static> BackendFileSystem for PassthroughFs<S> {
881    fn mount(&self) -> io::Result<(Entry, u64)> {
882        let entry = self.do_lookup(fuse::ROOT_ID, &CString::new(".").unwrap())?;
883        Ok((entry, VFS_MAX_INO))
884    }
885
886    fn as_any(&self) -> &dyn Any {
887        self
888    }
889}
890
891macro_rules! scoped_cred {
892    ($name:ident, $ty:ty, $syscall_nr:expr) => {
893        #[derive(Debug)]
894        pub(crate) struct $name;
895
896        impl $name {
897            // Changes the effective uid/gid of the current thread to `val`.  Changes
898            // the thread's credentials back to root when the returned struct is dropped.
899            fn new(val: $ty) -> io::Result<Option<$name>> {
900                if val == 0 {
901                    // Nothing to do since we are already uid 0.
902                    return Ok(None);
903                }
904
905                // We want credential changes to be per-thread because otherwise
906                // we might interfere with operations being carried out on other
907                // threads with different uids/gids.  However, posix requires that
908                // all threads in a process share the same credentials.  To do this
909                // libc uses signals to ensure that when one thread changes its
910                // credentials the other threads do the same thing.
911                //
912                // So instead we invoke the syscall directly in order to get around
913                // this limitation.  Another option is to use the setfsuid and
914                // setfsgid systems calls.   However since those calls have no way to
915                // return an error, it's preferable to do this instead.
916
917                // This call is safe because it doesn't modify any memory and we
918                // check the return value.
919                let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) };
920                if res == 0 {
921                    Ok(Some($name))
922                } else {
923                    Err(io::Error::last_os_error())
924                }
925            }
926        }
927
928        impl Drop for $name {
929            fn drop(&mut self) {
930                let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) };
931                if res < 0 {
932                    error!(
933                        "fuse: failed to change credentials back to root: {}",
934                        io::Error::last_os_error(),
935                    );
936                }
937            }
938        }
939    };
940}
941scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid);
942scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid);
943
944fn set_creds(
945    uid: libc::uid_t,
946    gid: libc::gid_t,
947) -> io::Result<(Option<ScopedUid>, Option<ScopedGid>)> {
948    // We have to change the gid before we change the uid because if we change the uid first then we
949    // lose the capability to change the gid.  However changing back can happen in any order.
950    ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid)))
951}
952
953struct CapFsetid {}
954
955impl Drop for CapFsetid {
956    fn drop(&mut self) {
957        if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) {
958            error!("fail to restore thread cap_fsetid: {}", e);
959        };
960    }
961}
962
963fn drop_cap_fsetid() -> io::Result<Option<CapFsetid>> {
964    if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID)
965        .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))?
966    {
967        return Ok(None);
968    }
969    caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| {
970        io::Error::new(
971            io::ErrorKind::PermissionDenied,
972            "failed to drop CAP_FSETID capability",
973        )
974    })?;
975    Ok(Some(CapFsetid {}))
976}
977
978#[cfg(test)]
979mod tests {
980    use super::*;
981    use crate::abi::fuse_abi::CreateIn;
982    use crate::api::filesystem::*;
983    use crate::api::{Vfs, VfsOptions};
984    use caps::{CapSet, Capability};
985    use log;
986    use std::io::{Read, Seek, SeekFrom, Write};
987    use std::ops::Deref;
988    use std::os::unix::prelude::MetadataExt;
989    use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile};
990
991    fn prepare_passthroughfs() -> PassthroughFs {
992        let source = TempDir::new().expect("Cannot create temporary directory.");
993        let parent_path =
994            TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
995        let _child_path =
996            TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
997
998        let fs_cfg = Config {
999            writeback: true,
1000            do_import: true,
1001            no_open: true,
1002            inode_file_handles: false,
1003            root_dir: source
1004                .as_path()
1005                .to_str()
1006                .expect("source path to string")
1007                .to_string(),
1008            ..Default::default()
1009        };
1010        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1011        fs.import().unwrap();
1012
1013        fs
1014    }
1015
1016    fn passthroughfs_no_open(cfg: bool) {
1017        let opts = VfsOptions {
1018            no_open: cfg,
1019            ..Default::default()
1020        };
1021
1022        let vfs = &Vfs::new(opts);
1023        // Assume that fuse kernel supports no_open.
1024        vfs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1025
1026        let fs_cfg = Config {
1027            do_import: false,
1028            no_open: cfg,
1029            ..Default::default()
1030        };
1031        let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1032        fs.import().unwrap();
1033        vfs.mount(Box::new(fs), "/submnt/A").unwrap();
1034
1035        let (p_fs, _) = vfs.get_rootfs("/submnt/A").unwrap().unwrap();
1036        let any_fs = p_fs.deref().as_any();
1037        any_fs
1038            .downcast_ref::<PassthroughFs>()
1039            .map(|fs| {
1040                assert_eq!(fs.no_open.load(Ordering::Relaxed), cfg);
1041            })
1042            .unwrap();
1043    }
1044
1045    #[test]
1046    fn test_passthroughfs_no_open() {
1047        passthroughfs_no_open(true);
1048        passthroughfs_no_open(false);
1049    }
1050
1051    #[test]
1052    fn test_passthroughfs_inode_file_handles() {
1053        log::set_max_level(log::LevelFilter::Trace);
1054
1055        match caps::has_cap(None, CapSet::Effective, Capability::CAP_DAC_READ_SEARCH) {
1056            Ok(false) | Err(_) => {
1057                println!("invoking open_by_handle_at needs CAP_DAC_READ_SEARCH");
1058                return;
1059            }
1060            Ok(true) => {}
1061        }
1062
1063        let source = TempDir::new().expect("Cannot create temporary directory.");
1064        let parent_path =
1065            TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1066        let child_path =
1067            TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1068
1069        let fs_cfg = Config {
1070            writeback: true,
1071            do_import: true,
1072            no_open: true,
1073            inode_file_handles: true,
1074            root_dir: source
1075                .as_path()
1076                .to_str()
1077                .expect("source path to string")
1078                .to_string(),
1079            ..Default::default()
1080        };
1081        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1082        fs.import().unwrap();
1083
1084        let ctx = Context::default();
1085
1086        // read a few files to inode map.
1087        let parent = CString::new(
1088            parent_path
1089                .as_path()
1090                .file_name()
1091                .unwrap()
1092                .to_str()
1093                .expect("path to string"),
1094        )
1095        .unwrap();
1096        let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1097        let p_inode = p_entry.inode;
1098
1099        let child = CString::new(
1100            child_path
1101                .as_path()
1102                .file_name()
1103                .unwrap()
1104                .to_str()
1105                .expect("path to string"),
1106        )
1107        .unwrap();
1108        let c_entry = fs.lookup(&ctx, p_inode, &child).unwrap();
1109
1110        // Following test depends on host fs, it's not reliable.
1111        //let data = fs.inode_map.get(c_entry.inode).unwrap();
1112        //assert_eq!(matches!(data.handle, InodeHandle::Handle(_)), true);
1113
1114        let (_, duration) = fs.getattr(&ctx, c_entry.inode, None).unwrap();
1115        assert_eq!(duration, fs.cfg.attr_timeout);
1116
1117        fs.destroy();
1118    }
1119
1120    #[test]
1121    fn test_lookup_escape_root() {
1122        let fs = prepare_passthroughfs();
1123        let ctx = Context::default();
1124
1125        let name = CString::new("..").unwrap();
1126        let entry = fs.lookup(&ctx, ROOT_ID, &name).unwrap();
1127        assert_eq!(entry.inode, ROOT_ID);
1128    }
1129
1130    #[test]
1131    fn test_get_writeback_open_flags() {
1132        // prepare a fs with writeback cache and open being true, so O_WRONLY should be promoted to
1133        // O_RDWR, as writeback may read files even if file being opened with write-only. And
1134        // O_APPEND should be cleared as well.
1135        let mut fs = prepare_passthroughfs();
1136        fs.writeback = AtomicBool::new(true);
1137        fs.no_open = AtomicBool::new(false);
1138
1139        assert!(fs.writeback.load(Ordering::Relaxed));
1140        assert!(!fs.no_open.load(Ordering::Relaxed));
1141
1142        let flags = libc::O_RDWR;
1143        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1144
1145        let flags = libc::O_RDONLY;
1146        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1147
1148        let flags = libc::O_WRONLY;
1149        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1150
1151        let flags = libc::O_RDWR | libc::O_APPEND;
1152        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1153
1154        let flags = libc::O_RDONLY | libc::O_APPEND;
1155        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1156
1157        let flags = libc::O_WRONLY | libc::O_APPEND;
1158        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1159
1160        // prepare a fs with writeback cache disabled, open flags should not change
1161        let mut fs = prepare_passthroughfs();
1162        fs.writeback = AtomicBool::new(false);
1163        fs.no_open = AtomicBool::new(false);
1164
1165        assert!(!fs.writeback.load(Ordering::Relaxed));
1166        assert!(!fs.no_open.load(Ordering::Relaxed));
1167
1168        let flags = libc::O_RDWR;
1169        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1170
1171        let flags = libc::O_RDONLY;
1172        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1173
1174        let flags = libc::O_WRONLY;
1175        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_WRONLY);
1176
1177        let flags = libc::O_RDWR | libc::O_APPEND;
1178        assert_eq!(
1179            fs.get_writeback_open_flags(flags),
1180            libc::O_RDWR | libc::O_APPEND
1181        );
1182
1183        let flags = libc::O_RDONLY | libc::O_APPEND;
1184        assert_eq!(
1185            fs.get_writeback_open_flags(flags),
1186            libc::O_RDONLY | libc::O_APPEND
1187        );
1188
1189        let flags = libc::O_WRONLY | libc::O_APPEND;
1190        assert_eq!(
1191            fs.get_writeback_open_flags(flags),
1192            libc::O_WRONLY | libc::O_APPEND
1193        );
1194    }
1195
1196    #[test]
1197    fn test_writeback_open_and_create() {
1198        // prepare a fs with writeback cache and open being true, so a write-only opened file
1199        // should have read permission as well.
1200        let source = TempDir::new().expect("Cannot create temporary directory.");
1201        let _ = std::process::Command::new("sh")
1202            .arg("-c")
1203            .arg(format!("touch {}/existfile", source.as_path().to_str().unwrap()).as_str())
1204            .output()
1205            .unwrap();
1206        let fs_cfg = Config {
1207            writeback: true,
1208            do_import: true,
1209            no_open: false,
1210            inode_file_handles: false,
1211            root_dir: source
1212                .as_path()
1213                .to_str()
1214                .expect("source path to string")
1215                .to_string(),
1216            ..Default::default()
1217        };
1218        let mut fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1219        fs.writeback = AtomicBool::new(true);
1220        fs.no_open = AtomicBool::new(false);
1221        fs.import().unwrap();
1222
1223        assert!(fs.writeback.load(Ordering::Relaxed));
1224        assert!(!fs.no_open.load(Ordering::Relaxed));
1225
1226        let ctx = Context::default();
1227
1228        // Create a new file with O_WRONLY, and make sure we can read it as well.
1229        let fname = CString::new("testfile").unwrap();
1230        let args = CreateIn {
1231            flags: libc::O_WRONLY as u32,
1232            mode: 0644,
1233            umask: 0,
1234            fuse_flags: 0,
1235        };
1236        let (entry, handle, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1237        let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1238        let (_guard, mut f) = handle_data.get_file_mut();
1239        let mut buf = [0; 4];
1240        // Buggy code return EBADF on read
1241        let n = f.read(&mut buf).unwrap();
1242        assert_eq!(n, 0);
1243
1244        // Then Open an existing file with O_WRONLY, we should be able to read it as well.
1245        let fname = CString::new("existfile").unwrap();
1246        let entry = fs.lookup(&ctx, ROOT_ID, &fname).unwrap();
1247        let (handle, _, _) = fs
1248            .open(&ctx, entry.inode, libc::O_WRONLY as u32, 0)
1249            .unwrap();
1250        let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1251        let (_guard, mut f) = handle_data.get_file_mut();
1252        let mut buf = [0; 4];
1253        let n = f.read(&mut buf).unwrap();
1254        assert_eq!(n, 0);
1255    }
1256
1257    #[test]
1258    fn test_passthroughfs_dir_timeout() {
1259        log::set_max_level(log::LevelFilter::Trace);
1260
1261        let source = TempDir::new().expect("Cannot create temporary directory.");
1262        let parent_path =
1263            TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1264        let child_path =
1265            TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1266
1267        // passthroughfs with cache=none, but non-zero dir entry/attr timeout.
1268        let fs_cfg = Config {
1269            writeback: false,
1270            do_import: true,
1271            no_open: false,
1272            root_dir: source
1273                .as_path()
1274                .to_str()
1275                .expect("source path to string")
1276                .to_string(),
1277            cache_policy: CachePolicy::Never,
1278            entry_timeout: Duration::from_secs(0),
1279            attr_timeout: Duration::from_secs(0),
1280            dir_entry_timeout: Some(Duration::from_secs(1)),
1281            dir_attr_timeout: Some(Duration::from_secs(2)),
1282            ..Default::default()
1283        };
1284        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1285        fs.import().unwrap();
1286
1287        let ctx = Context::default();
1288
1289        // parent entry should have non-zero timeouts
1290        let parent = CString::new(
1291            parent_path
1292                .as_path()
1293                .file_name()
1294                .unwrap()
1295                .to_str()
1296                .expect("path to string"),
1297        )
1298        .unwrap();
1299        let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1300        assert_eq!(p_entry.entry_timeout, Duration::from_secs(1));
1301        assert_eq!(p_entry.attr_timeout, Duration::from_secs(2));
1302
1303        // regular file has zero timeout value
1304        let child = CString::new(
1305            child_path
1306                .as_path()
1307                .file_name()
1308                .unwrap()
1309                .to_str()
1310                .expect("path to string"),
1311        )
1312        .unwrap();
1313        let c_entry = fs.lookup(&ctx, p_entry.inode, &child).unwrap();
1314        assert_eq!(c_entry.entry_timeout, Duration::from_secs(0));
1315        assert_eq!(c_entry.attr_timeout, Duration::from_secs(0));
1316
1317        fs.destroy();
1318    }
1319
1320    #[test]
1321    fn test_stable_inode() {
1322        use std::os::unix::fs::MetadataExt;
1323        let source = TempDir::new().expect("Cannot create temporary directory.");
1324        let child_path = TempFile::new_in(source.as_path()).expect("Cannot create temporary file.");
1325        let child = CString::new(
1326            child_path
1327                .as_path()
1328                .file_name()
1329                .unwrap()
1330                .to_str()
1331                .expect("path to string"),
1332        )
1333        .unwrap();
1334        let meta = child_path.as_file().metadata().unwrap();
1335        let ctx = Context::default();
1336        {
1337            let fs_cfg = Config {
1338                writeback: true,
1339                do_import: true,
1340                no_open: true,
1341                inode_file_handles: false,
1342                root_dir: source
1343                    .as_path()
1344                    .to_str()
1345                    .expect("source path to string")
1346                    .to_string(),
1347                ..Default::default()
1348            };
1349            let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1350            fs.import().unwrap();
1351            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1352            assert_eq!(entry.inode, ROOT_ID + 1);
1353            fs.forget(&ctx, entry.inode, 1);
1354            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1355            assert_eq!(entry.inode, ROOT_ID + 1);
1356        }
1357        {
1358            let fs_cfg = Config {
1359                writeback: true,
1360                do_import: true,
1361                no_open: true,
1362                inode_file_handles: false,
1363                root_dir: source
1364                    .as_path()
1365                    .to_str()
1366                    .expect("source path to string")
1367                    .to_string(),
1368                use_host_ino: true,
1369                ..Default::default()
1370            };
1371            let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1372            fs.import().unwrap();
1373            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1374            assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1375            let inode_store = fs.inode_map.get_map_mut();
1376            let inode_data = inode_store.get(&entry.inode).unwrap();
1377            assert!(inode_store.inode_by_id(&inode_data.id).is_some());
1378            let id = inode_data.id.clone();
1379            drop(inode_store);
1380
1381            fs.forget(&ctx, entry.inode, 1);
1382            let inode_store = fs.inode_map.get_map_mut();
1383            assert!(inode_store.get(&entry.inode).is_none());
1384            assert!(inode_store.inode_by_id(&id).is_none());
1385            drop(inode_store);
1386
1387            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1388            assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1389        }
1390    }
1391
1392    #[test]
1393    fn test_allocation_inode_locked() {
1394        {
1395            let fs = prepare_passthroughfs();
1396            let m = InodeStore::default();
1397            let id = InodeId {
1398                ino: MAX_HOST_INO + 1,
1399                dev: 1,
1400                mnt: 1,
1401            };
1402
1403            // Default
1404            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1405            assert_eq!(inode, 2);
1406        }
1407
1408        {
1409            let mut fs = prepare_passthroughfs();
1410            fs.cfg.use_host_ino = true;
1411            let m = InodeStore::default();
1412            let id = InodeId {
1413                ino: 12345,
1414                dev: 1,
1415                mnt: 1,
1416            };
1417            // direct return host inode 12345
1418            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1419            assert_eq!(inode & MAX_HOST_INO, 12345)
1420        }
1421
1422        {
1423            let mut fs = prepare_passthroughfs();
1424            fs.cfg.use_host_ino = true;
1425            let mut m = InodeStore::default();
1426            let id = InodeId {
1427                ino: MAX_HOST_INO + 1,
1428                dev: 1,
1429                mnt: 1,
1430            };
1431            // allocate a virtual inode
1432            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1433            assert_eq!(inode & MAX_HOST_INO, 2);
1434            let file = TempFile::new().expect("Cannot create temporary file.");
1435            let mode = file.as_file().metadata().unwrap().mode();
1436            let inode_data =
1437                InodeData::new(inode, InodeHandle::File(file.into_file()), 1, id, mode);
1438            m.insert(Arc::new(inode_data));
1439            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1440            assert_eq!(inode & MAX_HOST_INO, 2);
1441        }
1442    }
1443
1444    #[test]
1445    fn test_validate_virtiofs_config() {
1446        // cache=none + writeback, writeback should be disabled
1447        let fs_cfg = Config {
1448            writeback: true,
1449            cache_policy: CachePolicy::Never,
1450            ..Default::default()
1451        };
1452        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1453        assert!(!fs.cfg.writeback);
1454
1455        // cache=none + no_open, no_open should be disabled
1456        let fs_cfg = Config {
1457            no_open: true,
1458            cache_policy: CachePolicy::Never,
1459            ..Default::default()
1460        };
1461        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1462        assert!(!fs.cfg.no_open);
1463
1464        // cache=auto + no_open, no_open should be disabled
1465        let fs_cfg = Config {
1466            no_open: true,
1467            cache_policy: CachePolicy::Auto,
1468            ..Default::default()
1469        };
1470        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1471        assert!(!fs.cfg.no_open);
1472
1473        // cache=always + no_open, no_open should be set
1474        let fs_cfg = Config {
1475            no_open: true,
1476            cache_policy: CachePolicy::Always,
1477            ..Default::default()
1478        };
1479        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1480        assert!(fs.cfg.no_open);
1481
1482        // cache=none + no_open + writeback, no_open and writeback should be disabled
1483        let fs_cfg = Config {
1484            no_open: true,
1485            writeback: true,
1486            cache_policy: CachePolicy::Never,
1487            ..Default::default()
1488        };
1489        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1490        assert!(!fs.cfg.no_open);
1491        assert!(!fs.cfg.writeback);
1492    }
1493
1494    #[test]
1495    fn test_generic_read_write_noopen() {
1496        let tmpdir = TempDir::new().expect("Cannot create temporary directory.");
1497        // Prepare passthrough fs.
1498        let fs_cfg = Config {
1499            do_import: false,
1500            no_open: true,
1501            root_dir: tmpdir.as_path().to_string_lossy().to_string(),
1502            ..Default::default()
1503        };
1504        let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1505        fs.import().unwrap();
1506        fs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1507        fs.mount().unwrap();
1508
1509        // Create a new file for testing.
1510        let ctx = Context::default();
1511        let createin = CreateIn {
1512            flags: libc::O_CREAT as u32,
1513            mode: 0o644,
1514            umask: 0,
1515            fuse_flags: 0,
1516        };
1517        let file_name = CString::new("test_file").unwrap();
1518
1519        let (entry, _, _, _) = fs
1520            .create(&ctx, ROOT_ID, file_name.as_c_str(), createin)
1521            .unwrap();
1522        let ino = entry.inode;
1523        assert_ne!(ino, 0);
1524        assert_ne!(ino, ROOT_ID);
1525
1526        // Write on the inode
1527        let data = b"hello world";
1528        // Write to one intermidiate temp file.
1529        let buffer_file = TempFile::new().expect("Cannot create temporary file.");
1530        let mut buffer_file = buffer_file.into_file();
1531        buffer_file.write_all(data).unwrap();
1532        let _ = buffer_file.flush();
1533
1534        // Read back and check.
1535        let mut newbuf = Vec::new();
1536        buffer_file.seek(SeekFrom::Start(0)).unwrap();
1537        buffer_file.read_to_end(&mut newbuf).unwrap();
1538        assert_eq!(newbuf, data);
1539
1540        // Call fs.write to write content to the file
1541        buffer_file.seek(SeekFrom::Start(0)).unwrap();
1542        let write_sz = fs
1543            .write(
1544                &ctx,
1545                ino,
1546                0,
1547                &mut buffer_file,
1548                data.len() as u32,
1549                0,
1550                None,
1551                false,
1552                0,
1553                0,
1554            )
1555            .unwrap();
1556        assert_eq!(write_sz, data.len());
1557
1558        // Create a new temp file as read buffer.
1559        let read_buffer_file = TempFile::new().expect("Cannot create temporary file.");
1560        let mut read_buffer_file = read_buffer_file.into_file();
1561        let read_sz = fs
1562            .read(
1563                &ctx,
1564                ino,
1565                0,
1566                &mut read_buffer_file,
1567                data.len() as u32,
1568                0,
1569                None,
1570                0,
1571            )
1572            .unwrap();
1573        assert_eq!(read_sz, data.len());
1574
1575        read_buffer_file.seek(SeekFrom::Start(0)).unwrap();
1576        let mut newbuf = Vec::new();
1577        read_buffer_file.read_to_end(&mut newbuf).unwrap();
1578        assert_eq!(newbuf, data);
1579    }
1580}