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            .map(Arc::clone)
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            .map(Arc::clone)
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            .map(Arc::clone)
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::new(
710                            io::ErrorKind::Other,
711                            format!("max inode number reached: {VFS_MAX_INO}"),
712                        ));
713                    }
714
715                    InodeMap::insert_locked(
716                        inodes.deref_mut(),
717                        Arc::new(InodeData::new(inode, handle, 1, id, st.st.st_mode)),
718                    );
719                    inode
720                }
721            }
722        };
723
724        let (entry_timeout, attr_timeout) = if is_dir(st.st.st_mode) {
725            (self.dir_entry_timeout, self.dir_attr_timeout)
726        } else {
727            (self.cfg.entry_timeout, self.cfg.attr_timeout)
728        };
729
730        // Whether to enable file DAX according to the value of dax_file_size
731        let mut attr_flags: u32 = 0;
732        if let Some(dax_file_size) = self.cfg.dax_file_size {
733            // st.stat.st_size is i64
734            if self.perfile_dax.load(Ordering::Relaxed)
735                && st.st.st_size >= 0x0
736                && st.st.st_size as u64 >= dax_file_size
737            {
738                attr_flags |= fuse::FUSE_ATTR_DAX;
739            }
740        }
741
742        Ok(Entry {
743            inode,
744            generation: 0,
745            attr: st.st,
746            attr_flags,
747            attr_timeout,
748            entry_timeout,
749        })
750    }
751
752    fn forget_one(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
753        // ROOT_ID should not be forgotten, or we're not able to access to files any more.
754        if inode == fuse::ROOT_ID {
755            return;
756        }
757
758        if let Some(data) = inodes.get(&inode) {
759            // Acquiring the write lock on the inode map prevents new lookups from incrementing the
760            // refcount but there is the possibility that a previous lookup already acquired a
761            // reference to the inode data and is in the process of updating the refcount so we need
762            // to loop here until we can decrement successfully.
763            loop {
764                let curr = data.refcount.load(Ordering::Acquire);
765
766                // Saturating sub because it doesn't make sense for a refcount to go below zero and
767                // we don't want misbehaving clients to cause integer overflow.
768                let new = curr.saturating_sub(count);
769
770                // Synchronizes with the acquire load in `do_lookup`.
771                if data
772                    .refcount
773                    .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
774                    .is_ok()
775                {
776                    if new == 0 {
777                        // We just removed the last refcount for this inode.
778                        // The allocated inode number should be kept in the map when use_host_ino
779                        // is false or inode is bigger than MAX_HOST_INO.
780                        let keep_mapping = !self.cfg.use_host_ino || inode > MAX_HOST_INO;
781                        inodes.remove(&inode, keep_mapping);
782                    }
783                    break;
784                }
785            }
786        }
787    }
788
789    fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
790        self.handle_map.release(handle, inode)
791    }
792
793    // Validate a path component, same as the one in vfs layer, but only do the validation if this
794    // passthroughfs is used without vfs layer, to avoid double validation.
795    fn validate_path_component(&self, name: &CStr) -> io::Result<()> {
796        // !self.cfg.do_import means we're under vfs, and vfs has already done the validation
797        if !self.cfg.do_import {
798            return Ok(());
799        }
800        validate_path_component(name)
801    }
802
803    // When seal_size is set, we don't allow operations that could change file size nor allocate
804    // space beyond EOF
805    fn seal_size_check(
806        &self,
807        opcode: Opcode,
808        file_size: u64,
809        offset: u64,
810        size: u64,
811        mode: i32,
812    ) -> io::Result<()> {
813        if offset.checked_add(size).is_none() {
814            error!(
815                "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX",
816                opcode, offset, size
817            );
818            return Err(einval());
819        }
820
821        match opcode {
822            // write should not exceed the file size.
823            Opcode::Write => {
824                if size + offset > file_size {
825                    return Err(eperm());
826                }
827            }
828
829            Opcode::Fallocate => {
830                let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE);
831                match op {
832                    // Allocate, punch and zero, must not change file size.
833                    0 | libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_ZERO_RANGE => {
834                        if size + offset > file_size {
835                            return Err(eperm());
836                        }
837                    }
838                    // collapse and insert will change file size, forbid.
839                    libc::FALLOC_FL_COLLAPSE_RANGE | libc::FALLOC_FL_INSERT_RANGE => {
840                        return Err(eperm());
841                    }
842                    // Invalid operation
843                    _ => return Err(einval()),
844                }
845            }
846
847            // setattr operation should be handled in setattr handler.
848            _ => return Err(enosys()),
849        }
850
851        Ok(())
852    }
853
854    fn get_writeback_open_flags(&self, flags: i32) -> i32 {
855        let mut new_flags = flags;
856        let writeback = self.writeback.load(Ordering::Relaxed);
857
858        // When writeback caching is enabled, the kernel may send read requests even if the
859        // userspace program opened the file write-only. So we need to ensure that we have opened
860        // the file for reading as well as writing.
861        if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
862            new_flags &= !libc::O_ACCMODE;
863            new_flags |= libc::O_RDWR;
864        }
865
866        // When writeback caching is enabled the kernel is responsible for handling `O_APPEND`.
867        // However, this breaks atomicity as the file may have changed on disk, invalidating the
868        // cached copy of the data in the kernel and the offset that the kernel thinks is the end of
869        // the file. Just allow this for now as it is the user's responsibility to enable writeback
870        // caching only for directories that are not shared. It also means that we need to clear the
871        // `O_APPEND` flag.
872        if writeback && flags & libc::O_APPEND != 0 {
873            new_flags &= !libc::O_APPEND;
874        }
875
876        new_flags
877    }
878}
879
880#[cfg(not(feature = "async-io"))]
881impl<S: BitmapSlice + Send + Sync + 'static> BackendFileSystem for PassthroughFs<S> {
882    fn mount(&self) -> io::Result<(Entry, u64)> {
883        let entry = self.do_lookup(fuse::ROOT_ID, &CString::new(".").unwrap())?;
884        Ok((entry, VFS_MAX_INO))
885    }
886
887    fn as_any(&self) -> &dyn Any {
888        self
889    }
890}
891
892macro_rules! scoped_cred {
893    ($name:ident, $ty:ty, $syscall_nr:expr) => {
894        #[derive(Debug)]
895        pub(crate) struct $name;
896
897        impl $name {
898            // Changes the effective uid/gid of the current thread to `val`.  Changes
899            // the thread's credentials back to root when the returned struct is dropped.
900            fn new(val: $ty) -> io::Result<Option<$name>> {
901                if val == 0 {
902                    // Nothing to do since we are already uid 0.
903                    return Ok(None);
904                }
905
906                // We want credential changes to be per-thread because otherwise
907                // we might interfere with operations being carried out on other
908                // threads with different uids/gids.  However, posix requires that
909                // all threads in a process share the same credentials.  To do this
910                // libc uses signals to ensure that when one thread changes its
911                // credentials the other threads do the same thing.
912                //
913                // So instead we invoke the syscall directly in order to get around
914                // this limitation.  Another option is to use the setfsuid and
915                // setfsgid systems calls.   However since those calls have no way to
916                // return an error, it's preferable to do this instead.
917
918                // This call is safe because it doesn't modify any memory and we
919                // check the return value.
920                let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) };
921                if res == 0 {
922                    Ok(Some($name))
923                } else {
924                    Err(io::Error::last_os_error())
925                }
926            }
927        }
928
929        impl Drop for $name {
930            fn drop(&mut self) {
931                let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) };
932                if res < 0 {
933                    error!(
934                        "fuse: failed to change credentials back to root: {}",
935                        io::Error::last_os_error(),
936                    );
937                }
938            }
939        }
940    };
941}
942scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid);
943scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid);
944
945fn set_creds(
946    uid: libc::uid_t,
947    gid: libc::gid_t,
948) -> io::Result<(Option<ScopedUid>, Option<ScopedGid>)> {
949    // We have to change the gid before we change the uid because if we change the uid first then we
950    // lose the capability to change the gid.  However changing back can happen in any order.
951    ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid)))
952}
953
954struct CapFsetid {}
955
956impl Drop for CapFsetid {
957    fn drop(&mut self) {
958        if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) {
959            error!("fail to restore thread cap_fsetid: {}", e);
960        };
961    }
962}
963
964fn drop_cap_fsetid() -> io::Result<Option<CapFsetid>> {
965    if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID)
966        .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))?
967    {
968        return Ok(None);
969    }
970    caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| {
971        io::Error::new(
972            io::ErrorKind::PermissionDenied,
973            "failed to drop CAP_FSETID capability",
974        )
975    })?;
976    Ok(Some(CapFsetid {}))
977}
978
979#[cfg(test)]
980mod tests {
981    use super::*;
982    use crate::abi::fuse_abi::CreateIn;
983    use crate::api::filesystem::*;
984    use crate::api::{Vfs, VfsOptions};
985    use caps::{CapSet, Capability};
986    use log;
987    use std::io::{Read, Seek, SeekFrom, Write};
988    use std::ops::Deref;
989    use std::os::unix::prelude::MetadataExt;
990    use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile};
991
992    fn prepare_passthroughfs() -> PassthroughFs {
993        let source = TempDir::new().expect("Cannot create temporary directory.");
994        let parent_path =
995            TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
996        let _child_path =
997            TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
998
999        let fs_cfg = Config {
1000            writeback: true,
1001            do_import: true,
1002            no_open: true,
1003            inode_file_handles: false,
1004            root_dir: source
1005                .as_path()
1006                .to_str()
1007                .expect("source path to string")
1008                .to_string(),
1009            ..Default::default()
1010        };
1011        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1012        fs.import().unwrap();
1013
1014        fs
1015    }
1016
1017    fn passthroughfs_no_open(cfg: bool) {
1018        let opts = VfsOptions {
1019            no_open: cfg,
1020            ..Default::default()
1021        };
1022
1023        let vfs = &Vfs::new(opts);
1024        // Assume that fuse kernel supports no_open.
1025        vfs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1026
1027        let fs_cfg = Config {
1028            do_import: false,
1029            no_open: cfg,
1030            ..Default::default()
1031        };
1032        let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1033        fs.import().unwrap();
1034        vfs.mount(Box::new(fs), "/submnt/A").unwrap();
1035
1036        let p_fs = vfs.get_rootfs("/submnt/A").unwrap().unwrap();
1037        let any_fs = p_fs.deref().as_any();
1038        any_fs
1039            .downcast_ref::<PassthroughFs>()
1040            .map(|fs| {
1041                assert_eq!(fs.no_open.load(Ordering::Relaxed), cfg);
1042            })
1043            .unwrap();
1044    }
1045
1046    #[test]
1047    fn test_passthroughfs_no_open() {
1048        passthroughfs_no_open(true);
1049        passthroughfs_no_open(false);
1050    }
1051
1052    #[test]
1053    fn test_passthroughfs_inode_file_handles() {
1054        log::set_max_level(log::LevelFilter::Trace);
1055
1056        match caps::has_cap(None, CapSet::Effective, Capability::CAP_DAC_READ_SEARCH) {
1057            Ok(false) | Err(_) => {
1058                println!("invoking open_by_handle_at needs CAP_DAC_READ_SEARCH");
1059                return;
1060            }
1061            Ok(true) => {}
1062        }
1063
1064        let source = TempDir::new().expect("Cannot create temporary directory.");
1065        let parent_path =
1066            TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1067        let child_path =
1068            TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1069
1070        let fs_cfg = Config {
1071            writeback: true,
1072            do_import: true,
1073            no_open: true,
1074            inode_file_handles: true,
1075            root_dir: source
1076                .as_path()
1077                .to_str()
1078                .expect("source path to string")
1079                .to_string(),
1080            ..Default::default()
1081        };
1082        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1083        fs.import().unwrap();
1084
1085        let ctx = Context::default();
1086
1087        // read a few files to inode map.
1088        let parent = CString::new(
1089            parent_path
1090                .as_path()
1091                .file_name()
1092                .unwrap()
1093                .to_str()
1094                .expect("path to string"),
1095        )
1096        .unwrap();
1097        let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1098        let p_inode = p_entry.inode;
1099
1100        let child = CString::new(
1101            child_path
1102                .as_path()
1103                .file_name()
1104                .unwrap()
1105                .to_str()
1106                .expect("path to string"),
1107        )
1108        .unwrap();
1109        let c_entry = fs.lookup(&ctx, p_inode, &child).unwrap();
1110
1111        // Following test depends on host fs, it's not reliable.
1112        //let data = fs.inode_map.get(c_entry.inode).unwrap();
1113        //assert_eq!(matches!(data.handle, InodeHandle::Handle(_)), true);
1114
1115        let (_, duration) = fs.getattr(&ctx, c_entry.inode, None).unwrap();
1116        assert_eq!(duration, fs.cfg.attr_timeout);
1117
1118        fs.destroy();
1119    }
1120
1121    #[test]
1122    fn test_lookup_escape_root() {
1123        let fs = prepare_passthroughfs();
1124        let ctx = Context::default();
1125
1126        let name = CString::new("..").unwrap();
1127        let entry = fs.lookup(&ctx, ROOT_ID, &name).unwrap();
1128        assert_eq!(entry.inode, ROOT_ID);
1129    }
1130
1131    #[test]
1132    fn test_get_writeback_open_flags() {
1133        // prepare a fs with writeback cache and open being true, so O_WRONLY should be promoted to
1134        // O_RDWR, as writeback may read files even if file being opened with write-only. And
1135        // O_APPEND should be cleared as well.
1136        let mut fs = prepare_passthroughfs();
1137        fs.writeback = AtomicBool::new(true);
1138        fs.no_open = AtomicBool::new(false);
1139
1140        assert!(fs.writeback.load(Ordering::Relaxed));
1141        assert!(!fs.no_open.load(Ordering::Relaxed));
1142
1143        let flags = libc::O_RDWR;
1144        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1145
1146        let flags = libc::O_RDONLY;
1147        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1148
1149        let flags = libc::O_WRONLY;
1150        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1151
1152        let flags = libc::O_RDWR | libc::O_APPEND;
1153        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1154
1155        let flags = libc::O_RDONLY | libc::O_APPEND;
1156        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1157
1158        let flags = libc::O_WRONLY | libc::O_APPEND;
1159        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1160
1161        // prepare a fs with writeback cache disabled, open flags should not change
1162        let mut fs = prepare_passthroughfs();
1163        fs.writeback = AtomicBool::new(false);
1164        fs.no_open = AtomicBool::new(false);
1165
1166        assert!(!fs.writeback.load(Ordering::Relaxed));
1167        assert!(!fs.no_open.load(Ordering::Relaxed));
1168
1169        let flags = libc::O_RDWR;
1170        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1171
1172        let flags = libc::O_RDONLY;
1173        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1174
1175        let flags = libc::O_WRONLY;
1176        assert_eq!(fs.get_writeback_open_flags(flags), libc::O_WRONLY);
1177
1178        let flags = libc::O_RDWR | libc::O_APPEND;
1179        assert_eq!(
1180            fs.get_writeback_open_flags(flags),
1181            libc::O_RDWR | libc::O_APPEND
1182        );
1183
1184        let flags = libc::O_RDONLY | libc::O_APPEND;
1185        assert_eq!(
1186            fs.get_writeback_open_flags(flags),
1187            libc::O_RDONLY | libc::O_APPEND
1188        );
1189
1190        let flags = libc::O_WRONLY | libc::O_APPEND;
1191        assert_eq!(
1192            fs.get_writeback_open_flags(flags),
1193            libc::O_WRONLY | libc::O_APPEND
1194        );
1195    }
1196
1197    #[test]
1198    fn test_writeback_open_and_create() {
1199        // prepare a fs with writeback cache and open being true, so a write-only opened file
1200        // should have read permission as well.
1201        let source = TempDir::new().expect("Cannot create temporary directory.");
1202        let _ = std::process::Command::new("sh")
1203            .arg("-c")
1204            .arg(format!("touch {}/existfile", source.as_path().to_str().unwrap()).as_str())
1205            .output()
1206            .unwrap();
1207        let fs_cfg = Config {
1208            writeback: true,
1209            do_import: true,
1210            no_open: false,
1211            inode_file_handles: false,
1212            root_dir: source
1213                .as_path()
1214                .to_str()
1215                .expect("source path to string")
1216                .to_string(),
1217            ..Default::default()
1218        };
1219        let mut fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1220        fs.writeback = AtomicBool::new(true);
1221        fs.no_open = AtomicBool::new(false);
1222        fs.import().unwrap();
1223
1224        assert!(fs.writeback.load(Ordering::Relaxed));
1225        assert!(!fs.no_open.load(Ordering::Relaxed));
1226
1227        let ctx = Context::default();
1228
1229        // Create a new file with O_WRONLY, and make sure we can read it as well.
1230        let fname = CString::new("testfile").unwrap();
1231        let args = CreateIn {
1232            flags: libc::O_WRONLY as u32,
1233            mode: 0644,
1234            umask: 0,
1235            fuse_flags: 0,
1236        };
1237        let (entry, handle, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1238        let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1239        let (_guard, mut f) = handle_data.get_file_mut();
1240        let mut buf = [0; 4];
1241        // Buggy code return EBADF on read
1242        let n = f.read(&mut buf).unwrap();
1243        assert_eq!(n, 0);
1244
1245        // Then Open an existing file with O_WRONLY, we should be able to read it as well.
1246        let fname = CString::new("existfile").unwrap();
1247        let entry = fs.lookup(&ctx, ROOT_ID, &fname).unwrap();
1248        let (handle, _, _) = fs
1249            .open(&ctx, entry.inode, libc::O_WRONLY as u32, 0)
1250            .unwrap();
1251        let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1252        let (_guard, mut f) = handle_data.get_file_mut();
1253        let mut buf = [0; 4];
1254        let n = f.read(&mut buf).unwrap();
1255        assert_eq!(n, 0);
1256    }
1257
1258    #[test]
1259    fn test_passthroughfs_dir_timeout() {
1260        log::set_max_level(log::LevelFilter::Trace);
1261
1262        let source = TempDir::new().expect("Cannot create temporary directory.");
1263        let parent_path =
1264            TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1265        let child_path =
1266            TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1267
1268        // passthroughfs with cache=none, but non-zero dir entry/attr timeout.
1269        let fs_cfg = Config {
1270            writeback: false,
1271            do_import: true,
1272            no_open: false,
1273            root_dir: source
1274                .as_path()
1275                .to_str()
1276                .expect("source path to string")
1277                .to_string(),
1278            cache_policy: CachePolicy::Never,
1279            entry_timeout: Duration::from_secs(0),
1280            attr_timeout: Duration::from_secs(0),
1281            dir_entry_timeout: Some(Duration::from_secs(1)),
1282            dir_attr_timeout: Some(Duration::from_secs(2)),
1283            ..Default::default()
1284        };
1285        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1286        fs.import().unwrap();
1287
1288        let ctx = Context::default();
1289
1290        // parent entry should have non-zero timeouts
1291        let parent = CString::new(
1292            parent_path
1293                .as_path()
1294                .file_name()
1295                .unwrap()
1296                .to_str()
1297                .expect("path to string"),
1298        )
1299        .unwrap();
1300        let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1301        assert_eq!(p_entry.entry_timeout, Duration::from_secs(1));
1302        assert_eq!(p_entry.attr_timeout, Duration::from_secs(2));
1303
1304        // regular file has zero timeout value
1305        let child = CString::new(
1306            child_path
1307                .as_path()
1308                .file_name()
1309                .unwrap()
1310                .to_str()
1311                .expect("path to string"),
1312        )
1313        .unwrap();
1314        let c_entry = fs.lookup(&ctx, p_entry.inode, &child).unwrap();
1315        assert_eq!(c_entry.entry_timeout, Duration::from_secs(0));
1316        assert_eq!(c_entry.attr_timeout, Duration::from_secs(0));
1317
1318        fs.destroy();
1319    }
1320
1321    #[test]
1322    fn test_stable_inode() {
1323        use std::os::unix::fs::MetadataExt;
1324        let source = TempDir::new().expect("Cannot create temporary directory.");
1325        let child_path = TempFile::new_in(source.as_path()).expect("Cannot create temporary file.");
1326        let child = CString::new(
1327            child_path
1328                .as_path()
1329                .file_name()
1330                .unwrap()
1331                .to_str()
1332                .expect("path to string"),
1333        )
1334        .unwrap();
1335        let meta = child_path.as_file().metadata().unwrap();
1336        let ctx = Context::default();
1337        {
1338            let fs_cfg = Config {
1339                writeback: true,
1340                do_import: true,
1341                no_open: true,
1342                inode_file_handles: false,
1343                root_dir: source
1344                    .as_path()
1345                    .to_str()
1346                    .expect("source path to string")
1347                    .to_string(),
1348                ..Default::default()
1349            };
1350            let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1351            fs.import().unwrap();
1352            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1353            assert_eq!(entry.inode, ROOT_ID + 1);
1354            fs.forget(&ctx, entry.inode, 1);
1355            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1356            assert_eq!(entry.inode, ROOT_ID + 1);
1357        }
1358        {
1359            let fs_cfg = Config {
1360                writeback: true,
1361                do_import: true,
1362                no_open: true,
1363                inode_file_handles: false,
1364                root_dir: source
1365                    .as_path()
1366                    .to_str()
1367                    .expect("source path to string")
1368                    .to_string(),
1369                use_host_ino: true,
1370                ..Default::default()
1371            };
1372            let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1373            fs.import().unwrap();
1374            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1375            assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1376            fs.forget(&ctx, entry.inode, 1);
1377            let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1378            assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1379        }
1380    }
1381
1382    #[test]
1383    fn test_allocation_inode_locked() {
1384        {
1385            let fs = prepare_passthroughfs();
1386            let m = InodeStore::default();
1387            let id = InodeId {
1388                ino: MAX_HOST_INO + 1,
1389                dev: 1,
1390                mnt: 1,
1391            };
1392
1393            // Default
1394            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1395            assert_eq!(inode, 2);
1396        }
1397
1398        {
1399            let mut fs = prepare_passthroughfs();
1400            fs.cfg.use_host_ino = true;
1401            let m = InodeStore::default();
1402            let id = InodeId {
1403                ino: 12345,
1404                dev: 1,
1405                mnt: 1,
1406            };
1407            // direct return host inode 12345
1408            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1409            assert_eq!(inode & MAX_HOST_INO, 12345)
1410        }
1411
1412        {
1413            let mut fs = prepare_passthroughfs();
1414            fs.cfg.use_host_ino = true;
1415            let mut m = InodeStore::default();
1416            let id = InodeId {
1417                ino: MAX_HOST_INO + 1,
1418                dev: 1,
1419                mnt: 1,
1420            };
1421            // allocate a virtual inode
1422            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1423            assert_eq!(inode & MAX_HOST_INO, 2);
1424            let file = TempFile::new().expect("Cannot create temporary file.");
1425            let mode = file.as_file().metadata().unwrap().mode();
1426            let inode_data =
1427                InodeData::new(inode, InodeHandle::File(file.into_file()), 1, id, mode);
1428            m.insert(Arc::new(inode_data));
1429            let inode = fs.allocate_inode(&m, &id, None).unwrap();
1430            assert_eq!(inode & MAX_HOST_INO, 2);
1431        }
1432    }
1433
1434    #[test]
1435    fn test_validate_virtiofs_config() {
1436        // cache=none + writeback, writeback should be disabled
1437        let fs_cfg = Config {
1438            writeback: true,
1439            cache_policy: CachePolicy::Never,
1440            ..Default::default()
1441        };
1442        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1443        assert!(!fs.cfg.writeback);
1444
1445        // cache=none + no_open, no_open should be disabled
1446        let fs_cfg = Config {
1447            no_open: true,
1448            cache_policy: CachePolicy::Never,
1449            ..Default::default()
1450        };
1451        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1452        assert!(!fs.cfg.no_open);
1453
1454        // cache=auto + no_open, no_open should be disabled
1455        let fs_cfg = Config {
1456            no_open: true,
1457            cache_policy: CachePolicy::Auto,
1458            ..Default::default()
1459        };
1460        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1461        assert!(!fs.cfg.no_open);
1462
1463        // cache=always + no_open, no_open should be set
1464        let fs_cfg = Config {
1465            no_open: true,
1466            cache_policy: CachePolicy::Always,
1467            ..Default::default()
1468        };
1469        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1470        assert!(fs.cfg.no_open);
1471
1472        // cache=none + no_open + writeback, no_open and writeback should be disabled
1473        let fs_cfg = Config {
1474            no_open: true,
1475            writeback: true,
1476            cache_policy: CachePolicy::Never,
1477            ..Default::default()
1478        };
1479        let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1480        assert!(!fs.cfg.no_open);
1481        assert!(!fs.cfg.writeback);
1482    }
1483
1484    #[test]
1485    fn test_generic_read_write_noopen() {
1486        let tmpdir = TempDir::new().expect("Cannot create temporary directory.");
1487        // Prepare passthrough fs.
1488        let fs_cfg = Config {
1489            do_import: false,
1490            no_open: true,
1491            root_dir: tmpdir.as_path().to_string_lossy().to_string(),
1492            ..Default::default()
1493        };
1494        let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1495        fs.import().unwrap();
1496        fs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1497        fs.mount().unwrap();
1498
1499        // Create a new file for testing.
1500        let ctx = Context::default();
1501        let createin = CreateIn {
1502            flags: libc::O_CREAT as u32,
1503            mode: 0o644,
1504            umask: 0,
1505            fuse_flags: 0,
1506        };
1507        let file_name = CString::new("test_file").unwrap();
1508
1509        let (entry, _, _, _) = fs
1510            .create(&ctx, ROOT_ID, file_name.as_c_str(), createin)
1511            .unwrap();
1512        let ino = entry.inode;
1513        assert_ne!(ino, 0);
1514        assert_ne!(ino, ROOT_ID);
1515
1516        // Write on the inode
1517        let data = b"hello world";
1518        // Write to one intermidiate temp file.
1519        let buffer_file = TempFile::new().expect("Cannot create temporary file.");
1520        let mut buffer_file = buffer_file.into_file();
1521        buffer_file.write_all(data).unwrap();
1522        let _ = buffer_file.flush();
1523
1524        // Read back and check.
1525        let mut newbuf = Vec::new();
1526        buffer_file.seek(SeekFrom::Start(0)).unwrap();
1527        buffer_file.read_to_end(&mut newbuf).unwrap();
1528        assert_eq!(newbuf, data);
1529
1530        // Call fs.write to write content to the file
1531        buffer_file.seek(SeekFrom::Start(0)).unwrap();
1532        let write_sz = fs
1533            .write(
1534                &ctx,
1535                ino,
1536                0,
1537                &mut buffer_file,
1538                data.len() as u32,
1539                0,
1540                None,
1541                false,
1542                0,
1543                0,
1544            )
1545            .unwrap();
1546        assert_eq!(write_sz, data.len());
1547
1548        // Create a new temp file as read buffer.
1549        let read_buffer_file = TempFile::new().expect("Cannot create temporary file.");
1550        let mut read_buffer_file = read_buffer_file.into_file();
1551        let read_sz = fs
1552            .read(
1553                &ctx,
1554                ino,
1555                0,
1556                &mut read_buffer_file,
1557                data.len() as u32,
1558                0,
1559                None,
1560                0,
1561            )
1562            .unwrap();
1563        assert_eq!(read_sz, data.len());
1564
1565        read_buffer_file.seek(SeekFrom::Start(0)).unwrap();
1566        let mut newbuf = Vec::new();
1567        read_buffer_file.read_to_end(&mut newbuf).unwrap();
1568        assert_eq!(newbuf, data);
1569    }
1570}