From 0eb26a0d7598ffe097950950341cf2ba57390ffe Mon Sep 17 00:00:00 2001 From: akitaSummer <644171127@qq.com> Date: Fri, 12 Jan 2024 00:49:25 +0800 Subject: [PATCH 1/8] feat: init macos passthrough Signed-off-by: akitaSummer <644171127@qq.com> --- Cargo.toml | 2 + src/lib.rs | 2 +- src/passthrough/inode_store.rs | 102 +++++- .../{sync_io.rs => linux_sync_io.rs} | 0 src/passthrough/macos_sync_io.rs | 41 +++ src/passthrough/mod.rs | 298 +++++++++++++++++- src/passthrough/stat.rs | 42 +++ src/passthrough/util.rs | 37 ++- 8 files changed, 500 insertions(+), 24 deletions(-) rename src/passthrough/{sync_io.rs => linux_sync_io.rs} (100%) create mode 100644 src/passthrough/macos_sync_io.rs create mode 100644 src/passthrough/stat.rs diff --git a/Cargo.toml b/Cargo.toml index 34eb3d4b3..cd5ed3a1a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,8 @@ tokio-uring = { version = "0.4.0", optional = true } tokio-test = "0.4.2" vmm-sys-util = "0.11" vm-memory = { version = "0.10", features = ["backend-mmap", "backend-bitmap"] } +[target.'cfg(target_os = "macos")'.dev-dependencies] +tempfile = "3.2.0" [features] default = ["fusedev"] diff --git a/src/lib.rs b/src/lib.rs index b8756920a..00ac1f2d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,7 @@ pub type Result = ::std::result::Result; pub mod abi; pub mod api; -#[cfg(all(any(feature = "fusedev", feature = "virtiofs"), target_os = "linux"))] +// #[cfg(all(any(feature = "fusedev", feature = "virtiofs"), target_os = "linux"))] pub mod passthrough; pub mod transport; diff --git a/src/passthrough/inode_store.rs b/src/passthrough/inode_store.rs index 1cfeab40a..c9a036646 100644 --- a/src/passthrough/inode_store.rs +++ b/src/passthrough/inode_store.rs @@ -4,19 +4,25 @@ use std::collections::BTreeMap; use std::sync::Arc; +#[cfg(target_os = "linux")] use super::file_handle::FileHandle; +#[cfg(target_os = "macos")] +use super::stat::Stat; +#[cfg(target_os = "linux")] use super::statx::StatExt; -use super::{Inode, InodeData, InodeHandle}; +use super::{InoT, Inode, InodeData, InodeHandle}; #[derive(Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq, Debug)] /// Identify an inode in `PassthroughFs` by `InodeId`. pub struct InodeId { - pub ino: libc::ino64_t, + pub ino: InoT, pub dev: libc::dev_t, + #[cfg(target_os = "linux")] pub mnt: u64, } impl InodeId { + #[cfg(target_os = "linux")] #[inline] pub(super) fn from_stat(st: &StatExt) -> Self { InodeId { @@ -25,12 +31,22 @@ impl InodeId { mnt: st.mnt_id, } } + + #[cfg(target_os = "macos")] + #[inline] + pub(super) fn from_stat(st: &Stat) -> Self { + InodeId { + ino: st.st.st_ino, + dev: st.st.st_dev, + } + } } #[derive(Default)] pub struct InodeStore { data: BTreeMap>, by_id: BTreeMap, + #[cfg(target_os = "linux")] by_handle: BTreeMap, Inode>, } @@ -41,6 +57,7 @@ impl InodeStore { /// will get lost. pub fn insert(&mut self, data: Arc) { self.by_id.insert(data.id, data.inode); + #[cfg(target_os = "linux")] if let InodeHandle::Handle(handle) = &data.handle { self.by_handle .insert(handle.file_handle().clone(), data.inode); @@ -59,6 +76,7 @@ impl InodeStore { } if let Some(data) = data.as_ref() { + #[cfg(target_os = "linux")] if let InodeHandle::Handle(handle) = &data.handle { self.by_handle.remove(handle.file_handle()); } @@ -69,6 +87,7 @@ impl InodeStore { pub fn clear(&mut self) { self.data.clear(); + #[cfg(target_os = "linux")] self.by_handle.clear(); self.by_id.clear(); } @@ -82,6 +101,7 @@ impl InodeStore { self.get(inode) } + #[cfg(target_os = "linux")] pub fn get_by_handle(&self, handle: &FileHandle) -> Option<&Arc> { let inode = self.inode_by_handle(handle)?; self.get(inode) @@ -91,6 +111,7 @@ impl InodeStore { self.by_id.get(id) } + #[cfg(target_os = "linux")] pub fn inode_by_handle(&self, handle: &FileHandle) -> Option<&Inode> { self.by_handle.get(handle) } @@ -105,8 +126,13 @@ mod test { use std::mem::MaybeUninit; use std::os::unix::io::AsRawFd; use std::sync::atomic::Ordering; + #[cfg(target_os = "macos")] + use tempfile::Builder; + #[cfg(target_os = "linux")] use vmm_sys_util::tempfile::TempFile; + use stat::stat; + impl PartialEq for InodeData { fn eq(&self, other: &Self) -> bool { if self.inode != other.inode @@ -117,6 +143,7 @@ mod test { return false; } + #[cfg(target_os = "linux")] match (&self.handle, &other.handle) { (InodeHandle::File(f1), InodeHandle::File(f2)) => f1.as_raw_fd() == f2.as_raw_fd(), (InodeHandle::Handle(h1), InodeHandle::Handle(h2)) => { @@ -124,9 +151,18 @@ mod test { } _ => false, } + + #[cfg(target_os = "macos")] + match (&self.handle, &other.handle) { + (InodeHandle::File(f1, _), InodeHandle::File(f2, _)) => { + f1.as_raw_fd() == f2.as_raw_fd() + } + _ => false, + } } } + #[cfg(target_os = "linux")] fn stat_fd(fd: &impl AsRawFd) -> io::Result { let mut st = MaybeUninit::::zeroed(); let null_path = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") }; @@ -148,6 +184,7 @@ mod test { } } + #[cfg(target_os = "linux")] #[test] fn test_inode_store() { let mut m = InodeStore::default(); @@ -214,4 +251,65 @@ mod test { assert!(m.get(&inode2).is_none()); assert!(m.get_by_id(&id2).is_none()); } + + #[cfg(target_os = "macos")] + #[test] + fn test_inode_store() { + let mut m = InodeStore::default(); + let tmpfile1 = Builder::new().tempfile().unwrap(); + let tmpfile2 = Builder::new().tempfile().unwrap(); + + let inode1: Inode = 3; + let inode2: Inode = 4; + let inode_stat1 = stat(tmpfile1.as_file()).unwrap(); + let inode_stat2 = stat(tmpfile2.as_file()).unwrap(); + let id1 = InodeId::from_stat(&inode_stat1); + let id2 = InodeId::from_stat(&inode_stat2); + let cstr1 = CString::new(tmpfile1.path().to_string_lossy().to_string()).unwrap(); + let cstr2 = CString::new(tmpfile2.path().to_string_lossy().to_string()).unwrap(); + let file_or_handle1 = InodeHandle::File(tmpfile1.into_file(), cstr1); + let file_or_handle2 = InodeHandle::File(tmpfile2.into_file(), cstr2); + let data1 = InodeData::new(inode1, file_or_handle1, 2, id1, inode_stat1.st.st_mode); + let data2 = InodeData::new(inode2, file_or_handle2, 2, id2, inode_stat2.st.st_mode); + let data1 = Arc::new(data1); + let data2 = Arc::new(data2); + + m.insert(data1.clone()); + + // get not present key, expect none + assert!(m.get(&1).is_none()); + + // get just inserted value by key, by id, by handle + assert!(m.get_by_id(&InodeId::default()).is_none()); + assert_eq!(m.get(&inode1).unwrap(), &data1); + assert_eq!(m.get_by_id(&id1).unwrap(), &data1); + + // insert another value, and check again + m.insert(data2.clone()); + assert!(m.get(&1).is_none()); + assert!(m.get_by_id(&InodeId::default()).is_none()); + assert_eq!(m.get(&inode1).unwrap(), &data1); + assert_eq!(m.get_by_id(&id1).unwrap(), &data1); + assert_eq!(m.get(&inode2).unwrap(), &data2); + assert_eq!(m.get_by_id(&id2).unwrap(), &data2); + + // remove non-present key + assert!(m.remove(&1, false).is_none()); + + // remove present key, return its value + assert_eq!(m.remove(&inode1, false).unwrap(), data1.clone()); + assert!(m.get(&inode1).is_none()); + assert!(m.get_by_id(&id1).is_none()); + assert_eq!(m.get(&inode2).unwrap(), &data2); + assert_eq!(m.get_by_id(&id2).unwrap(), &data2); + + // clear the map + m.clear(); + assert!(m.get(&1).is_none()); + assert!(m.get_by_id(&InodeId::default()).is_none()); + assert!(m.get(&inode1).is_none()); + assert!(m.get_by_id(&id1).is_none()); + assert!(m.get(&inode2).is_none()); + assert!(m.get_by_id(&id2).is_none()); + } } diff --git a/src/passthrough/sync_io.rs b/src/passthrough/linux_sync_io.rs similarity index 100% rename from src/passthrough/sync_io.rs rename to src/passthrough/linux_sync_io.rs diff --git a/src/passthrough/macos_sync_io.rs b/src/passthrough/macos_sync_io.rs new file mode 100644 index 000000000..174829bdd --- /dev/null +++ b/src/passthrough/macos_sync_io.rs @@ -0,0 +1,41 @@ +// Copyright (C) 2020 Alibaba Cloud. All rights reserved. +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. + +//! Fuse passthrough file system, mirroring an existing FS hierarchy. + +use std::ffi::{CStr, CString}; +use std::fs::File; +use std::io; +use std::mem::{self, size_of, ManuallyDrop, MaybeUninit}; +use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::time::Duration; + +use super::*; +use crate::abi::fuse_abi::{CreateIn, Opcode, FOPEN_IN_KILL_SUIDGID}; +#[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] +use crate::abi::virtio_fs; +use crate::api::filesystem::{ + Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions, + SetattrValid, ZeroCopyReader, ZeroCopyWriter, +}; +use crate::bytes_to_cstr; +#[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] +use crate::transport::FsCacheReqHandler; + +impl PassthroughFs {} + +impl FileSystem for PassthroughFs { + type Inode = Inode; + type Handle = Handle; + + fn lookup(&self, _ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result { + if name.to_bytes_with_nul().contains(&SLASH_ASCII) { + return Err(einval()); + } + self.do_lookup(parent, name) + } +} diff --git a/src/passthrough/mod.rs b/src/passthrough/mod.rs index 32efee1f7..4068072b7 100644 --- a/src/passthrough/mod.rs +++ b/src/passthrough/mod.rs @@ -29,14 +29,20 @@ use std::time::Duration; use vm_memory::{bitmap::BitmapSlice, ByteValued}; pub use self::config::{CachePolicy, Config}; +#[cfg(target_os = "linux")] use self::file_handle::{FileHandle, OpenableFileHandle}; use self::inode_store::{InodeId, InodeStore}; +#[cfg(target_os = "linux")] use self::mount_fd::MountFds; + +use self::stat::{open, stat, Stat}; +#[cfg(target_os = "linux")] use self::statx::{statx, StatExt}; use self::util::{ - ebadf, einval, enosys, eperm, is_dir, is_safe_inode, openat, reopen_fd_through_proc, stat_fd, - UniqueInodeGenerator, + ebadf, einval, enosys, eperm, is_dir, is_safe_inode, openat, UniqueInodeGenerator, }; +#[cfg(target_os = "linux")] +use self::util::{reopen_fd_through_proc, stat_fd}; use crate::abi::fuse_abi as fuse; use crate::abi::fuse_abi::Opcode; use crate::api::filesystem::Entry; @@ -48,17 +54,41 @@ use crate::api::{ #[cfg(feature = "async-io")] mod async_io; mod config; +#[cfg(target_os = "linux")] mod file_handle; mod inode_store; +#[cfg(target_os = "linux")] +mod linux_sync_io; +#[cfg(target_os = "macos")] +mod macos_sync_io; +#[cfg(target_os = "linux")] mod mount_fd; +#[cfg(target_os = "linux")] mod os_compat; +#[cfg(target_os = "macos")] +mod stat; +#[cfg(target_os = "linux")] mod statx; -mod sync_io; mod util; type Inode = u64; type Handle = u64; +#[cfg(target_os = "linux")] +type InoT = libc::ino64_t; +#[cfg(target_os = "macos")] +type InoT = libc::ino_t; + +#[cfg(target_os = "linux")] +type InodeMode = u32; +#[cfg(target_os = "macos")] +type InodeMode = u16; + +#[cfg(target_os = "linux")] +type LibCStat = libc::stat64; +#[cfg(target_os = "macos")] +type LibCStat = libc::stat; + /// Maximum host inode number supported by passthroughfs const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff; @@ -96,12 +126,20 @@ impl AsFd for InodeFile<'_> { } #[derive(Debug)] +#[cfg(target_os = "linux")] enum InodeHandle { File(File), Handle(Arc), } +#[derive(Debug)] +#[cfg(target_os = "macos")] +enum InodeHandle { + File(File, CString), +} + impl InodeHandle { + #[cfg(target_os = "linux")] fn file_handle(&self) -> Option<&FileHandle> { match self { InodeHandle::File(_) => None, @@ -110,6 +148,7 @@ impl InodeHandle { } fn get_file(&self) -> io::Result> { + #[cfg(target_os = "linux")] match self { InodeHandle::File(f) => Ok(InodeFile::Ref(f)), InodeHandle::Handle(h) => { @@ -117,16 +156,30 @@ impl InodeHandle { Ok(InodeFile::Owned(f)) } } + #[cfg(target_os = "macos")] + match self { + InodeHandle::File(f, _) => Ok(InodeFile::Ref(f)), + } } - fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result { + fn open_file( + &self, + flags: libc::c_int, + #[cfg(target_os = "linux")] proc_self_fd: &File, + ) -> io::Result { + #[cfg(target_os = "linux")] match self { InodeHandle::File(f) => reopen_fd_through_proc(f, flags, proc_self_fd), InodeHandle::Handle(h) => h.open(flags), } + #[cfg(target_os = "macos")] + match self { + InodeHandle::File(_, pathname) => open(pathname, flags, 0), + } } - fn stat(&self) -> io::Result { + #[cfg(target_os = "linux")] + fn stat(&self) -> io::Result { match self { InodeHandle::File(f) => stat_fd(f, None), InodeHandle::Handle(_h) => { @@ -135,6 +188,20 @@ impl InodeHandle { } } } + + #[cfg(target_os = "macos")] + fn stat(&self) -> io::Result { + match self { + InodeHandle::File(f, _) => stat(f), + } + } + + #[cfg(target_os = "macos")] + fn get_path(&self) -> io::Result { + match self { + InodeHandle::File(_, pathname) => Ok(pathname.clone()), + } + } } /// Represents an inode in `PassthroughFs`. @@ -146,11 +213,11 @@ pub struct InodeData { id: InodeId, refcount: AtomicU64, // File type and mode - mode: u32, + mode: InodeMode, } impl InodeData { - fn new(inode: Inode, f: InodeHandle, refcount: u64, id: InodeId, mode: u32) -> Self { + fn new(inode: Inode, f: InodeHandle, refcount: u64, id: InodeId, mode: InodeMode) -> Self { InodeData { inode, handle: f, @@ -164,9 +231,20 @@ impl InodeData { self.handle.get_file() } + #[cfg(target_os = "linux")] fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result { self.handle.open_file(flags, proc_self_fd) } + + #[cfg(target_os = "macos")] + fn open_file(&self, flags: libc::c_int) -> io::Result { + self.handle.open_file(flags) + } + + #[cfg(target_os = "macos")] + fn get_path(&self) -> io::Result { + self.handle.get_path() + } } /// Data structures to manage accessed inodes. @@ -196,6 +274,7 @@ impl InodeMap { .ok_or_else(ebadf) } + #[cfg(target_os = "linux")] fn get_inode_locked( inodes: &InodeStore, id: &InodeId, @@ -207,6 +286,12 @@ impl InodeMap { } } + #[cfg(target_os = "macos")] + fn get_inode_locked(inodes: &InodeStore, id: &InodeId) -> Option { + inodes.inode_by_id(id).copied() + } + + #[cfg(target_os = "linux")] fn get_alt(&self, id: &InodeId, handle: Option<&FileHandle>) -> Option> { // Do not expect poisoned lock here, so safe to unwrap(). let inodes = self.inodes.read().unwrap(); @@ -214,6 +299,14 @@ impl InodeMap { Self::get_alt_locked(inodes.deref(), id, handle) } + #[cfg(target_os = "macos")] + fn get_alt(&self, id: &InodeId) -> Option> { + let inodes = self.inodes.read().unwrap(); + + Self::get_alt_locked(inodes.deref(), id) + } + + #[cfg(target_os = "linux")] fn get_alt_locked( inodes: &InodeStore, id: &InodeId, @@ -237,6 +330,11 @@ impl InodeMap { .map(Arc::clone) } + #[cfg(target_os = "macos")] + fn get_alt_locked(inodes: &InodeStore, id: &InodeId) -> Option> { + inodes.get_by_id(id).map(Arc::clone) + } + fn get_map_mut(&self) -> RwLockWriteGuard { // Do not expect poisoned lock here, so safe to unwrap(). self.inodes.write().unwrap() @@ -362,13 +460,16 @@ pub struct PassthroughFs { // Use to generate unique inode ino_allocator: UniqueInodeGenerator, + // Maps mount IDs to an open FD on the respective ID for the purpose of open_by_handle_at(). + #[cfg(target_os = "linux")] mount_fds: MountFds, // File descriptor pointing to the `/proc/self/fd` directory. This is used to convert an fd from // `inodes` into one that can go into `handles`. This is accomplished by reading the // `/proc/self/fd/{}` symlink. We keep an open fd here in case the file system tree that we are meant // to be serving doesn't have access to `/proc/self/fd`. + #[cfg(target_os = "linux")] proc_self_fd: File, // Whether writeback caching is enabled for this directory. This will only be true when @@ -417,7 +518,9 @@ impl PassthroughFs { } // Safe because this is a constant value and a valid C string. + #[cfg(target_os = "linux")] let proc_self_fd_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_SELF_FD_CSTR) }; + #[cfg(target_os = "linux")] let proc_self_fd = Self::open_file( &libc::AT_FDCWD, proc_self_fd_cstr, @@ -433,6 +536,7 @@ impl PassthroughFs { (None, None) => (cfg.entry_timeout, cfg.attr_timeout), }; + #[cfg(target_os = "linux")] let mount_fds = MountFds::new(None)?; Ok(PassthroughFs { @@ -443,7 +547,9 @@ impl PassthroughFs { handle_map: HandleMap::new(), next_handle: AtomicU64::new(1), + #[cfg(target_os = "linux")] mount_fds, + #[cfg(target_os = "linux")] proc_self_fd, writeback: AtomicBool::new(false), @@ -465,18 +571,28 @@ impl PassthroughFs { pub fn import(&self) -> io::Result<()> { let root = CString::new(self.cfg.root_dir.as_str()).expect("CString::new failed"); + #[cfg(target_os = "linux")] let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &libc::AT_FDCWD, &root) .map_err(|e| { error!("fuse: import: failed to get file or handle: {:?}", e); e })?; + + #[cfg(target_os = "macos")] + let (path_fd, st) = self.open_file(&root).unwrap(); + let id = InodeId::from_stat(&st); + + #[cfg(target_os = "linux")] let handle = if let Some(h) = handle_opt { InodeHandle::Handle(self.to_openable_handle(h)?) } else { InodeHandle::File(path_fd) }; + #[cfg(target_os = "macos")] + let handle = InodeHandle::File(path_fd, root); + // Safe because this doesn't modify any memory and there is no need to check the return // value because this system call always succeeds. We need to clear the umask here because // we want the client to be able to set all the bits in the mode. @@ -495,10 +611,12 @@ impl PassthroughFs { } /// Get the list of file descriptors which should be reserved across live upgrade. + #[cfg(target_os = "linux")] pub fn keep_fds(&self) -> Vec { vec![self.proc_self_fd.as_raw_fd()] } + #[cfg(target_os = "linux")] fn readlinkat(dfd: i32, pathname: &CStr) -> io::Result { let mut buf = Vec::with_capacity(libc::PATH_MAX as usize); @@ -528,6 +646,7 @@ impl PassthroughFs { /// Get the file pathname corresponding to the Inode /// This function is used by Nydus blobfs + #[cfg(target_os = "linux")] pub fn readlinkat_proc_file(&self, inode: Inode) -> io::Result { let data = self.inode_map.get(inode)?; let file = data.get_file()?; @@ -557,11 +676,20 @@ impl PassthroughFs { } } } - + #[cfg(target_os = "linux")] fn open_file(dfd: &impl AsRawFd, pathname: &CStr, flags: i32, mode: u32) -> io::Result { openat(dfd, pathname, flags, mode) } + #[cfg(target_os = "macos")] + fn open_file(&self, pathname: &CStr) -> io::Result<(File, Stat)> { + let path_file = self.open_file_restricted(pathname, libc::O_NOFOLLOW, 0)?; + let st = stat(&path_file)?; + + Ok((path_file, st)) + } + + #[cfg(target_os = "linux")] fn open_file_restricted( &self, dir: &impl AsRawFd, @@ -579,7 +707,14 @@ impl PassthroughFs { //} } + #[cfg(target_os = "macos")] + fn open_file_restricted(&self, pathname: &CStr, flags: i32, mode: u32) -> io::Result { + let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags; + open(pathname, flags, mode) + } + /// Create a File or File Handle for `name` under directory `dir_fd` to support `lookup()`. + #[cfg(target_os = "linux")] fn open_file_and_handle( &self, dir: &impl AsRawFd, @@ -596,6 +731,7 @@ impl PassthroughFs { Ok((path_file, handle, st)) } + #[cfg(target_os = "linux")] fn to_openable_handle(&self, fh: FileHandle) -> io::Result> { fh.into_openable(&self.mount_fds, |fd, flags, _mode| { reopen_fd_through_proc(&fd, flags, &self.proc_self_fd) @@ -613,20 +749,30 @@ impl PassthroughFs { &self, inodes: &InodeStore, id: &InodeId, - handle_opt: Option<&FileHandle>, + #[cfg(target_os = "linux")] handle_opt: Option<&FileHandle>, ) -> io::Result { if !self.cfg.use_host_ino { // If the inode has already been assigned before, the new inode is not reassigned, // ensuring that the same file is always the same inode - Ok(InodeMap::get_inode_locked(inodes, id, handle_opt) - .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))) + #[cfg(target_os = "linux")] + return Ok(InodeMap::get_inode_locked(inodes, id, handle_opt) + .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))); + #[cfg(target_os = "macos")] + return Ok(InodeMap::get_inode_locked(inodes, id) + .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))); } else { let inode = if id.ino > MAX_HOST_INO { // Prefer looking for previous mappings from memory + #[cfg(target_os = "linux")] match InodeMap::get_inode_locked(inodes, id, handle_opt) { Some(ino) => ino, None => self.ino_allocator.get_unique_inode(id)?, } + #[cfg(target_os = "macos")] + match InodeMap::get_inode_locked(inodes, id) { + Some(ino) => ino, + None => self.ino_allocator.get_unique_inode(id)?, + } } else { self.ino_allocator.get_unique_inode(id)? }; @@ -645,13 +791,34 @@ impl PassthroughFs { }; let dir = self.inode_map.get(parent)?; - let dir_file = dir.get_file()?; - let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &dir_file, name)?; + + #[cfg(target_os = "linux")] + let (path_fd, handle_opt, st) = { + let dir_file = dir.get_file()?; + Self::open_file_and_handle(self, &dir_file, name)? + }; + + #[cfg(target_os = "macos")] + let (path_fd, st, cstring_path) = { + let string_from_name: String = name.to_string_lossy().to_string(); + let dir_path = dir.get_path()?.into_string().unwrap(); + let mut full_path = PathBuf::from(dir_path); + full_path.push(string_from_name); + let string_path = full_path.to_string_lossy().to_string(); + let cstring_path = CString::new(string_path).expect("Failed to convert to CString"); + let (path_fd, st) = self.open_file(&cstring_path)?; + (path_fd, st, cstring_path) + }; + let id = InodeId::from_stat(&st); let mut found = None; 'search: loop { - match self.inode_map.get_alt(&id, handle_opt.as_ref()) { + match self.inode_map.get_alt( + &id, + #[cfg(target_os = "linux")] + handle_opt.as_ref(), + ) { // No existing entry found None => break 'search, Some(data) => { @@ -680,12 +847,16 @@ impl PassthroughFs { let inode = if let Some(v) = found { v } else { + #[cfg(target_os = "linux")] let handle = if let Some(h) = handle_opt.clone() { InodeHandle::Handle(self.to_openable_handle(h)?) } else { InodeHandle::File(path_fd) }; + #[cfg(target_os = "macos")] + let handle = InodeHandle::File(path_fd, cstring_path); + // Write guard get_alt_locked() and insert_lock() to avoid race conditions. let mut inodes = self.inode_map.get_map_mut(); @@ -693,7 +864,12 @@ impl PassthroughFs { // racing thread already added an inode with the same id while we're not holding // the lock. If so just use the newly added inode, otherwise the inode will be replaced // and results in EBADF. - match InodeMap::get_alt_locked(inodes.deref(), &id, handle_opt.as_ref()) { + match InodeMap::get_alt_locked( + inodes.deref(), + &id, + #[cfg(target_os = "linux")] + handle_opt.as_ref(), + ) { Some(data) => { // An inode was added concurrently while we did not hold a lock on // `self.inodes_map`, so we use that instead. `handle` will be dropped. @@ -701,7 +877,12 @@ impl PassthroughFs { data.inode } None => { - let inode = self.allocate_inode(inodes.deref(), &id, handle_opt.as_ref())?; + let inode = self.allocate_inode( + inodes.deref(), + &id, + #[cfg(target_os = "linux")] + handle_opt.as_ref(), + )?; if inode > VFS_MAX_INO { error!("fuse: max inode number reached: {}", VFS_MAX_INO); @@ -825,6 +1006,7 @@ impl PassthroughFs { } } + #[cfg(target_os = "linux")] Opcode::Fallocate => { let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE); match op { @@ -888,6 +1070,7 @@ impl BackendFileSystem for PassthroughFs } } +#[cfg(target_os = "linux")] macro_rules! scoped_cred { ($name:ident, $ty:ty, $syscall_nr:expr) => { #[derive(Debug)] @@ -938,9 +1121,57 @@ macro_rules! scoped_cred { } }; } + +#[cfg(target_os = "linux")] scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid); +#[cfg(target_os = "linux")] scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid); +#[cfg(target_os = "macos")] +macro_rules! scoped_cred { + ($name:ident, $ty:ty, $set_func:path) => { + #[derive(Debug)] + pub(crate) struct $name; + + impl $name { + fn new(val: $ty) -> io::Result> { + if val == 0 { + return Ok(None); + } + + let current_val = unsafe { $set_func(0) }; + + if current_val == -1 { + return Err(io::Error::last_os_error()); + } + + let res = unsafe { $set_func(val) }; + if res == 0 { + Ok(Some($name)) + } else { + unsafe { $set_func(current_val as u32) }; + return Err(io::Error::last_os_error()); + } + } + } + + impl Drop for $name { + fn drop(&mut self) { + let res = unsafe { $set_func(0) }; + error!( + "fuse: failed to change credentials back to root: {}", + io::Error::last_os_error(), + ); + } + } + }; +} + +#[cfg(target_os = "macos")] +scoped_cred!(ScopedUid, libc::uid_t, libc::seteuid); +#[cfg(target_os = "macos")] +scoped_cred!(ScopedGid, libc::gid_t, libc::setegid); + fn set_creds( uid: libc::uid_t, gid: libc::gid_t, @@ -950,8 +1181,10 @@ fn set_creds( ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid))) } +#[cfg(target_os = "linux")] struct CapFsetid {} +#[cfg(target_os = "linux")] impl Drop for CapFsetid { fn drop(&mut self) { if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) { @@ -960,6 +1193,7 @@ impl Drop for CapFsetid { } } +#[cfg(target_os = "linux")] fn drop_cap_fsetid() -> io::Result> { if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))? @@ -975,6 +1209,7 @@ fn drop_cap_fsetid() -> io::Result> { Ok(Some(CapFsetid {})) } +#[cfg(target_os = "linux")] #[cfg(test)] mod tests { use super::*; @@ -1480,3 +1715,34 @@ mod tests { assert!(!fs.cfg.writeback); } } + +#[cfg(target_os = "macos")] +#[cfg(test)] +mod tests { + use fuse::ROOT_ID; + + use crate::api::filesystem::{Context, FileSystem}; + + use super::*; + + fn prepare_passthroughfs() -> PassthroughFs { + let fs_cfg = Config { + root_dir: std::env::temp_dir().into_os_string().into_string().unwrap(), + ..Default::default() + }; + let fs = PassthroughFs::<()>::new(fs_cfg).unwrap(); + fs.import().unwrap(); + + fs + } + + #[test] + fn test_lookup_escape_root() { + let fs = prepare_passthroughfs(); + let ctx = Context::default(); + + let name = CString::new("..").unwrap(); + let entry = fs.lookup(&ctx, ROOT_ID, &name).unwrap(); + assert_eq!(entry.inode, ROOT_ID); + } +} diff --git a/src/passthrough/stat.rs b/src/passthrough/stat.rs new file mode 100644 index 000000000..77a1db189 --- /dev/null +++ b/src/passthrough/stat.rs @@ -0,0 +1,42 @@ +use std::{ + ffi::{CStr, CString}, + fs::File, + io, + mem::MaybeUninit, + os::fd::{AsRawFd, FromRawFd}, +}; + +pub struct Stat { + pub st: libc::stat, +} + +pub fn stat(path_file: &impl AsRawFd) -> io::Result { + let mut st_ui = MaybeUninit::::zeroed(); + + let res = unsafe { libc::fstat(path_file.as_raw_fd(), st_ui.as_mut_ptr()) }; + + if res >= 0 { + let st = unsafe { st_ui.assume_init() }; + + Ok(Stat { st }) + } else { + Err(io::Error::last_os_error()) + } +} + +pub fn open(path: &CStr, flags: libc::c_int, mode: u32) -> io::Result { + let path_cstr = CString::new(path.to_bytes()).expect("CString conversion failed"); + let fd = unsafe { + if flags & libc::O_CREAT == libc::O_CREAT { + libc::open(path_cstr.as_ptr(), flags, mode) + } else { + libc::open(path_cstr.as_ptr(), flags) + } + }; + + if fd >= 0 { + Ok(unsafe { File::from_raw_fd(fd) }) + } else { + Err(io::Error::last_os_error()) + } +} diff --git a/src/passthrough/util.rs b/src/passthrough/util.rs index 08a31031f..7fdc36c79 100644 --- a/src/passthrough/util.rs +++ b/src/passthrough/util.rs @@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicU64, AtomicU8, Ordering}; use std::sync::Mutex; use super::inode_store::InodeId; -use super::MAX_HOST_INO; +use super::{InoT, InodeMode, MAX_HOST_INO}; use crate::abi::fuse_abi as fuse; use crate::api::EMPTY_CSTR; @@ -45,9 +45,12 @@ impl UniqueInodeGenerator { } } - pub fn get_unique_inode(&self, id: &InodeId) -> io::Result { + pub fn get_unique_inode(&self, id: &InodeId) -> io::Result { let unique_id = { + #[cfg(target_os = "linux")] let id: DevMntIDPair = DevMntIDPair(id.dev, id.mnt); + #[cfg(target_os = "macos")] + let id: DevMntIDPair = DevMntIDPair(id.dev, id.ino); let mut id_map_guard = self.dev_mntid_map.lock().unwrap(); match id_map_guard.entry(id) { btree_map::Entry::Occupied(v) => *v.get(), @@ -81,7 +84,7 @@ impl UniqueInodeGenerator { } #[cfg(test)] - fn decode_unique_inode(&self, inode: libc::ino64_t) -> io::Result { + fn decode_unique_inode(&self, inode: InoT) -> io::Result { if inode > crate::api::VFS_MAX_INO { return Err(io::Error::new( io::ErrorKind::InvalidInput, @@ -123,6 +126,7 @@ impl UniqueInodeGenerator { Ok(InodeId { ino: inode & MAX_HOST_INO, dev, + #[cfg(target_os = "linux")] mnt, }) } @@ -158,6 +162,7 @@ pub fn openat( /// Open `/proc/self/fd/{fd}` with the given flags to effectively duplicate the given `fd` with new /// flags (e.g. to turn an `O_PATH` file descriptor into one that can be used for I/O). +#[cfg(target_os = "linux")] pub fn reopen_fd_through_proc( fd: &impl AsRawFd, flags: libc::c_int, @@ -174,6 +179,7 @@ pub fn reopen_fd_through_proc( ) } +#[cfg(target_os = "linux")] pub fn stat_fd(dir: &impl AsRawFd, path: Option<&CStr>) -> io::Result { // Safe because this is a constant value and a valid C string. let pathname = @@ -198,14 +204,14 @@ pub fn stat_fd(dir: &impl AsRawFd, path: Option<&CStr>) -> io::Result bool { +pub fn is_safe_inode(mode: InodeMode) -> bool { // Only regular files and directories are considered safe to be opened from the file // server without O_PATH. matches!(mode & libc::S_IFMT, libc::S_IFREG | libc::S_IFDIR) } /// Returns true if the mode is for a directory. -pub fn is_dir(mode: u32) -> bool { +pub fn is_dir(mode: InodeMode) -> bool { (mode & libc::S_IFMT) == libc::S_IFDIR } @@ -271,6 +277,7 @@ mod tests { let inode_alt_key = InodeId { ino: 1, dev: 0, + #[cfg(target_os = "linux")] mnt: 0, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -284,6 +291,7 @@ mod tests { let inode_alt_key = InodeId { ino: 1, dev: 0, + #[cfg(target_os = "linux")] mnt: 1, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -297,6 +305,7 @@ mod tests { let inode_alt_key = InodeId { ino: 2, dev: 0, + #[cfg(target_os = "linux")] mnt: 1, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -310,6 +319,7 @@ mod tests { let inode_alt_key = InodeId { ino: MAX_HOST_INO, dev: 0, + #[cfg(target_os = "linux")] mnt: 1, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -326,7 +336,11 @@ mod tests { let generator = UniqueInodeGenerator::new(); let inode_alt_key = InodeId { ino: MAX_HOST_INO + 1, + #[cfg(target_os = "macos")] + dev: i32::MAX, + #[cfg(target_os = "linux")] dev: u64::MAX, + #[cfg(target_os = "linux")] mnt: u64::MAX, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -337,7 +351,11 @@ mod tests { let inode_alt_key = InodeId { ino: MAX_HOST_INO + 2, + #[cfg(target_os = "macos")] + dev: i32::MAX, + #[cfg(target_os = "linux")] dev: u64::MAX, + #[cfg(target_os = "linux")] mnt: u64::MAX, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -348,7 +366,11 @@ mod tests { let inode_alt_key = InodeId { ino: MAX_HOST_INO + 3, + #[cfg(target_os = "macos")] + dev: i32::MAX, + #[cfg(target_os = "linux")] dev: u64::MAX, + #[cfg(target_os = "linux")] mnt: 0, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -359,7 +381,11 @@ mod tests { let inode_alt_key = InodeId { ino: u64::MAX, + #[cfg(target_os = "macos")] + dev: i32::MAX, + #[cfg(target_os = "linux")] dev: u64::MAX, + #[cfg(target_os = "linux")] mnt: u64::MAX, }; let unique_inode = generator.get_unique_inode(&inode_alt_key).unwrap(); @@ -371,6 +397,7 @@ mod tests { } #[test] + #[cfg(target_os = "linux")] fn test_stat_fd() { let topdir = env!("CARGO_MANIFEST_DIR"); let dir = File::open(topdir).unwrap(); From 5101a36449cad5e241e84efd11f1c9ff8c0a23f0 Mon Sep 17 00:00:00 2001 From: akitaSummer <644171127@qq.com> Date: Sat, 13 Jan 2024 02:57:08 +0800 Subject: [PATCH 2/8] feat: add mac mod ut Signed-off-by: akitaSummer <644171127@qq.com> --- src/passthrough/macos_sync_io.rs | 158 +++++++++++++- src/passthrough/mod.rs | 345 +++++++++++++++++++++++-------- src/passthrough/stat.rs | 2 - 3 files changed, 417 insertions(+), 88 deletions(-) diff --git a/src/passthrough/macos_sync_io.rs b/src/passthrough/macos_sync_io.rs index 174829bdd..fc4951e11 100644 --- a/src/passthrough/macos_sync_io.rs +++ b/src/passthrough/macos_sync_io.rs @@ -26,16 +26,172 @@ use crate::bytes_to_cstr; #[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] use crate::transport::FsCacheReqHandler; -impl PassthroughFs {} +impl PassthroughFs { + fn open_inode(&self, inode: Inode, flags: i32) -> io::Result { + let data = self.inode_map.get(inode)?; + if !is_safe_inode(data.mode) { + Err(ebadf()) + } else { + let new_flags = self.get_writeback_open_flags(flags); + data.open_file(new_flags | libc::O_CLOEXEC) + } + } + + fn do_open( + &self, + inode: Inode, + flags: u32, + fuse_flags: u32, + ) -> io::Result<(Option, OpenOptions, Option)> { + let file = self.open_inode(inode, flags as i32)?; + + let data = HandleData::new(inode, file, flags); + let handle = self.next_handle.fetch_add(1, Ordering::Relaxed); + self.handle_map.insert(handle, data); + + let mut opts = OpenOptions::empty(); + match self.cfg.cache_policy { + // We only set the direct I/O option on files. + CachePolicy::Never => opts.set( + OpenOptions::DIRECT_IO, + flags & (libc::O_DIRECTORY as u32) == 0, + ), + CachePolicy::Metadata => { + if flags & (libc::O_DIRECTORY as u32) == 0 { + opts |= OpenOptions::DIRECT_IO; + } else { + opts |= OpenOptions::KEEP_CACHE; + } + } + CachePolicy::Always => { + opts |= OpenOptions::KEEP_CACHE; + } + _ => {} + }; + + Ok((Some(handle), opts, None)) + } + + fn do_getattr(&self, inode: Inode, handle: Option) -> io::Result<(LibCStat, Duration)> { + let st; + let data = self.inode_map.get(inode).map_err(|e| { + error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); + e + })?; + + // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle + // this case correctly. + if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { + // Safe as we just checked handle + let hd = self.handle_map.get(handle.unwrap(), inode)?; + st = stat(hd.get_file()); + } else { + st = data.handle.stat(); + } + + let st = st.map_err(|e| { + error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); + e + })?; + + Ok((st.st, self.cfg.attr_timeout)) + } +} impl FileSystem for PassthroughFs { type Inode = Inode; type Handle = Handle; + fn init(&self, capable: FsOptions) -> io::Result { + if self.cfg.do_import { + self.import()?; + } + + let mut opts = FsOptions::FILE_OPS; + + Ok(opts) + } + fn lookup(&self, _ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result { if name.to_bytes_with_nul().contains(&SLASH_ASCII) { return Err(einval()); } self.do_lookup(parent, name) } + + fn open( + &self, + _ctx: &Context, + inode: Inode, + flags: u32, + fuse_flags: u32, + ) -> io::Result<(Option, OpenOptions, Option)> { + if self.no_open.load(Ordering::Relaxed) { + info!("fuse: open is not supported."); + Err(enosys()) + } else { + self.do_open(inode, flags, fuse_flags) + } + } + + fn create( + &self, + ctx: &Context, + parent: Inode, + name: &CStr, + args: CreateIn, + ) -> io::Result<(Entry, Option, OpenOptions, Option)> { + self.validate_path_component(name)?; + + let dir = self.inode_map.get(parent)?; + let dir_file = dir.get_file()?; + + let new_file = { + let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; + + let flags = self.get_writeback_open_flags(args.flags as i32); + Self::create_file_excl(&dir_file, name, flags, args.mode & !(args.umask & 0o777))? + }; + + let entry = self.do_lookup(parent, name)?; + let file = match new_file { + // File didn't exist, now created by create_file_excl() + Some(f) => f, + // File exists, and args.flags doesn't contain O_EXCL. Now let's open it with + // open_inode(). + None => { + let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; + self.open_inode(entry.inode, args.flags as i32)? + } + }; + + let ret_handle = if !self.no_open.load(Ordering::Relaxed) { + let handle = self.next_handle.fetch_add(1, Ordering::Relaxed); + let data = HandleData::new(entry.inode, file, args.flags); + + self.handle_map.insert(handle, data); + Some(handle) + } else { + None + }; + + let mut opts = OpenOptions::empty(); + match self.cfg.cache_policy { + CachePolicy::Never => opts |= OpenOptions::DIRECT_IO, + CachePolicy::Metadata => opts |= OpenOptions::DIRECT_IO, + CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE, + _ => {} + }; + + Ok((entry, ret_handle, opts, None)) + } + + fn getattr( + &self, + _ctx: &Context, + inode: Inode, + handle: Option, + ) -> io::Result<(LibCStat, Duration)> { + self.do_getattr(inode, handle) + } } diff --git a/src/passthrough/mod.rs b/src/passthrough/mod.rs index 4068072b7..d36d8af95 100644 --- a/src/passthrough/mod.rs +++ b/src/passthrough/mod.rs @@ -683,7 +683,7 @@ impl PassthroughFs { #[cfg(target_os = "macos")] fn open_file(&self, pathname: &CStr) -> io::Result<(File, Stat)> { - let path_file = self.open_file_restricted(pathname, libc::O_NOFOLLOW, 0)?; + let path_file = self.open_file_restricted(pathname, libc::O_NOFOLLOW, 0o40777)?; let st = stat(&path_file)?; Ok((path_file, st)) @@ -1070,7 +1070,6 @@ impl BackendFileSystem for PassthroughFs } } -#[cfg(target_os = "linux")] macro_rules! scoped_cred { ($name:ident, $ty:ty, $syscall_nr:expr) => { #[derive(Debug)] @@ -1099,7 +1098,10 @@ macro_rules! scoped_cred { // This call is safe because it doesn't modify any memory and we // check the return value. + #[cfg(target_os = "linux")] let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) }; + #[cfg(target_os = "macos")] + let res = unsafe { $syscall_nr(val) }; if res == 0 { Ok(Some($name)) } else { @@ -1110,7 +1112,10 @@ macro_rules! scoped_cred { impl Drop for $name { fn drop(&mut self) { + #[cfg(target_os = "linux")] let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) }; + let res = unsafe { $syscall_nr(0) }; + #[cfg(target_os = "macos")] if res < 0 { error!( "fuse: failed to change credentials back to root: {}", @@ -1127,45 +1132,45 @@ scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid); #[cfg(target_os = "linux")] scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid); -#[cfg(target_os = "macos")] -macro_rules! scoped_cred { - ($name:ident, $ty:ty, $set_func:path) => { - #[derive(Debug)] - pub(crate) struct $name; - - impl $name { - fn new(val: $ty) -> io::Result> { - if val == 0 { - return Ok(None); - } - - let current_val = unsafe { $set_func(0) }; - - if current_val == -1 { - return Err(io::Error::last_os_error()); - } - - let res = unsafe { $set_func(val) }; - if res == 0 { - Ok(Some($name)) - } else { - unsafe { $set_func(current_val as u32) }; - return Err(io::Error::last_os_error()); - } - } - } - - impl Drop for $name { - fn drop(&mut self) { - let res = unsafe { $set_func(0) }; - error!( - "fuse: failed to change credentials back to root: {}", - io::Error::last_os_error(), - ); - } - } - }; -} +// #[cfg(target_os = "macos")] +// macro_rules! scoped_cred { +// ($name:ident, $ty:ty, $set_func:path) => { +// #[derive(Debug)] +// pub(crate) struct $name; + +// impl $name { +// fn new(val: $ty) -> io::Result> { +// if val == 0 { +// return Ok(None); +// } + +// let current_val = unsafe { $set_func(0) }; + +// if current_val == -1 { +// return Err(io::Error::last_os_error()); +// } + +// let res = unsafe { $set_func(val) }; +// if res == 0 { +// Ok(Some($name)) +// } else { +// unsafe { $set_func(current_val as u32) }; +// return Err(io::Error::last_os_error()); +// } +// } +// } + +// impl Drop for $name { +// fn drop(&mut self) { +// let res = unsafe { $set_func(0) }; +// error!( +// "fuse: failed to change credentials back to root: {}", +// io::Error::last_os_error(), +// ); +// } +// } +// }; +// } #[cfg(target_os = "macos")] scoped_cred!(ScopedUid, libc::uid_t, libc::seteuid); @@ -1209,20 +1214,30 @@ fn drop_cap_fsetid() -> io::Result> { Ok(Some(CapFsetid {})) } -#[cfg(target_os = "linux")] #[cfg(test)] mod tests { use super::*; use crate::abi::fuse_abi::CreateIn; use crate::api::filesystem::*; use crate::api::{Vfs, VfsOptions}; + #[cfg(target_os = "linux")] use caps::{CapSet, Capability}; use log; use std::io::Read; use std::ops::Deref; use std::os::unix::prelude::MetadataExt; + + use std::fs; + use std::fs::Permissions; + use std::os::unix::fs::PermissionsExt; + + #[cfg(target_os = "linux")] use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile}; + #[cfg(target_os = "macos")] + use tempfile::{tempdir, tempdir_in, NamedTempFile, TempDir}; + + #[cfg(target_os = "linux")] fn prepare_passthroughfs() -> PassthroughFs { let source = TempDir::new().expect("Cannot create temporary directory."); let parent_path = @@ -1248,14 +1263,77 @@ mod tests { fs } + #[cfg(target_os = "macos")] + fn set_dir_permissions(dir_path: &str) { + let mut permissions = fs::metadata(&dir_path) + .expect("Failed to get directory metadata") + .permissions(); + permissions.set_mode(0o40777); + let r = permissions.mode(); + set_permissions_recursive(&dir_path).expect("Failed to set permissions"); + } + + #[cfg(target_os = "macos")] + fn set_permissions_recursive(path: &str) -> std::io::Result<()> { + let mut permissions = fs::metadata(path) + .expect("Failed to get directory metadata") + .permissions(); + permissions.set_mode(0o40777); + + let entries = fs::read_dir(path)?; + + for entry in entries { + let entry = entry?; + let entry_path = entry.path(); + + if entry_path.is_dir() { + set_permissions_recursive(entry_path.to_str().unwrap())?; + } else { + let mut permissions = fs::metadata(entry_path) + .expect("Failed to get directory metadata") + .permissions(); + permissions.set_mode(0o40777); + } + } + + Ok(()) + } + + #[cfg(target_os = "macos")] + fn prepare_passthroughfs() -> PassthroughFs { + let source = tempdir().expect("Cannot create temporary directory."); + let tmp_path = source.into_path(); + let parent = tempdir_in(&tmp_path).expect("Cannot create temporary directory."); + let parent_path = parent.into_path(); + let child = NamedTempFile::new_in(&parent_path).expect("Cannot create temporary file."); + child.keep().unwrap(); + set_dir_permissions(tmp_path.to_str().unwrap()); + + // Rest of the code remains unchanged + let fs_cfg = Config { + writeback: true, + do_import: true, + no_open: true, + inode_file_handles: false, + root_dir: tmp_path.to_string_lossy().to_string(), + ..Default::default() + }; + let fs = PassthroughFs::<()>::new(fs_cfg).unwrap(); + fs.import().unwrap(); + fs + } + + #[cfg(target_os = "linux")] fn passthroughfs_no_open(cfg: bool) { let opts = VfsOptions { + #[cfg(target_os = "linux")] no_open: cfg, ..Default::default() }; let vfs = &Vfs::new(opts); // Assume that fuse kernel supports no_open. + vfs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap(); let fs_cfg = Config { @@ -1277,6 +1355,7 @@ mod tests { .unwrap(); } + #[cfg(target_os = "linux")] #[test] fn test_passthroughfs_no_open() { passthroughfs_no_open(true); @@ -1287,6 +1366,7 @@ mod tests { fn test_passthroughfs_inode_file_handles() { log::set_max_level(log::LevelFilter::Trace); + #[cfg(target_os = "linux")] match caps::has_cap(None, CapSet::Effective, Capability::CAP_DAC_READ_SEARCH) { Ok(false) | Err(_) => { println!("invoking open_by_handle_at needs CAP_DAC_READ_SEARCH"); @@ -1295,22 +1375,40 @@ mod tests { Ok(true) => {} } - let source = TempDir::new().expect("Cannot create temporary directory."); - let parent_path = - TempDir::new_in(source.as_path()).expect("Cannot create temporary directory."); - let child_path = - TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file."); + #[cfg(target_os = "linux")] + let (source, parent_path, child_path) = { + let source = TempDir::new().expect("Cannot create temporary directory."); + let parent_path = + TempDir::new_in(source.as_path()).expect("Cannot create temporary directory."); + let child_path = + TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file."); + (source, parent_path, child_path) + }; + + #[cfg(target_os = "macos")] + let (source, parent_path, child_path) = { + let source = tempdir().expect("Cannot create temporary directory."); + let tmp_path = source.into_path(); + let parent = tempdir_in(&tmp_path).expect("Cannot create temporary directory."); + let parent_path = parent.into_path(); + let child = NamedTempFile::new_in(&parent_path).expect("Cannot create temporary file."); + let (_, child_path) = child.keep().unwrap(); + (tmp_path, parent_path, child_path) + }; let fs_cfg = Config { writeback: true, do_import: true, no_open: true, inode_file_handles: true, + #[cfg(target_os = "linux")] root_dir: source .as_path() .to_str() .expect("source path to string") .to_string(), + #[cfg(target_os = "macos")] + root_dir: source.to_str().expect("source path to string").to_string(), ..Default::default() }; let fs = PassthroughFs::<()>::new(fs_cfg).unwrap(); @@ -1320,24 +1418,38 @@ mod tests { // read a few files to inode map. let parent = CString::new( + #[cfg(target_os = "linux")] parent_path .as_path() .file_name() .unwrap() .to_str() .expect("path to string"), + #[cfg(target_os = "macos")] + parent_path + .file_name() + .unwrap() + .to_str() + .expect("path to string"), ) .unwrap(); let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap(); let p_inode = p_entry.inode; let child = CString::new( + #[cfg(target_os = "linux")] child_path .as_path() .file_name() .unwrap() .to_str() .expect("path to string"), + #[cfg(target_os = "macos")] + child_path + .file_name() + .unwrap() + .to_str() + .expect("path to string"), ) .unwrap(); let c_entry = fs.lookup(&ctx, p_inode, &child).unwrap(); @@ -1432,24 +1544,43 @@ mod tests { fn test_writeback_open_and_create() { // prepare a fs with writeback cache and open being true, so a write-only opened file // should have read permission as well. + #[cfg(target_os = "linux")] let source = TempDir::new().expect("Cannot create temporary directory."); + #[cfg(target_os = "macos")] + let source = tempdir().expect("Cannot create temporary directory."); + #[cfg(target_os = "linux")] let _ = std::process::Command::new("sh") .arg("-c") .arg(format!("touch {}/existfile", source.as_path().to_str().unwrap()).as_str()) .output() .unwrap(); + #[cfg(target_os = "macos")] + let _ = std::process::Command::new("sh") + .arg("-c") + .arg(format!("touch {}/existfile", source.path().to_str().unwrap()).as_str()) + .output() + .unwrap(); let fs_cfg = Config { writeback: true, do_import: true, no_open: false, inode_file_handles: false, + #[cfg(target_os = "linux")] root_dir: source .as_path() .to_str() .expect("source path to string") .to_string(), + #[cfg(target_os = "macos")] + root_dir: source + .path() + .to_str() + .expect("source path to string") + .to_string(), ..Default::default() }; + #[cfg(target_os = "macos")] + set_dir_permissions(source.path().to_str().unwrap()); let mut fs = PassthroughFs::<()>::new(fs_cfg).unwrap(); fs.writeback = AtomicBool::new(true); fs.no_open = AtomicBool::new(false); @@ -1464,7 +1595,10 @@ mod tests { let fname = CString::new("testfile").unwrap(); let args = CreateIn { flags: libc::O_WRONLY as u32, + #[cfg(target_os = "linux")] mode: 0644, + #[cfg(target_os = "macos")] + mode: 0o40777, umask: 0, fuse_flags: 0, }; @@ -1493,11 +1627,26 @@ mod tests { fn test_passthroughfs_dir_timeout() { log::set_max_level(log::LevelFilter::Trace); - let source = TempDir::new().expect("Cannot create temporary directory."); - let parent_path = - TempDir::new_in(source.as_path()).expect("Cannot create temporary directory."); - let child_path = - TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file."); + #[cfg(target_os = "linux")] + let (source, parent_path, child_path) = { + let source = TempDir::new().expect("Cannot create temporary directory."); + let parent_path = + TempDir::new_in(source.as_path()).expect("Cannot create temporary directory."); + let child_path = + TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file."); + (source, parent_path, child_path) + }; + + #[cfg(target_os = "macos")] + let (source, parent_path, child_path) = { + let source = tempdir().expect("Cannot create temporary directory."); + let tmp_path = source.into_path(); + let parent = tempdir_in(&tmp_path).expect("Cannot create temporary directory."); + let parent_path = parent.into_path(); + let child = NamedTempFile::new_in(&parent_path).expect("Cannot create temporary file."); + let (_, child_path) = child.keep().unwrap(); + (tmp_path, parent_path, child_path) + }; // passthroughfs with cache=none, but non-zero dir entry/attr timeout. let fs_cfg = Config { @@ -1555,8 +1704,21 @@ mod tests { #[test] fn test_stable_inode() { use std::os::unix::fs::MetadataExt; - let source = TempDir::new().expect("Cannot create temporary directory."); - let child_path = TempFile::new_in(source.as_path()).expect("Cannot create temporary file."); + #[cfg(target_os = "linux")] + let (source, child_path) = { + let source = TempDir::new().expect("Cannot create temporary directory."); + let child_path = + TempFile::new_in(source.as_path()).expect("Cannot create temporary file."); + (source, child_path) + }; + #[cfg(target_os = "macos")] + let (source, chile_file, child_path) = { + let source = tempdir().expect("Cannot create temporary directory."); + let tmp_path = source.into_path(); + let child = NamedTempFile::new_in(&tmp_path).expect("Cannot create temporary file."); + let (chile_file, child_path) = child.keep().unwrap(); + (tmp_path, chile_file, child_path) + }; let child = CString::new( child_path .as_path() @@ -1566,7 +1728,10 @@ mod tests { .expect("path to string"), ) .unwrap(); + #[cfg(target_os = "linux")] let meta = child_path.as_file().metadata().unwrap(); + #[cfg(target_os = "macos")] + let meta = chile_file.metadata().unwrap(); let ctx = Context::default(); { let fs_cfg = Config { @@ -1621,11 +1786,16 @@ mod tests { let id = InodeId { ino: MAX_HOST_INO + 1, dev: 1, + #[cfg(target_os = "linux")] mnt: 1, }; // Default + #[cfg(target_os = "linux")] let inode = fs.allocate_inode(&m, &id, None).unwrap(); + + #[cfg(target_os = "macos")] + let inode = fs.allocate_inode(&m, &id).unwrap(); assert_eq!(inode, 2); } @@ -1636,10 +1806,15 @@ mod tests { let id = InodeId { ino: 12345, dev: 1, + #[cfg(target_os = "linux")] mnt: 1, }; // direct return host inode 12345 + #[cfg(target_os = "linux")] let inode = fs.allocate_inode(&m, &id, None).unwrap(); + + #[cfg(target_os = "macos")] + let inode = fs.allocate_inode(&m, &id).unwrap(); assert_eq!(inode & MAX_HOST_INO, 12345) } @@ -1650,17 +1825,48 @@ mod tests { let id = InodeId { ino: MAX_HOST_INO + 1, dev: 1, + #[cfg(target_os = "linux")] mnt: 1, }; // allocate a virtual inode + #[cfg(target_os = "linux")] let inode = fs.allocate_inode(&m, &id, None).unwrap(); + + #[cfg(target_os = "macos")] + let inode = fs.allocate_inode(&m, &id).unwrap(); assert_eq!(inode & MAX_HOST_INO, 2); + #[cfg(target_os = "linux")] let file = TempFile::new().expect("Cannot create temporary file."); + #[cfg(target_os = "macos")] + let (_, file, child_path) = { + let source = tempdir().expect("Cannot create temporary directory."); + let tmp_path = source.into_path(); + let child = + NamedTempFile::new_in(&tmp_path).expect("Cannot create temporary file."); + let (chile_file, child_path) = child.keep().unwrap(); + (tmp_path, chile_file, child_path) + }; + #[cfg(target_os = "linux")] let mode = file.as_file().metadata().unwrap().mode(); + #[cfg(target_os = "macos")] + let (mode, cstring_path) = { + let mode = file.metadata().unwrap().mode(); + let cstring_path = CString::new(child_path.to_string_lossy().to_string()) + .expect("Failed to convert to CString"); + (mode as u16, cstring_path) + }; + #[cfg(target_os = "linux")] let inode_data = InodeData::new(inode, InodeHandle::File(file.into_file()), 1, id, mode); + #[cfg(target_os = "macos")] + let inode_data = + InodeData::new(inode, InodeHandle::File(file, cstring_path), 1, id, mode); m.insert(Arc::new(inode_data)); + #[cfg(target_os = "linux")] let inode = fs.allocate_inode(&m, &id, None).unwrap(); + + #[cfg(target_os = "macos")] + let inode = fs.allocate_inode(&m, &id).unwrap(); assert_eq!(inode & MAX_HOST_INO, 2); } } @@ -1715,34 +1921,3 @@ mod tests { assert!(!fs.cfg.writeback); } } - -#[cfg(target_os = "macos")] -#[cfg(test)] -mod tests { - use fuse::ROOT_ID; - - use crate::api::filesystem::{Context, FileSystem}; - - use super::*; - - fn prepare_passthroughfs() -> PassthroughFs { - let fs_cfg = Config { - root_dir: std::env::temp_dir().into_os_string().into_string().unwrap(), - ..Default::default() - }; - let fs = PassthroughFs::<()>::new(fs_cfg).unwrap(); - fs.import().unwrap(); - - fs - } - - #[test] - fn test_lookup_escape_root() { - let fs = prepare_passthroughfs(); - let ctx = Context::default(); - - let name = CString::new("..").unwrap(); - let entry = fs.lookup(&ctx, ROOT_ID, &name).unwrap(); - assert_eq!(entry.inode, ROOT_ID); - } -} diff --git a/src/passthrough/stat.rs b/src/passthrough/stat.rs index 77a1db189..3410ad717 100644 --- a/src/passthrough/stat.rs +++ b/src/passthrough/stat.rs @@ -14,10 +14,8 @@ pub fn stat(path_file: &impl AsRawFd) -> io::Result { let mut st_ui = MaybeUninit::::zeroed(); let res = unsafe { libc::fstat(path_file.as_raw_fd(), st_ui.as_mut_ptr()) }; - if res >= 0 { let st = unsafe { st_ui.assume_init() }; - Ok(Stat { st }) } else { Err(io::Error::last_os_error()) From d33ff9c0a8dd28d9637455afb4e44bbffad2cdf4 Mon Sep 17 00:00:00 2001 From: akitaSummer <644171127@qq.com> Date: Sat, 13 Jan 2024 06:27:55 +0800 Subject: [PATCH 3/8] feat: add macos sync io Signed-off-by: akitaSummer <644171127@qq.com> --- src/lib.rs | 5 +- src/passthrough/macos_sync_io.rs | 197 ---------- src/passthrough/mod.rs | 15 +- .../{linux_sync_io.rs => sync_io.rs} | 359 +++++++++++++++--- src/passthrough/util.rs | 15 + tests/example/mod.rs | 2 +- tests/example/passthroughfs.rs | 2 + tests/smoke.rs | 18 +- 8 files changed, 356 insertions(+), 257 deletions(-) delete mode 100644 src/passthrough/macos_sync_io.rs rename src/passthrough/{linux_sync_io.rs => sync_io.rs} (79%) diff --git a/src/lib.rs b/src/lib.rs index 00ac1f2d1..1eb2782cd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,10 @@ pub type Result = ::std::result::Result; pub mod abi; pub mod api; -// #[cfg(all(any(feature = "fusedev", feature = "virtiofs"), target_os = "linux"))] +#[cfg(all( + any(feature = "fusedev", feature = "virtiofs"), + any(target_os = "macos", target_os = "linux") +))] pub mod passthrough; pub mod transport; diff --git a/src/passthrough/macos_sync_io.rs b/src/passthrough/macos_sync_io.rs deleted file mode 100644 index fc4951e11..000000000 --- a/src/passthrough/macos_sync_io.rs +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (C) 2020 Alibaba Cloud. All rights reserved. -// Copyright 2019 The Chromium OS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE-BSD-3-Clause file. - -//! Fuse passthrough file system, mirroring an existing FS hierarchy. - -use std::ffi::{CStr, CString}; -use std::fs::File; -use std::io; -use std::mem::{self, size_of, ManuallyDrop, MaybeUninit}; -use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::time::Duration; - -use super::*; -use crate::abi::fuse_abi::{CreateIn, Opcode, FOPEN_IN_KILL_SUIDGID}; -#[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] -use crate::abi::virtio_fs; -use crate::api::filesystem::{ - Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions, - SetattrValid, ZeroCopyReader, ZeroCopyWriter, -}; -use crate::bytes_to_cstr; -#[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] -use crate::transport::FsCacheReqHandler; - -impl PassthroughFs { - fn open_inode(&self, inode: Inode, flags: i32) -> io::Result { - let data = self.inode_map.get(inode)?; - if !is_safe_inode(data.mode) { - Err(ebadf()) - } else { - let new_flags = self.get_writeback_open_flags(flags); - data.open_file(new_flags | libc::O_CLOEXEC) - } - } - - fn do_open( - &self, - inode: Inode, - flags: u32, - fuse_flags: u32, - ) -> io::Result<(Option, OpenOptions, Option)> { - let file = self.open_inode(inode, flags as i32)?; - - let data = HandleData::new(inode, file, flags); - let handle = self.next_handle.fetch_add(1, Ordering::Relaxed); - self.handle_map.insert(handle, data); - - let mut opts = OpenOptions::empty(); - match self.cfg.cache_policy { - // We only set the direct I/O option on files. - CachePolicy::Never => opts.set( - OpenOptions::DIRECT_IO, - flags & (libc::O_DIRECTORY as u32) == 0, - ), - CachePolicy::Metadata => { - if flags & (libc::O_DIRECTORY as u32) == 0 { - opts |= OpenOptions::DIRECT_IO; - } else { - opts |= OpenOptions::KEEP_CACHE; - } - } - CachePolicy::Always => { - opts |= OpenOptions::KEEP_CACHE; - } - _ => {} - }; - - Ok((Some(handle), opts, None)) - } - - fn do_getattr(&self, inode: Inode, handle: Option) -> io::Result<(LibCStat, Duration)> { - let st; - let data = self.inode_map.get(inode).map_err(|e| { - error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); - e - })?; - - // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle - // this case correctly. - if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { - // Safe as we just checked handle - let hd = self.handle_map.get(handle.unwrap(), inode)?; - st = stat(hd.get_file()); - } else { - st = data.handle.stat(); - } - - let st = st.map_err(|e| { - error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); - e - })?; - - Ok((st.st, self.cfg.attr_timeout)) - } -} - -impl FileSystem for PassthroughFs { - type Inode = Inode; - type Handle = Handle; - - fn init(&self, capable: FsOptions) -> io::Result { - if self.cfg.do_import { - self.import()?; - } - - let mut opts = FsOptions::FILE_OPS; - - Ok(opts) - } - - fn lookup(&self, _ctx: &Context, parent: Self::Inode, name: &CStr) -> io::Result { - if name.to_bytes_with_nul().contains(&SLASH_ASCII) { - return Err(einval()); - } - self.do_lookup(parent, name) - } - - fn open( - &self, - _ctx: &Context, - inode: Inode, - flags: u32, - fuse_flags: u32, - ) -> io::Result<(Option, OpenOptions, Option)> { - if self.no_open.load(Ordering::Relaxed) { - info!("fuse: open is not supported."); - Err(enosys()) - } else { - self.do_open(inode, flags, fuse_flags) - } - } - - fn create( - &self, - ctx: &Context, - parent: Inode, - name: &CStr, - args: CreateIn, - ) -> io::Result<(Entry, Option, OpenOptions, Option)> { - self.validate_path_component(name)?; - - let dir = self.inode_map.get(parent)?; - let dir_file = dir.get_file()?; - - let new_file = { - let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; - - let flags = self.get_writeback_open_flags(args.flags as i32); - Self::create_file_excl(&dir_file, name, flags, args.mode & !(args.umask & 0o777))? - }; - - let entry = self.do_lookup(parent, name)?; - let file = match new_file { - // File didn't exist, now created by create_file_excl() - Some(f) => f, - // File exists, and args.flags doesn't contain O_EXCL. Now let's open it with - // open_inode(). - None => { - let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; - self.open_inode(entry.inode, args.flags as i32)? - } - }; - - let ret_handle = if !self.no_open.load(Ordering::Relaxed) { - let handle = self.next_handle.fetch_add(1, Ordering::Relaxed); - let data = HandleData::new(entry.inode, file, args.flags); - - self.handle_map.insert(handle, data); - Some(handle) - } else { - None - }; - - let mut opts = OpenOptions::empty(); - match self.cfg.cache_policy { - CachePolicy::Never => opts |= OpenOptions::DIRECT_IO, - CachePolicy::Metadata => opts |= OpenOptions::DIRECT_IO, - CachePolicy::Always => opts |= OpenOptions::KEEP_CACHE, - _ => {} - }; - - Ok((entry, ret_handle, opts, None)) - } - - fn getattr( - &self, - _ctx: &Context, - inode: Inode, - handle: Option, - ) -> io::Result<(LibCStat, Duration)> { - self.do_getattr(inode, handle) - } -} diff --git a/src/passthrough/mod.rs b/src/passthrough/mod.rs index d36d8af95..506582a5d 100644 --- a/src/passthrough/mod.rs +++ b/src/passthrough/mod.rs @@ -58,10 +58,6 @@ mod config; mod file_handle; mod inode_store; #[cfg(target_os = "linux")] -mod linux_sync_io; -#[cfg(target_os = "macos")] -mod macos_sync_io; -#[cfg(target_os = "linux")] mod mount_fd; #[cfg(target_os = "linux")] mod os_compat; @@ -69,6 +65,7 @@ mod os_compat; mod stat; #[cfg(target_os = "linux")] mod statx; +mod sync_io; mod util; type Inode = u64; @@ -89,6 +86,16 @@ type LibCStat = libc::stat64; #[cfg(target_os = "macos")] type LibCStat = libc::stat; +#[cfg(target_os = "linux")] +type OffT = libc::off64_t; +#[cfg(target_os = "macos")] +type OffT = libc::off_t; + +#[cfg(target_os = "linux")] +type StatVfs = libc::statvfs64; +#[cfg(target_os = "macos")] +type StatVfs = libc::statvfs; + /// Maximum host inode number supported by passthroughfs const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff; diff --git a/src/passthrough/linux_sync_io.rs b/src/passthrough/sync_io.rs similarity index 79% rename from src/passthrough/linux_sync_io.rs rename to src/passthrough/sync_io.rs index 97d24c563..48eedeee6 100644 --- a/src/passthrough/linux_sync_io.rs +++ b/src/passthrough/sync_io.rs @@ -14,10 +14,14 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; +#[cfg(target_os = "linux")] use super::os_compat::LinuxDirent64; +#[cfg(target_os = "linux")] use super::util::stat_fd; use super::*; -use crate::abi::fuse_abi::{CreateIn, Opcode, FOPEN_IN_KILL_SUIDGID, WRITE_KILL_PRIV}; +#[cfg(target_os = "linux")] +use crate::abi::fuse_abi::WRITE_KILL_PRIV; +use crate::abi::fuse_abi::{CreateIn, Opcode, FOPEN_IN_KILL_SUIDGID}; #[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] use crate::abi::virtio_fs; use crate::api::filesystem::{ @@ -35,7 +39,10 @@ impl PassthroughFs { Err(ebadf()) } else { let new_flags = self.get_writeback_open_flags(flags); - data.open_file(new_flags | libc::O_CLOEXEC, &self.proc_self_fd) + #[cfg(target_os = "linux")] + return data.open_file(new_flags | libc::O_CLOEXEC, &self.proc_self_fd); + #[cfg(target_os = "macos")] + return data.open_file(new_flags | libc::O_CLOEXEC); } } @@ -77,14 +84,17 @@ impl PassthroughFs { let (guard, dir) = data.get_file_mut(); // Safe because this doesn't modify any memory and we check the return value. - let res = - unsafe { libc::lseek64(dir.as_raw_fd(), offset as libc::off64_t, libc::SEEK_SET) }; + #[cfg(target_os = "linux")] + let res = unsafe { libc::lseek64(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; + #[cfg(target_os = "macos")] + let res = unsafe { libc::lseek(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; if res < 0 { return Err(io::Error::last_os_error()); } // Safe because the kernel guarantees that it will only write to `buf` and we check the // return value. + #[cfg(target_os = "linux")] let res = unsafe { libc::syscall( libc::SYS_getdents64, @@ -93,6 +103,14 @@ impl PassthroughFs { size as libc::c_int, ) }; + #[cfg(target_os = "macos")] + let res = unsafe { + libc::read( + dir.as_raw_fd(), + buf.as_mut_ptr() as *mut libc::c_void, + size as libc::size_t, + ) + }; if res < 0 { return Err(io::Error::last_os_error()); } @@ -106,6 +124,8 @@ impl PassthroughFs { let mut rem = &buf[..]; let orig_rem_len = rem.len(); + + #[cfg(target_os = "linux")] while !rem.is_empty() { // We only use debug asserts here because these values are coming from the kernel and we // trust them implicitly. @@ -173,6 +193,58 @@ impl PassthroughFs { } } + #[cfg(target_os = "macos")] + while !rem.is_empty() { + debug_assert!( + rem.len() >= mem::size_of::(), + "fuse: not enough space left in `rem`" + ); + + let (front, back) = rem.split_at(mem::size_of::()); + + let dirent = unsafe { *(front.as_ptr() as *const libc::dirent) }; + + let namelen = dirent.d_namlen as usize; + debug_assert!( + namelen <= back.len(), + "fuse: back is smaller than `namelen`" + ); + + let name = &back[..namelen]; + let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { + Ok(1) + } else { + let name = bytes_to_cstr(name) + .map_err(|e| { + error!("fuse: do_readdir: {:?}", e); + einval() + })? + .to_bytes(); + + add_entry( + DirEntry { + ino: dirent.d_ino, + offset: dirent.d_seekoff as u64, + type_: dirent.d_type as u32, + name, + }, + data.borrow_fd().as_raw_fd(), + ) + }; + + debug_assert!( + rem.len() >= dirent.d_reclen as usize, + "fuse: rem is smaller than `d_reclen`" + ); + + match res { + Ok(0) => break, + Ok(_) => rem = &rem[dirent.d_reclen as usize..], + Err(e) if rem.len() == orig_rem_len => return Err(e), + Err(_) => return Ok(()), + } + } + Ok(()) } @@ -182,6 +254,7 @@ impl PassthroughFs { flags: u32, fuse_flags: u32, ) -> io::Result<(Option, OpenOptions, Option)> { + #[cfg(target_os = "linux")] let killpriv = if self.killpriv_v2.load(Ordering::Relaxed) && (fuse_flags & FOPEN_IN_KILL_SUIDGID != 0) { @@ -190,6 +263,7 @@ impl PassthroughFs { None }; let file = self.open_inode(inode, flags as i32)?; + #[cfg(target_os = "linux")] drop(killpriv); let data = HandleData::new(inode, file, flags); @@ -204,14 +278,23 @@ impl PassthroughFs { flags & (libc::O_DIRECTORY as u32) == 0, ), CachePolicy::Metadata => { + #[cfg(target_os = "linux")] if flags & (libc::O_DIRECTORY as u32) == 0 { opts |= OpenOptions::DIRECT_IO; } else { opts |= OpenOptions::CACHE_DIR | OpenOptions::KEEP_CACHE; } + + #[cfg(target_os = "macos")] + if flags & (libc::O_DIRECTORY as u32) == 0 { + opts |= OpenOptions::DIRECT_IO; + } else { + opts |= OpenOptions::KEEP_CACHE; + } } CachePolicy::Always => { opts |= OpenOptions::KEEP_CACHE; + #[cfg(target_os = "linux")] if flags & (libc::O_DIRECTORY as u32) != 0 { opts |= OpenOptions::CACHE_DIR; } @@ -222,11 +305,7 @@ impl PassthroughFs { Ok((Some(handle), opts, None)) } - fn do_getattr( - &self, - inode: Inode, - handle: Option, - ) -> io::Result<(libc::stat64, Duration)> { + fn do_getattr(&self, inode: Inode, handle: Option) -> io::Result<(LibCStat, Duration)> { let st; let data = self.inode_map.get(inode).map_err(|e| { error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); @@ -235,6 +314,8 @@ impl PassthroughFs { // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle // this case correctly. + + #[cfg(target_os = "linux")] if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { // Safe as we just checked handle let hd = self.handle_map.get(handle.unwrap(), inode)?; @@ -243,12 +324,24 @@ impl PassthroughFs { st = data.handle.stat(); } + #[cfg(target_os = "macos")] + if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { + // Safe as we just checked handle + let hd = self.handle_map.get(handle.unwrap(), inode)?; + st = stat(hd.get_file()); + } else { + st = data.handle.stat(); + } + let st = st.map_err(|e| { error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); e })?; + #[cfg(target_os = "linux")] + return Ok((st, self.cfg.attr_timeout)); - Ok((st, self.cfg.attr_timeout)) + #[cfg(target_os = "macos")] + return Ok((st.st, self.cfg.attr_timeout)); } fn do_unlink(&self, parent: Inode, name: &CStr, flags: libc::c_int) -> io::Result<()> { @@ -256,6 +349,7 @@ impl PassthroughFs { let file = data.get_file()?; // Safe because this doesn't modify any memory and we check the return value. let res = unsafe { libc::unlinkat(file.as_raw_fd(), name.as_ptr(), flags) }; + if res == 0 { Ok(()) } else { @@ -303,41 +397,48 @@ impl FileSystem for PassthroughFs { self.import()?; } + #[cfg(target_os = "linux")] let mut opts = FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO; // !cfg.do_import means we are under vfs, in which case capable is already // negotiated and must be honored. - if (!self.cfg.do_import || self.cfg.writeback) - && capable.contains(FsOptions::WRITEBACK_CACHE) - { - opts |= FsOptions::WRITEBACK_CACHE; - self.writeback.store(true, Ordering::Relaxed); - } - if (!self.cfg.do_import || self.cfg.no_open) - && capable.contains(FsOptions::ZERO_MESSAGE_OPEN) + #[cfg(target_os = "linux")] { - opts |= FsOptions::ZERO_MESSAGE_OPEN; - // We can't support FUSE_ATOMIC_O_TRUNC with no_open - opts.remove(FsOptions::ATOMIC_O_TRUNC); - self.no_open.store(true, Ordering::Relaxed); - } - if (!self.cfg.do_import || self.cfg.no_opendir) - && capable.contains(FsOptions::ZERO_MESSAGE_OPENDIR) - { - opts |= FsOptions::ZERO_MESSAGE_OPENDIR; - self.no_opendir.store(true, Ordering::Relaxed); - } - if (!self.cfg.do_import || self.cfg.killpriv_v2) - && capable.contains(FsOptions::HANDLE_KILLPRIV_V2) - { - opts |= FsOptions::HANDLE_KILLPRIV_V2; - self.killpriv_v2.store(true, Ordering::Relaxed); - } + if (!self.cfg.do_import || self.cfg.writeback) + && capable.contains(FsOptions::WRITEBACK_CACHE) + { + opts |= FsOptions::WRITEBACK_CACHE; + self.writeback.store(true, Ordering::Relaxed); + } + if (!self.cfg.do_import || self.cfg.no_open) + && capable.contains(FsOptions::ZERO_MESSAGE_OPEN) + { + opts |= FsOptions::ZERO_MESSAGE_OPEN; + // We can't support FUSE_ATOMIC_O_TRUNC with no_open + opts.remove(FsOptions::ATOMIC_O_TRUNC); + self.no_open.store(true, Ordering::Relaxed); + } + if (!self.cfg.do_import || self.cfg.no_opendir) + && capable.contains(FsOptions::ZERO_MESSAGE_OPENDIR) + { + opts |= FsOptions::ZERO_MESSAGE_OPENDIR; + self.no_opendir.store(true, Ordering::Relaxed); + } + if (!self.cfg.do_import || self.cfg.killpriv_v2) + && capable.contains(FsOptions::HANDLE_KILLPRIV_V2) + { + opts |= FsOptions::HANDLE_KILLPRIV_V2; + self.killpriv_v2.store(true, Ordering::Relaxed); + } - if capable.contains(FsOptions::PERFILE_DAX) { - opts |= FsOptions::PERFILE_DAX; - self.perfile_dax.store(true, Ordering::Relaxed); + if capable.contains(FsOptions::PERFILE_DAX) { + opts |= FsOptions::PERFILE_DAX; + self.perfile_dax.store(true, Ordering::Relaxed); + } } + #[cfg(target_os = "macos")] + let mut opts = FsOptions::FILE_OPS; + Ok(opts) } @@ -350,17 +451,24 @@ impl FileSystem for PassthroughFs { }; } - fn statfs(&self, _ctx: &Context, inode: Inode) -> io::Result { - let mut out = MaybeUninit::::zeroed(); + fn statfs(&self, _ctx: &Context, inode: Inode) -> io::Result { + let mut out = MaybeUninit::::zeroed(); let data = self.inode_map.get(inode)?; let file = data.get_file()?; // Safe because this will only modify `out` and we check the return value. + #[cfg(target_os = "linux")] match unsafe { libc::fstatvfs64(file.as_raw_fd(), out.as_mut_ptr()) } { // Safe because the kernel guarantees that `out` has been initialized. 0 => Ok(unsafe { out.assume_init() }), _ => Err(io::Error::last_os_error()), } + #[cfg(target_os = "macos")] + match unsafe { libc::fstatvfs(file.as_raw_fd(), out.as_mut_ptr()) } { + // Safe because the kernel guarantees that `out` has been initialized. + 0 => Ok(unsafe { out.assume_init() }), + _ => Err(io::Error::last_os_error()), + } } fn lookup(&self, _ctx: &Context, parent: Inode, name: &CStr) -> io::Result { @@ -427,7 +535,18 @@ impl FileSystem for PassthroughFs { let file = data.get_file()?; // Safe because this doesn't modify any memory and we check the return value. - unsafe { libc::mkdirat(file.as_raw_fd(), name.as_ptr(), mode & !umask) } + #[cfg(target_os = "macos")] + unsafe { + libc::mkdirat( + file.as_raw_fd(), + name.as_ptr(), + (mode & !umask) as libc::mode_t, + ) + } + #[cfg(target_os = "linux")] + unsafe { + libc::mkdirat(file.as_raw_fd(), name.as_ptr(), mode & !umask) + } }; if res < 0 { return Err(io::Error::last_os_error()); @@ -570,6 +689,7 @@ impl FileSystem for PassthroughFs { // open_inode(). None => { // Cap restored when _killpriv is dropped + #[cfg(target_os = "linux")] let _killpriv = if self.killpriv_v2.load(Ordering::Relaxed) && (args.fuse_flags & FOPEN_IN_KILL_SUIDGID != 0) { @@ -695,13 +815,18 @@ impl FileSystem for PassthroughFs { self.check_fd_flags(data, f.as_raw_fd(), flags)?; if self.seal_size.load(Ordering::Relaxed) { + #[cfg(target_os = "linux")] let st = stat_fd(&f, None)?; + + #[cfg(target_os = "macos")] + let st = stat(&f)?.st; self.seal_size_check(Opcode::Write, st.st_size as u64, offset, size as u64, 0)?; } let mut f = ManuallyDrop::new(f); // Cap restored when _killpriv is dropped + #[cfg(target_os = "linux")] let _killpriv = if self.killpriv_v2.load(Ordering::Relaxed) && (fuse_flags & WRITE_KILL_PRIV != 0) { self::drop_cap_fsetid()? @@ -717,7 +842,7 @@ impl FileSystem for PassthroughFs { _ctx: &Context, inode: Inode, handle: Option, - ) -> io::Result<(libc::stat64, Duration)> { + ) -> io::Result<(LibCStat, Duration)> { self.do_getattr(inode, handle) } @@ -725,10 +850,10 @@ impl FileSystem for PassthroughFs { &self, _ctx: &Context, inode: Inode, - attr: libc::stat64, + attr: LibCStat, handle: Option, valid: SetattrValid, - ) -> io::Result<(libc::stat64, Duration)> { + ) -> io::Result<(LibCStat, Duration)> { let inode_data = self.inode_map.get(inode)?; enum Data { @@ -737,6 +862,7 @@ impl FileSystem for PassthroughFs { } let file = inode_data.get_file()?; + #[cfg(target_os = "linux")] let data = if self.no_open.load(Ordering::Relaxed) { let pathname = CString::new(format!("{}", file.as_raw_fd())) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; @@ -753,6 +879,21 @@ impl FileSystem for PassthroughFs { } }; + #[cfg(target_os = "macos")] + let data = if self.no_open.load(Ordering::Relaxed) { + let pathname = inode_data.get_path()?; + Data::ProcPath(pathname) + } else { + // If we have a handle then use it otherwise get a new fd from the inode. + if let Some(handle) = handle { + let hd = self.handle_map.get(handle, inode)?; + Data::Handle(hd) + } else { + let pathname = inode_data.get_path()?; + Data::ProcPath(pathname) + } + }; + if valid.contains(SetattrValid::SIZE) && self.seal_size.load(Ordering::Relaxed) { return Err(io::Error::from_raw_os_error(libc::EPERM)); } @@ -762,9 +903,12 @@ impl FileSystem for PassthroughFs { let res = unsafe { match data { Data::Handle(ref h) => libc::fchmod(h.borrow_fd().as_raw_fd(), attr.st_mode), + #[cfg(target_os = "linux")] Data::ProcPath(ref p) => { libc::fchmodat(self.proc_self_fd.as_raw_fd(), p.as_ptr(), attr.st_mode, 0) } + #[cfg(target_os = "macos")] + Data::ProcPath(ref p) => libc::chmod(p.as_ptr(), attr.st_mode), } }; if res < 0 { @@ -790,6 +934,7 @@ impl FileSystem for PassthroughFs { let empty = unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) }; // Safe because this doesn't modify any memory and we check the return value. + #[cfg(target_os = "linux")] let res = unsafe { libc::fchownat( file.as_raw_fd(), @@ -799,6 +944,16 @@ impl FileSystem for PassthroughFs { libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW, ) }; + #[cfg(target_os = "macos")] + let res = unsafe { + libc::fchownat( + file.as_raw_fd(), + empty.as_ptr(), + uid, + gid, + libc::AT_SYMLINK_NOFOLLOW, + ) + }; if res < 0 { return Err(io::Error::last_os_error()); } @@ -806,6 +961,7 @@ impl FileSystem for PassthroughFs { if valid.contains(SetattrValid::SIZE) { // Cap restored when _killpriv is dropped + #[cfg(target_os = "linux")] let _killpriv = if self.killpriv_v2.load(Ordering::Relaxed) && valid.contains(SetattrValid::KILL_SUIDGID) { @@ -861,9 +1017,24 @@ impl FileSystem for PassthroughFs { Data::Handle(ref h) => unsafe { libc::futimens(h.borrow_fd().as_raw_fd(), tvs.as_ptr()) }, + #[cfg(target_os = "linux")] Data::ProcPath(ref p) => unsafe { libc::utimensat(self.proc_self_fd.as_raw_fd(), p.as_ptr(), tvs.as_ptr(), 0) }, + #[cfg(target_os = "macos")] + Data::ProcPath(ref p) => { + let tvs = [ + libc::timeval { + tv_sec: tvs[0].tv_sec, + tv_usec: (tvs[0].tv_nsec / 1000) as i32, + }, + libc::timeval { + tv_sec: tvs[1].tv_sec, + tv_usec: (tvs[1].tv_nsec / 1000) as i32, + }, + ]; + unsafe { libc::utimes(p.as_ptr(), tvs.as_ptr()) } + } }; if res < 0 { return Err(io::Error::last_os_error()); @@ -893,6 +1064,7 @@ impl FileSystem for PassthroughFs { // Safe because this doesn't modify any memory and we check the return value. // TODO: Switch to libc::renameat2 once https://github.com/rust-lang/libc/pull/1508 lands // and we have glibc 2.28. + #[cfg(target_os = "linux")] let res = unsafe { libc::syscall( libc::SYS_renameat2, @@ -903,6 +1075,16 @@ impl FileSystem for PassthroughFs { flags, ) }; + + #[cfg(target_os = "macos")] + let res = unsafe { + libc::renameat( + old_file.as_raw_fd(), + oldname.as_ptr(), + new_file.as_raw_fd(), + newname.as_ptr(), + ) + }; if res == 0 { Ok(()) } else { @@ -923,11 +1105,13 @@ impl FileSystem for PassthroughFs { let data = self.inode_map.get(parent)?; let file = data.get_file()?; + let pathname = data.get_path()?; let res = { let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?; // Safe because this doesn't modify any memory and we check the return value. + #[cfg(target_os = "linux")] unsafe { libc::mknodat( file.as_raw_fd(), @@ -936,6 +1120,14 @@ impl FileSystem for PassthroughFs { u64::from(rdev), ) } + #[cfg(target_os = "macos")] + unsafe { + libc::mknod( + pathname.as_ptr(), + (mode & !umask) as libc::mode_t, + rdev as i32, + ) + } }; if res < 0 { Err(io::Error::last_os_error()) @@ -962,6 +1154,7 @@ impl FileSystem for PassthroughFs { let empty = unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) }; // Safe because this doesn't modify any memory and we check the return value. + #[cfg(target_os = "linux")] let res = unsafe { libc::linkat( file.as_raw_fd(), @@ -971,6 +1164,16 @@ impl FileSystem for PassthroughFs { libc::AT_EMPTY_PATH, ) }; + #[cfg(target_os = "macos")] + let res = unsafe { + libc::linkat( + file.as_raw_fd(), + empty.as_ptr(), + new_file.as_raw_fd(), + newname.as_ptr(), + libc::AT_FDCWD, + ) + }; if res == 0 { self.do_lookup(newparent, newname) } else { @@ -1070,6 +1273,7 @@ impl FileSystem for PassthroughFs { let fd = data.borrow_fd(); // Safe because this doesn't modify any memory and we check the return value. + #[cfg(target_os = "linux")] let res = unsafe { if datasync { libc::fdatasync(fd.as_raw_fd()) @@ -1077,6 +1281,8 @@ impl FileSystem for PassthroughFs { libc::fsync(fd.as_raw_fd()) } }; + #[cfg(target_os = "macos")] + let res = unsafe { libc::fsync(fd.as_raw_fd()) }; if res == 0 { Ok(()) } else { @@ -1096,7 +1302,10 @@ impl FileSystem for PassthroughFs { fn access(&self, ctx: &Context, inode: Inode, mask: u32) -> io::Result<()> { let data = self.inode_map.get(inode)?; + #[cfg(target_os = "linux")] let st = stat_fd(&data.get_file()?, None)?; + #[cfg(target_os = "macos")] + let st = stat(&data.get_file()?)?.st; let mode = mask as i32 & (libc::R_OK | libc::W_OK | libc::X_OK); if mode == libc::F_OK { @@ -1150,12 +1359,17 @@ impl FileSystem for PassthroughFs { let data = self.inode_map.get(inode)?; let file = data.get_file()?; + #[cfg(target_os = "linux")] let pathname = CString::new(format!("/proc/self/fd/{}", file.as_raw_fd())) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + #[cfg(target_os = "macos")] + let pathname = data.get_path()?; + // The f{set,get,remove,list}xattr functions don't work on an fd opened with `O_PATH` so we // need to use the {set,get,remove,list}xattr variants. // Safe because this doesn't modify any memory and we check the return value. + #[cfg(target_os = "linux")] let res = unsafe { libc::setxattr( pathname.as_ptr(), @@ -1165,6 +1379,17 @@ impl FileSystem for PassthroughFs { flags as libc::c_int, ) }; + #[cfg(target_os = "macos")] + let res = unsafe { + libc::setxattr( + pathname.as_ptr(), + name.as_ptr(), + value.as_ptr() as *const libc::c_void, + value.len(), + 0, + flags as libc::c_int, + ) + }; if res == 0 { Ok(()) } else { @@ -1186,18 +1411,34 @@ impl FileSystem for PassthroughFs { let data = self.inode_map.get(inode)?; let file = data.get_file()?; let mut buf = Vec::::with_capacity(size as usize); + #[cfg(target_os = "linux")] let pathname = CString::new(format!("/proc/self/fd/{}", file.as_raw_fd(),)) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + #[cfg(target_os = "macos")] + let pathname = data.get_path()?; + // The f{set,get,remove,list}xattr functions don't work on an fd opened with `O_PATH` so we // need to use the {set,get,remove,list}xattr variants. // Safe because this will only modify the contents of `buf`. + #[cfg(target_os = "linux")] + let res = unsafe { + libc::getxattr( + pathname.as_ptr(), + name.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_void, + size as libc::size_t, + ) + }; + #[cfg(target_os = "macos")] let res = unsafe { libc::getxattr( pathname.as_ptr(), name.as_ptr(), buf.as_mut_ptr() as *mut libc::c_void, size as libc::size_t, + 0, + 0, ) }; if res < 0 { @@ -1221,12 +1462,17 @@ impl FileSystem for PassthroughFs { let data = self.inode_map.get(inode)?; let file = data.get_file()?; let mut buf = Vec::::with_capacity(size as usize); + #[cfg(target_os = "linux")] let pathname = CString::new(format!("/proc/self/fd/{}", file.as_raw_fd())) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + #[cfg(target_os = "macos")] + let pathname = data.get_path()?; + // The f{set,get,remove,list}xattr functions don't work on an fd opened with `O_PATH` so we // need to use the {set,get,remove,list}xattr variants. // Safe because this will only modify the contents of `buf`. + #[cfg(target_os = "linux")] let res = unsafe { libc::listxattr( pathname.as_ptr(), @@ -1234,6 +1480,15 @@ impl FileSystem for PassthroughFs { size as libc::size_t, ) }; + #[cfg(target_os = "macos")] + let res = unsafe { + libc::listxattr( + pathname.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_char, + size as libc::size_t, + 0, + ) + }; if res < 0 { return Err(io::Error::last_os_error()); } @@ -1254,13 +1509,20 @@ impl FileSystem for PassthroughFs { let data = self.inode_map.get(inode)?; let file = data.get_file()?; + #[cfg(target_os = "linux")] let pathname = CString::new(format!("/proc/self/fd/{}", file.as_raw_fd())) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + #[cfg(target_os = "macos")] + let pathname = data.get_path()?; + // The f{set,get,remove,list}xattr functions don't work on an fd opened with `O_PATH` so we // need to use the {set,get,remove,list}xattr variants. // Safe because this doesn't modify any memory and we check the return value. + #[cfg(target_os = "linux")] let res = unsafe { libc::removexattr(pathname.as_ptr(), name.as_ptr()) }; + #[cfg(target_os = "macos")] + let res = unsafe { libc::removexattr(pathname.as_ptr(), name.as_ptr(), 0) }; if res == 0 { Ok(()) } else { @@ -1268,6 +1530,7 @@ impl FileSystem for PassthroughFs { } } + #[cfg(target_os = "linux")] fn fallocate( &self, _ctx: &Context, @@ -1323,13 +1586,7 @@ impl FileSystem for PassthroughFs { let (_guard, file) = data.get_file_mut(); // Safe because this doesn't modify any memory and we check the return value. - let res = unsafe { - libc::lseek( - file.as_raw_fd(), - offset as libc::off64_t, - whence as libc::c_int, - ) - }; + let res = unsafe { libc::lseek(file.as_raw_fd(), offset as OffT, whence as libc::c_int) }; if res < 0 { Err(io::Error::last_os_error()) } else { diff --git a/src/passthrough/util.rs b/src/passthrough/util.rs index 7fdc36c79..89cdefc91 100644 --- a/src/passthrough/util.rs +++ b/src/passthrough/util.rs @@ -298,7 +298,10 @@ mod tests { // 56 bit = 0 // 55~48 bit = 0000 0010 // 47~1 bit = 1 + #[cfg(target_os = "linux")] assert_eq!(unique_inode, 0x01000000000001); + #[cfg(target_os = "macos")] + assert_eq!(unique_inode, 0x00800000000001); let expect_inode_alt_key = generator.decode_unique_inode(unique_inode).unwrap(); assert_eq!(expect_inode_alt_key, inode_alt_key); @@ -326,7 +329,10 @@ mod tests { // 56 bit = 0 // 55~48 bit = 0000 0010 // 47~1 bit = 0x7fffffffffff + #[cfg(target_os = "linux")] assert_eq!(unique_inode, 0x017fffffffffff); + #[cfg(target_os = "macos")] + assert_eq!(unique_inode, 0x01ffffffffffff); let expect_inode_alt_key = generator.decode_unique_inode(unique_inode).unwrap(); assert_eq!(expect_inode_alt_key, inode_alt_key); } @@ -362,7 +368,10 @@ mod tests { // 56 bit = 1 // 55~48 bit = 0000 0001 // 47~1 bit = 3 + #[cfg(target_os = "linux")] assert_eq!(unique_inode, 0x80800000000003); + #[cfg(target_os = "macos")] + assert_eq!(format!("0x{:x}", unique_inode), "0x81000000000003"); let inode_alt_key = InodeId { ino: MAX_HOST_INO + 3, @@ -377,7 +386,10 @@ mod tests { // 56 bit = 1 // 55~48 bit = 0000 0010 // 47~1 bit = 4 + #[cfg(target_os = "linux")] assert_eq!(unique_inode, 0x81000000000004); + #[cfg(target_os = "macos")] + assert_eq!(unique_inode, 0x81800000000004); let inode_alt_key = InodeId { ino: u64::MAX, @@ -392,7 +404,10 @@ mod tests { // 56 bit = 1 // 55~48 bit = 0000 0001 // 47~1 bit = 5 + #[cfg(target_os = "linux")] assert_eq!(unique_inode, 0x80800000000005); + #[cfg(target_os = "macos")] + assert_eq!(format!("0x{:x}", unique_inode), "0x82000000000005"); } } diff --git a/tests/example/mod.rs b/tests/example/mod.rs index b2ed97ba8..af7211e1c 100644 --- a/tests/example/mod.rs +++ b/tests/example/mod.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#[cfg(all(feature = "fusedev", target_os = "linux"))] +#[cfg(all(feature = "fusedev"))] pub(crate) mod passthroughfs; #[cfg(all(feature = "fusedev", target_os = "macos"))] diff --git a/tests/example/passthroughfs.rs b/tests/example/passthroughfs.rs index 856c8a261..4f086667b 100644 --- a/tests/example/passthroughfs.rs +++ b/tests/example/passthroughfs.rs @@ -27,7 +27,9 @@ impl Daemon { pub fn new(src: &str, mountpoint: &str, thread_cnt: u32) -> Result { // create vfs let vfs = Vfs::new(VfsOptions { + #[cfg(target_os = "linux")] no_open: false, + #[cfg(target_os = "linux")] no_opendir: false, ..Default::default() }); diff --git a/tests/smoke.rs b/tests/smoke.rs index c3261f692..8fa15a8a4 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -3,20 +3,24 @@ // SPDX-License-Identifier: Apache-2.0 // -#[cfg(all(feature = "fusedev", target_os = "linux"))] +#[cfg(all(feature = "fusedev"))] #[macro_use] extern crate log; mod example; -#[cfg(all(feature = "fusedev", target_os = "linux"))] +#[cfg(feature = "fusedev")] mod fusedev_tests { use std::io::Result; use std::path::Path; use std::process::Command; + #[cfg(target_os = "linux")] use vmm_sys_util::tempdir::TempDir; + #[cfg(target_os = "macos")] + use tempfile::{tempdir, tempdir_in, NamedTempFile, TempDir}; + use crate::example::passthroughfs; fn validate_two_git_directory(src: &str, dest: &str) -> bool { @@ -37,7 +41,6 @@ mod fusedev_tests { ); return false; } - let src_md5 = exec( format!( "cd {}; git ls-files --recurse-submodules | grep --invert-match rust-vmm-ci | xargs md5sum; cd - > /dev/null", @@ -85,7 +88,16 @@ mod fusedev_tests { // test the fuse-rs repository let src = Path::new(".").canonicalize().unwrap(); let src_dir = src.to_str().unwrap(); + + #[cfg(target_os = "linux")] let tmp_dir = TempDir::new().unwrap(); + + #[cfg(target_os = "macos")] + let tmp_dir = { + let source = tempdir().expect("Cannot create temporary directory."); + source.into_path() + }; + let mnt_dir = tmp_dir.as_path().to_str().unwrap(); info!( "test passthroughfs src {:?} mountpoint {}", From 28f93e340077fb509828a08d0f2ae8d00fd3222b Mon Sep 17 00:00:00 2001 From: akitaSummer <644171127@qq.com> Date: Mon, 15 Jan 2024 23:24:33 +0800 Subject: [PATCH 4/8] fix: fix CI Signed-off-by: akitaSummer <644171127@qq.com> --- src/passthrough/inode_store.rs | 5 +- src/passthrough/mod.rs | 32 +++++++--- src/passthrough/sync_io.rs | 103 ++++++++++++++++++++------------- src/passthrough/util.rs | 8 ++- 4 files changed, 97 insertions(+), 51 deletions(-) diff --git a/src/passthrough/inode_store.rs b/src/passthrough/inode_store.rs index c9a036646..b0be5e2ae 100644 --- a/src/passthrough/inode_store.rs +++ b/src/passthrough/inode_store.rs @@ -10,7 +10,10 @@ use super::file_handle::FileHandle; use super::stat::Stat; #[cfg(target_os = "linux")] use super::statx::StatExt; -use super::{InoT, Inode, InodeData, InodeHandle}; + +#[cfg(target_os = "linux")] +use super::InodeHandle; +use super::{InoT, Inode, InodeData}; #[derive(Clone, Copy, Default, PartialOrd, Ord, PartialEq, Eq, Debug)] /// Identify an inode in `PassthroughFs` by `InodeId`. diff --git a/src/passthrough/mod.rs b/src/passthrough/mod.rs index 506582a5d..244e9b832 100644 --- a/src/passthrough/mod.rs +++ b/src/passthrough/mod.rs @@ -13,12 +13,15 @@ use std::any::Any; use std::collections::{btree_map, BTreeMap}; -use std::ffi::{CStr, CString, OsString}; +#[cfg(target_os = "linux")] +use std::ffi::OsString; +use std::ffi::{CStr, CString}; use std::fs::File; use std::io; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::os::fd::{AsFd, BorrowedFd}; +#[cfg(target_os = "linux")] use std::os::unix::ffi::OsStringExt; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::PathBuf; @@ -26,15 +29,17 @@ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard}; use std::time::Duration; -use vm_memory::{bitmap::BitmapSlice, ByteValued}; - pub use self::config::{CachePolicy, Config}; #[cfg(target_os = "linux")] use self::file_handle::{FileHandle, OpenableFileHandle}; use self::inode_store::{InodeId, InodeStore}; #[cfg(target_os = "linux")] use self::mount_fd::MountFds; +use vm_memory::bitmap::BitmapSlice; +#[cfg(target_os = "linux")] +use vm_memory::ByteValued; +#[cfg(target_os = "macos")] use self::stat::{open, stat, Stat}; #[cfg(target_os = "linux")] use self::statx::{statx, StatExt}; @@ -46,9 +51,11 @@ use self::util::{reopen_fd_through_proc, stat_fd}; use crate::abi::fuse_abi as fuse; use crate::abi::fuse_abi::Opcode; use crate::api::filesystem::Entry; +#[cfg(target_os = "linux")] +use crate::api::PROC_SELF_FD_CSTR; use crate::api::{ validate_path_component, BackendFileSystem, CURRENT_DIR_CSTR, EMPTY_CSTR, PARENT_DIR_CSTR, - PROC_SELF_FD_CSTR, SLASH_ASCII, VFS_MAX_INO, + SLASH_ASCII, VFS_MAX_INO, }; #[cfg(feature = "async-io")] @@ -108,6 +115,7 @@ const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff; */ #[derive(Debug)] enum InodeFile<'a> { + #[cfg(target_os = "linux")] Owned(File), Ref(&'a File), } @@ -117,6 +125,7 @@ impl AsRawFd for InodeFile<'_> { /// Note: This fd is only valid as long as the `InodeFile` exists. fn as_raw_fd(&self) -> RawFd { match self { + #[cfg(target_os = "linux")] Self::Owned(file) => file.as_raw_fd(), Self::Ref(file_ref) => file_ref.as_raw_fd(), } @@ -126,6 +135,7 @@ impl AsRawFd for InodeFile<'_> { impl AsFd for InodeFile<'_> { fn as_fd(&self) -> BorrowedFd<'_> { match self { + #[cfg(target_os = "linux")] Self::Owned(file) => file.as_fd(), Self::Ref(file_ref) => file_ref.as_fd(), } @@ -490,6 +500,7 @@ pub struct PassthroughFs { no_opendir: AtomicBool, // Whether kill_priv_v2 is enabled. + #[cfg(target_os = "linux")] killpriv_v2: AtomicBool, // Whether no_readdir is enabled. @@ -562,6 +573,7 @@ impl PassthroughFs { writeback: AtomicBool::new(false), no_open: AtomicBool::new(false), no_opendir: AtomicBool::new(false), + #[cfg(target_os = "linux")] killpriv_v2: AtomicBool::new(false), no_readdir: AtomicBool::new(cfg.no_readdir), seal_size: AtomicBool::new(cfg.seal_size), @@ -995,7 +1007,8 @@ impl PassthroughFs { file_size: u64, offset: u64, size: u64, - mode: i32, + #[cfg(target_os = "linux")] mode: i32, + #[cfg(target_os = "macos")] _mode: i32, ) -> io::Result<()> { if offset.checked_add(size).is_none() { error!( @@ -1121,8 +1134,8 @@ macro_rules! scoped_cred { fn drop(&mut self) { #[cfg(target_os = "linux")] let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) }; - let res = unsafe { $syscall_nr(0) }; #[cfg(target_os = "macos")] + let res = unsafe { $syscall_nr(0) }; if res < 0 { error!( "fuse: failed to change credentials back to root: {}", @@ -1226,15 +1239,18 @@ mod tests { use super::*; use crate::abi::fuse_abi::CreateIn; use crate::api::filesystem::*; + #[cfg(target_os = "linux")] use crate::api::{Vfs, VfsOptions}; #[cfg(target_os = "linux")] use caps::{CapSet, Capability}; use log; use std::io::Read; + #[cfg(target_os = "linux")] use std::ops::Deref; use std::os::unix::prelude::MetadataExt; use std::fs; + #[cfg(target_os = "linux")] use std::fs::Permissions; use std::os::unix::fs::PermissionsExt; @@ -1242,7 +1258,7 @@ mod tests { use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile}; #[cfg(target_os = "macos")] - use tempfile::{tempdir, tempdir_in, NamedTempFile, TempDir}; + use tempfile::{tempdir, tempdir_in, NamedTempFile}; #[cfg(target_os = "linux")] fn prepare_passthroughfs() -> PassthroughFs { @@ -1276,7 +1292,7 @@ mod tests { .expect("Failed to get directory metadata") .permissions(); permissions.set_mode(0o40777); - let r = permissions.mode(); + let _r = permissions.mode(); set_permissions_recursive(&dir_path).expect("Failed to set permissions"); } diff --git a/src/passthrough/sync_io.rs b/src/passthrough/sync_io.rs index 48eedeee6..a414f7200 100644 --- a/src/passthrough/sync_io.rs +++ b/src/passthrough/sync_io.rs @@ -8,7 +8,9 @@ use std::ffi::{CStr, CString}; use std::fs::File; use std::io; -use std::mem::{self, size_of, ManuallyDrop, MaybeUninit}; +#[cfg(target_os = "linux")] +use std::mem::size_of; +use std::mem::{self, ManuallyDrop, MaybeUninit}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -19,9 +21,9 @@ use super::os_compat::LinuxDirent64; #[cfg(target_os = "linux")] use super::util::stat_fd; use super::*; +use crate::abi::fuse_abi::{CreateIn, Opcode}; #[cfg(target_os = "linux")] -use crate::abi::fuse_abi::WRITE_KILL_PRIV; -use crate::abi::fuse_abi::{CreateIn, Opcode, FOPEN_IN_KILL_SUIDGID}; +use crate::abi::fuse_abi::{FOPEN_IN_KILL_SUIDGID, WRITE_KILL_PRIV}; #[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] use crate::abi::virtio_fs; use crate::api::filesystem::{ @@ -224,7 +226,10 @@ impl PassthroughFs { add_entry( DirEntry { ino: dirent.d_ino, + #[cfg(target_os = "linux")] offset: dirent.d_seekoff as u64, + #[cfg(target_os = "macos")] + offset: dirent.d_seekoff, type_: dirent.d_type as u32, name, }, @@ -252,7 +257,8 @@ impl PassthroughFs { &self, inode: Inode, flags: u32, - fuse_flags: u32, + #[cfg(target_os = "linux")] fuse_flags: u32, + #[cfg(target_os = "macos")] _fuse_flags: u32, ) -> io::Result<(Option, OpenOptions, Option)> { #[cfg(target_os = "linux")] let killpriv = if self.killpriv_v2.load(Ordering::Relaxed) @@ -392,52 +398,58 @@ impl FileSystem for PassthroughFs { type Inode = Inode; type Handle = Handle; + #[cfg(target_os = "linux")] fn init(&self, capable: FsOptions) -> io::Result { if self.cfg.do_import { self.import()?; } - #[cfg(target_os = "linux")] let mut opts = FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO; // !cfg.do_import means we are under vfs, in which case capable is already // negotiated and must be honored. - #[cfg(target_os = "linux")] + + if (!self.cfg.do_import || self.cfg.writeback) + && capable.contains(FsOptions::WRITEBACK_CACHE) { - if (!self.cfg.do_import || self.cfg.writeback) - && capable.contains(FsOptions::WRITEBACK_CACHE) - { - opts |= FsOptions::WRITEBACK_CACHE; - self.writeback.store(true, Ordering::Relaxed); - } - if (!self.cfg.do_import || self.cfg.no_open) - && capable.contains(FsOptions::ZERO_MESSAGE_OPEN) - { - opts |= FsOptions::ZERO_MESSAGE_OPEN; - // We can't support FUSE_ATOMIC_O_TRUNC with no_open - opts.remove(FsOptions::ATOMIC_O_TRUNC); - self.no_open.store(true, Ordering::Relaxed); - } - if (!self.cfg.do_import || self.cfg.no_opendir) - && capable.contains(FsOptions::ZERO_MESSAGE_OPENDIR) - { - opts |= FsOptions::ZERO_MESSAGE_OPENDIR; - self.no_opendir.store(true, Ordering::Relaxed); - } - if (!self.cfg.do_import || self.cfg.killpriv_v2) - && capable.contains(FsOptions::HANDLE_KILLPRIV_V2) - { - opts |= FsOptions::HANDLE_KILLPRIV_V2; - self.killpriv_v2.store(true, Ordering::Relaxed); - } + opts |= FsOptions::WRITEBACK_CACHE; + self.writeback.store(true, Ordering::Relaxed); + } + if (!self.cfg.do_import || self.cfg.no_open) + && capable.contains(FsOptions::ZERO_MESSAGE_OPEN) + { + opts |= FsOptions::ZERO_MESSAGE_OPEN; + // We can't support FUSE_ATOMIC_O_TRUNC with no_open + opts.remove(FsOptions::ATOMIC_O_TRUNC); + self.no_open.store(true, Ordering::Relaxed); + } + if (!self.cfg.do_import || self.cfg.no_opendir) + && capable.contains(FsOptions::ZERO_MESSAGE_OPENDIR) + { + opts |= FsOptions::ZERO_MESSAGE_OPENDIR; + self.no_opendir.store(true, Ordering::Relaxed); + } + if (!self.cfg.do_import || self.cfg.killpriv_v2) + && capable.contains(FsOptions::HANDLE_KILLPRIV_V2) + { + opts |= FsOptions::HANDLE_KILLPRIV_V2; + self.killpriv_v2.store(true, Ordering::Relaxed); + } - if capable.contains(FsOptions::PERFILE_DAX) { - opts |= FsOptions::PERFILE_DAX; - self.perfile_dax.store(true, Ordering::Relaxed); - } + if capable.contains(FsOptions::PERFILE_DAX) { + opts |= FsOptions::PERFILE_DAX; + self.perfile_dax.store(true, Ordering::Relaxed); } - #[cfg(target_os = "macos")] - let mut opts = FsOptions::FILE_OPS; + Ok(opts) + } + + #[cfg(target_os = "macos")] + fn init(&self, _capable: FsOptions) -> io::Result { + if self.cfg.do_import { + self.import()?; + } + + let opts = FsOptions::FILE_OPS; Ok(opts) } @@ -803,7 +815,8 @@ impl FileSystem for PassthroughFs { _lock_owner: Option, _delayed_write: bool, flags: u32, - fuse_flags: u32, + #[cfg(target_os = "linux")] fuse_flags: u32, + #[cfg(target_os = "macos")] _fuse_flags: u32, ) -> io::Result { let data = self.get_data(handle, inode, libc::O_RDWR)?; @@ -1051,7 +1064,8 @@ impl FileSystem for PassthroughFs { oldname: &CStr, newdir: Inode, newname: &CStr, - flags: u32, + #[cfg(target_os = "linux")] flags: u32, + #[cfg(target_os = "macos")] _flags: u32, ) -> io::Result<()> { self.validate_path_component(oldname)?; self.validate_path_component(newname)?; @@ -1104,7 +1118,9 @@ impl FileSystem for PassthroughFs { self.validate_path_component(name)?; let data = self.inode_map.get(parent)?; + #[cfg(target_os = "linux")] let file = data.get_file()?; + #[cfg(target_os = "macos")] let pathname = data.get_path()?; let res = { @@ -1266,7 +1282,8 @@ impl FileSystem for PassthroughFs { &self, _ctx: &Context, inode: Inode, - datasync: bool, + #[cfg(target_os = "linux")] datasync: bool, + #[cfg(target_os = "macos")] _datasync: bool, handle: Handle, ) -> io::Result<()> { let data = self.get_data(handle, inode, libc::O_RDONLY)?; @@ -1358,6 +1375,7 @@ impl FileSystem for PassthroughFs { } let data = self.inode_map.get(inode)?; + #[cfg(target_os = "linux")] let file = data.get_file()?; #[cfg(target_os = "linux")] let pathname = CString::new(format!("/proc/self/fd/{}", file.as_raw_fd())) @@ -1409,6 +1427,7 @@ impl FileSystem for PassthroughFs { } let data = self.inode_map.get(inode)?; + #[cfg(target_os = "linux")] let file = data.get_file()?; let mut buf = Vec::::with_capacity(size as usize); #[cfg(target_os = "linux")] @@ -1460,6 +1479,7 @@ impl FileSystem for PassthroughFs { } let data = self.inode_map.get(inode)?; + #[cfg(target_os = "linux")] let file = data.get_file()?; let mut buf = Vec::::with_capacity(size as usize); #[cfg(target_os = "linux")] @@ -1508,6 +1528,7 @@ impl FileSystem for PassthroughFs { } let data = self.inode_map.get(inode)?; + #[cfg(target_os = "linux")] let file = data.get_file()?; #[cfg(target_os = "linux")] let pathname = CString::new(format!("/proc/self/fd/{}", file.as_raw_fd())) diff --git a/src/passthrough/util.rs b/src/passthrough/util.rs index 89cdefc91..3f2a810c5 100644 --- a/src/passthrough/util.rs +++ b/src/passthrough/util.rs @@ -3,9 +3,13 @@ // Copyright (C) 2023 Alibaba Cloud. All rights reserved. use std::collections::{btree_map, BTreeMap}; -use std::ffi::{CStr, CString}; +use std::ffi::CStr; +#[cfg(target_os = "linux")] +use std::ffi::CString; use std::fs::File; use std::io; + +#[cfg(target_os = "linux")] use std::mem::MaybeUninit; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::sync::atomic::{AtomicU64, AtomicU8, Ordering}; @@ -14,6 +18,8 @@ use std::sync::Mutex; use super::inode_store::InodeId; use super::{InoT, InodeMode, MAX_HOST_INO}; use crate::abi::fuse_abi as fuse; + +#[cfg(target_os = "linux")] use crate::api::EMPTY_CSTR; /// the 56th bit used to set the inode to 1 indicates virtual inode From fbd70b9fbc5b86346866278211c2d9c519592e69 Mon Sep 17 00:00:00 2001 From: akitaSummer <644171127@qq.com> Date: Sun, 21 Jan 2024 01:19:41 +0800 Subject: [PATCH 5/8] refactor: split some logic Signed-off-by: akitaSummer <644171127@qq.com> --- src/api/server/sync_io.rs | 4 +- src/passthrough/file_handle.rs | 2 + src/passthrough/inode_store.rs | 16 +- src/passthrough/mod.rs | 516 ++---------------------- src/passthrough/passthrough_fs_linux.rs | 327 +++++++++++++++ src/passthrough/passthrough_fs_macos.rs | 149 +++++++ src/passthrough/sync_io.rs | 237 +---------- src/passthrough/sync_io_linux.rs | 168 ++++++++ src/passthrough/sync_io_macos.rs | 153 +++++++ tests/smoke.rs | 2 +- 10 files changed, 847 insertions(+), 727 deletions(-) create mode 100644 src/passthrough/passthrough_fs_linux.rs create mode 100644 src/passthrough/passthrough_fs_macos.rs create mode 100644 src/passthrough/sync_io_linux.rs create mode 100644 src/passthrough/sync_io_macos.rs diff --git a/src/api/server/sync_io.rs b/src/api/server/sync_io.rs index d9925cca7..0330678e3 100644 --- a/src/api/server/sync_io.rs +++ b/src/api/server/sync_io.rs @@ -143,9 +143,9 @@ impl Server { x if x == Opcode::Rename2 as u32 => self.rename2(ctx), #[cfg(target_os = "linux")] x if x == Opcode::Lseek as u32 => self.lseek(ctx), - #[cfg(feature = "virtiofs")] + #[cfg(all(target_os = "linux", feature = "virtiofs"))] x if x == Opcode::SetupMapping as u32 => self.setupmapping(ctx, vu_req), - #[cfg(feature = "virtiofs")] + #[cfg(all(target_os = "linux", feature = "virtiofs"))] x if x == Opcode::RemoveMapping as u32 => self.removemapping(ctx, vu_req), // Group reqeusts don't need reply together x => match x { diff --git a/src/passthrough/file_handle.rs b/src/passthrough/file_handle.rs index 5eb00c282..fa5379470 100644 --- a/src/passthrough/file_handle.rs +++ b/src/passthrough/file_handle.rs @@ -320,8 +320,10 @@ impl OpenableFileHandle { #[cfg(test)] mod tests { use super::*; + #[cfg(target_os = "macos")] use nix::unistd::getuid; use std::ffi::CString; + #[cfg(target_os = "macos")] use std::io::Read; fn generate_c_file_handle( diff --git a/src/passthrough/inode_store.rs b/src/passthrough/inode_store.rs index b0be5e2ae..20eddd13f 100644 --- a/src/passthrough/inode_store.rs +++ b/src/passthrough/inode_store.rs @@ -7,7 +7,7 @@ use std::sync::Arc; #[cfg(target_os = "linux")] use super::file_handle::FileHandle; #[cfg(target_os = "macos")] -use super::stat::Stat; +use super::stat::Stat as StatExt; #[cfg(target_os = "linux")] use super::statx::StatExt; @@ -25,24 +25,15 @@ pub struct InodeId { } impl InodeId { - #[cfg(target_os = "linux")] #[inline] pub(super) fn from_stat(st: &StatExt) -> Self { InodeId { ino: st.st.st_ino, dev: st.st.st_dev, + #[cfg(target_os = "linux")] mnt: st.mnt_id, } } - - #[cfg(target_os = "macos")] - #[inline] - pub(super) fn from_stat(st: &Stat) -> Self { - InodeId { - ino: st.st.st_ino, - dev: st.st.st_dev, - } - } } #[derive(Default)] @@ -125,7 +116,9 @@ mod test { use super::super::*; use super::*; + #[cfg(target_os = "linux")] use std::ffi::CStr; + #[cfg(target_os = "linux")] use std::mem::MaybeUninit; use std::os::unix::io::AsRawFd; use std::sync::atomic::Ordering; @@ -134,6 +127,7 @@ mod test { #[cfg(target_os = "linux")] use vmm_sys_util::tempfile::TempFile; + #[cfg(target_os = "macos")] use stat::stat; impl PartialEq for InodeData { diff --git a/src/passthrough/mod.rs b/src/passthrough/mod.rs index 244e9b832..cf035ee07 100644 --- a/src/passthrough/mod.rs +++ b/src/passthrough/mod.rs @@ -10,20 +10,18 @@ //! The code is derived from the //! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project, //! with heavy modification/enhancements from Alibaba Cloud OS team. +#![allow(missing_docs)] use std::any::Any; use std::collections::{btree_map, BTreeMap}; -#[cfg(target_os = "linux")] -use std::ffi::OsString; use std::ffi::{CStr, CString}; use std::fs::File; use std::io; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::os::fd::{AsFd, BorrowedFd}; -#[cfg(target_os = "linux")] -use std::os::unix::ffi::OsStringExt; use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(target_os = "macos")] use std::path::PathBuf; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard}; @@ -31,25 +29,16 @@ use std::time::Duration; pub use self::config::{CachePolicy, Config}; #[cfg(target_os = "linux")] -use self::file_handle::{FileHandle, OpenableFileHandle}; +use self::file_handle::FileHandle; use self::inode_store::{InodeId, InodeStore}; #[cfg(target_os = "linux")] use self::mount_fd::MountFds; use vm_memory::bitmap::BitmapSlice; -#[cfg(target_os = "linux")] -use vm_memory::ByteValued; #[cfg(target_os = "macos")] -use self::stat::{open, stat, Stat}; -#[cfg(target_os = "linux")] -use self::statx::{statx, StatExt}; -use self::util::{ - ebadf, einval, enosys, eperm, is_dir, is_safe_inode, openat, UniqueInodeGenerator, -}; -#[cfg(target_os = "linux")] -use self::util::{reopen_fd_through_proc, stat_fd}; +use self::stat::stat; +use self::util::{ebadf, einval, enosys, is_dir, is_safe_inode, openat, UniqueInodeGenerator}; use crate::abi::fuse_abi as fuse; -use crate::abi::fuse_abi::Opcode; use crate::api::filesystem::Entry; #[cfg(target_os = "linux")] use crate::api::PROC_SELF_FD_CSTR; @@ -75,33 +64,23 @@ mod statx; mod sync_io; mod util; -type Inode = u64; -type Handle = u64; - -#[cfg(target_os = "linux")] -type InoT = libc::ino64_t; -#[cfg(target_os = "macos")] -type InoT = libc::ino_t; - #[cfg(target_os = "linux")] -type InodeMode = u32; +mod passthrough_fs_linux; #[cfg(target_os = "macos")] -type InodeMode = u16; +mod passthrough_fs_macos; #[cfg(target_os = "linux")] -type LibCStat = libc::stat64; +mod sync_io_linux; #[cfg(target_os = "macos")] -type LibCStat = libc::stat; +mod sync_io_macos; #[cfg(target_os = "linux")] -type OffT = libc::off64_t; +pub use passthrough_fs_linux::*; #[cfg(target_os = "macos")] -type OffT = libc::off_t; +pub use passthrough_fs_macos::*; -#[cfg(target_os = "linux")] -type StatVfs = libc::statvfs64; -#[cfg(target_os = "macos")] -type StatVfs = libc::statvfs; +type Inode = u64; +type Handle = u64; /// Maximum host inode number supported by passthroughfs const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff; @@ -114,7 +93,7 @@ const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff; * case the object's lifetime is that of the respective `InodeData` object. */ #[derive(Debug)] -enum InodeFile<'a> { +pub enum InodeFile<'a> { #[cfg(target_os = "linux")] Owned(File), Ref(&'a File), @@ -142,85 +121,6 @@ impl AsFd for InodeFile<'_> { } } -#[derive(Debug)] -#[cfg(target_os = "linux")] -enum InodeHandle { - File(File), - Handle(Arc), -} - -#[derive(Debug)] -#[cfg(target_os = "macos")] -enum InodeHandle { - File(File, CString), -} - -impl InodeHandle { - #[cfg(target_os = "linux")] - fn file_handle(&self) -> Option<&FileHandle> { - match self { - InodeHandle::File(_) => None, - InodeHandle::Handle(h) => Some(h.file_handle().deref()), - } - } - - fn get_file(&self) -> io::Result> { - #[cfg(target_os = "linux")] - match self { - InodeHandle::File(f) => Ok(InodeFile::Ref(f)), - InodeHandle::Handle(h) => { - let f = h.open(libc::O_PATH)?; - Ok(InodeFile::Owned(f)) - } - } - #[cfg(target_os = "macos")] - match self { - InodeHandle::File(f, _) => Ok(InodeFile::Ref(f)), - } - } - - fn open_file( - &self, - flags: libc::c_int, - #[cfg(target_os = "linux")] proc_self_fd: &File, - ) -> io::Result { - #[cfg(target_os = "linux")] - match self { - InodeHandle::File(f) => reopen_fd_through_proc(f, flags, proc_self_fd), - InodeHandle::Handle(h) => h.open(flags), - } - #[cfg(target_os = "macos")] - match self { - InodeHandle::File(_, pathname) => open(pathname, flags, 0), - } - } - - #[cfg(target_os = "linux")] - fn stat(&self) -> io::Result { - match self { - InodeHandle::File(f) => stat_fd(f, None), - InodeHandle::Handle(_h) => { - let file = self.get_file()?; - stat_fd(&file, None) - } - } - } - - #[cfg(target_os = "macos")] - fn stat(&self) -> io::Result { - match self { - InodeHandle::File(f, _) => stat(f), - } - } - - #[cfg(target_os = "macos")] - fn get_path(&self) -> io::Result { - match self { - InodeHandle::File(_, pathname) => Ok(pathname.clone()), - } - } -} - /// Represents an inode in `PassthroughFs`. #[derive(Debug)] pub struct InodeData { @@ -247,21 +147,6 @@ impl InodeData { fn get_file(&self) -> io::Result> { self.handle.get_file() } - - #[cfg(target_os = "linux")] - fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result { - self.handle.open_file(flags, proc_self_fd) - } - - #[cfg(target_os = "macos")] - fn open_file(&self, flags: libc::c_int) -> io::Result { - self.handle.open_file(flags) - } - - #[cfg(target_os = "macos")] - fn get_path(&self) -> io::Result { - self.handle.get_path() - } } /// Data structures to manage accessed inodes. @@ -291,67 +176,6 @@ impl InodeMap { .ok_or_else(ebadf) } - #[cfg(target_os = "linux")] - fn get_inode_locked( - inodes: &InodeStore, - id: &InodeId, - handle: Option<&FileHandle>, - ) -> Option { - match handle { - Some(h) => inodes.inode_by_handle(h).copied(), - None => inodes.inode_by_id(id).copied(), - } - } - - #[cfg(target_os = "macos")] - fn get_inode_locked(inodes: &InodeStore, id: &InodeId) -> Option { - inodes.inode_by_id(id).copied() - } - - #[cfg(target_os = "linux")] - fn get_alt(&self, id: &InodeId, handle: Option<&FileHandle>) -> Option> { - // Do not expect poisoned lock here, so safe to unwrap(). - let inodes = self.inodes.read().unwrap(); - - Self::get_alt_locked(inodes.deref(), id, handle) - } - - #[cfg(target_os = "macos")] - fn get_alt(&self, id: &InodeId) -> Option> { - let inodes = self.inodes.read().unwrap(); - - Self::get_alt_locked(inodes.deref(), id) - } - - #[cfg(target_os = "linux")] - fn get_alt_locked( - inodes: &InodeStore, - id: &InodeId, - handle: Option<&FileHandle>, - ) -> Option> { - handle - .and_then(|h| inodes.get_by_handle(h)) - .or_else(|| { - inodes.get_by_id(id).filter(|data| { - // When we have to fall back to looking up an inode by its IDs, ensure that - // we hit an entry that does not have a file handle. Entries with file - // handles must also have a handle alt key, so if we have not found it by - // that handle alt key, we must have found an entry with a mismatching - // handle; i.e. an entry for a different file, even though it has the same - // inode ID. - // (This can happen when we look up a new file that has reused the inode ID - // of some previously unlinked inode we still have in `.inodes`.) - handle.is_none() || data.handle.file_handle().is_none() - }) - }) - .map(Arc::clone) - } - - #[cfg(target_os = "macos")] - fn get_alt_locked(inodes: &InodeStore, id: &InodeId) -> Option> { - inodes.get_by_id(id).map(Arc::clone) - } - fn get_map_mut(&self) -> RwLockWriteGuard { // Do not expect poisoned lock here, so safe to unwrap(). self.inodes.write().unwrap() @@ -368,7 +192,7 @@ impl InodeMap { } } -struct HandleData { +pub struct HandleData { inode: Inode, file: File, lock: Mutex<()>, @@ -591,26 +415,33 @@ impl PassthroughFs { let root = CString::new(self.cfg.root_dir.as_str()).expect("CString::new failed"); #[cfg(target_os = "linux")] - let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &libc::AT_FDCWD, &root) - .map_err(|e| { - error!("fuse: import: failed to get file or handle: {:?}", e); - e - })?; + let (st, id, handle) = { + let (path_fd, handle_opt, st) = + Self::open_file_and_handle(self, &libc::AT_FDCWD, &root).map_err(|e| { + error!("fuse: import: failed to get file or handle: {:?}", e); + e + })?; - #[cfg(target_os = "macos")] - let (path_fd, st) = self.open_file(&root).unwrap(); + let id = InodeId::from_stat(&st); - let id = InodeId::from_stat(&st); + let handle = if let Some(h) = handle_opt { + InodeHandle::Handle(self.to_openable_handle(h)?) + } else { + InodeHandle::File(path_fd) + }; - #[cfg(target_os = "linux")] - let handle = if let Some(h) = handle_opt { - InodeHandle::Handle(self.to_openable_handle(h)?) - } else { - InodeHandle::File(path_fd) + (st, id, handle) }; #[cfg(target_os = "macos")] - let handle = InodeHandle::File(path_fd, root); + let (st, id, handle) = { + let (path_fd, st) = self.open_file(&root).unwrap(); + + let id = InodeId::from_stat(&st); + + let handle = InodeHandle::File(path_fd, root); + (st, id, handle) + }; // Safe because this doesn't modify any memory and there is no need to check the return // value because this system call always succeeds. We need to clear the umask here because @@ -629,52 +460,6 @@ impl PassthroughFs { Ok(()) } - /// Get the list of file descriptors which should be reserved across live upgrade. - #[cfg(target_os = "linux")] - pub fn keep_fds(&self) -> Vec { - vec![self.proc_self_fd.as_raw_fd()] - } - - #[cfg(target_os = "linux")] - fn readlinkat(dfd: i32, pathname: &CStr) -> io::Result { - let mut buf = Vec::with_capacity(libc::PATH_MAX as usize); - - // Safe because the kernel will only write data to buf and we check the return value - let buf_read = unsafe { - libc::readlinkat( - dfd, - pathname.as_ptr(), - buf.as_mut_ptr() as *mut libc::c_char, - buf.capacity(), - ) - }; - if buf_read < 0 { - error!("fuse: readlinkat error"); - return Err(io::Error::last_os_error()); - } - - // Safe because we trust the value returned by kernel. - unsafe { buf.set_len(buf_read as usize) }; - buf.shrink_to_fit(); - - // Be careful: - // - readlink() does not append a terminating null byte to buf - // - OsString instances are not NUL terminated - Ok(PathBuf::from(OsString::from_vec(buf))) - } - - /// Get the file pathname corresponding to the Inode - /// This function is used by Nydus blobfs - #[cfg(target_os = "linux")] - pub fn readlinkat_proc_file(&self, inode: Inode) -> io::Result { - let data = self.inode_map.get(inode)?; - let file = data.get_file()?; - let pathname = CString::new(format!("{}", file.as_raw_fd())) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - - Self::readlinkat(self.proc_self_fd.as_raw_fd(), &pathname) - } - fn create_file_excl( dir: &impl AsRawFd, pathname: &CStr, @@ -695,110 +480,6 @@ impl PassthroughFs { } } } - #[cfg(target_os = "linux")] - fn open_file(dfd: &impl AsRawFd, pathname: &CStr, flags: i32, mode: u32) -> io::Result { - openat(dfd, pathname, flags, mode) - } - - #[cfg(target_os = "macos")] - fn open_file(&self, pathname: &CStr) -> io::Result<(File, Stat)> { - let path_file = self.open_file_restricted(pathname, libc::O_NOFOLLOW, 0o40777)?; - let st = stat(&path_file)?; - - Ok((path_file, st)) - } - - #[cfg(target_os = "linux")] - fn open_file_restricted( - &self, - dir: &impl AsRawFd, - pathname: &CStr, - flags: i32, - mode: u32, - ) -> io::Result { - let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags; - - // TODO - //if self.os_facts.has_openat2 { - // oslib::do_open_relative_to(dir, pathname, flags, mode) - //} else { - openat(dir, pathname, flags, mode) - //} - } - - #[cfg(target_os = "macos")] - fn open_file_restricted(&self, pathname: &CStr, flags: i32, mode: u32) -> io::Result { - let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags; - open(pathname, flags, mode) - } - - /// Create a File or File Handle for `name` under directory `dir_fd` to support `lookup()`. - #[cfg(target_os = "linux")] - fn open_file_and_handle( - &self, - dir: &impl AsRawFd, - name: &CStr, - ) -> io::Result<(File, Option, StatExt)> { - let path_file = self.open_file_restricted(dir, name, libc::O_PATH, 0)?; - let st = statx(&path_file, None)?; - let handle = if self.cfg.inode_file_handles { - FileHandle::from_fd(&path_file)? - } else { - None - }; - - Ok((path_file, handle, st)) - } - - #[cfg(target_os = "linux")] - fn to_openable_handle(&self, fh: FileHandle) -> io::Result> { - fh.into_openable(&self.mount_fds, |fd, flags, _mode| { - reopen_fd_through_proc(&fd, flags, &self.proc_self_fd) - }) - .map(Arc::new) - .map_err(|e| { - if !e.silent() { - error!("{}", e); - } - e.into_inner() - }) - } - - fn allocate_inode( - &self, - inodes: &InodeStore, - id: &InodeId, - #[cfg(target_os = "linux")] handle_opt: Option<&FileHandle>, - ) -> io::Result { - if !self.cfg.use_host_ino { - // If the inode has already been assigned before, the new inode is not reassigned, - // ensuring that the same file is always the same inode - #[cfg(target_os = "linux")] - return Ok(InodeMap::get_inode_locked(inodes, id, handle_opt) - .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))); - #[cfg(target_os = "macos")] - return Ok(InodeMap::get_inode_locked(inodes, id) - .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))); - } else { - let inode = if id.ino > MAX_HOST_INO { - // Prefer looking for previous mappings from memory - #[cfg(target_os = "linux")] - match InodeMap::get_inode_locked(inodes, id, handle_opt) { - Some(ino) => ino, - None => self.ino_allocator.get_unique_inode(id)?, - } - #[cfg(target_os = "macos")] - match InodeMap::get_inode_locked(inodes, id) { - Some(ino) => ino, - None => self.ino_allocator.get_unique_inode(id)?, - } - } else { - self.ino_allocator.get_unique_inode(id)? - }; - - Ok(inode) - } - } fn do_lookup(&self, parent: Inode, name: &CStr) -> io::Result { let name = @@ -999,59 +680,6 @@ impl PassthroughFs { validate_path_component(name) } - // When seal_size is set, we don't allow operations that could change file size nor allocate - // space beyond EOF - fn seal_size_check( - &self, - opcode: Opcode, - file_size: u64, - offset: u64, - size: u64, - #[cfg(target_os = "linux")] mode: i32, - #[cfg(target_os = "macos")] _mode: i32, - ) -> io::Result<()> { - if offset.checked_add(size).is_none() { - error!( - "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX", - opcode, offset, size - ); - return Err(einval()); - } - - match opcode { - // write should not exceed the file size. - Opcode::Write => { - if size + offset > file_size { - return Err(eperm()); - } - } - - #[cfg(target_os = "linux")] - Opcode::Fallocate => { - let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE); - match op { - // Allocate, punch and zero, must not change file size. - 0 | libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_ZERO_RANGE => { - if size + offset > file_size { - return Err(eperm()); - } - } - // collapse and insert will change file size, forbid. - libc::FALLOC_FL_COLLAPSE_RANGE | libc::FALLOC_FL_INSERT_RANGE => { - return Err(eperm()); - } - // Invalid operation - _ => return Err(einval()), - } - } - - // setattr operation should be handled in setattr handler. - _ => return Err(enosys()), - } - - Ok(()) - } - fn get_writeback_open_flags(&self, flags: i32) -> i32 { let mut new_flags = flags; let writeback = self.writeback.load(Ordering::Relaxed); @@ -1152,46 +780,6 @@ scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid); #[cfg(target_os = "linux")] scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid); -// #[cfg(target_os = "macos")] -// macro_rules! scoped_cred { -// ($name:ident, $ty:ty, $set_func:path) => { -// #[derive(Debug)] -// pub(crate) struct $name; - -// impl $name { -// fn new(val: $ty) -> io::Result> { -// if val == 0 { -// return Ok(None); -// } - -// let current_val = unsafe { $set_func(0) }; - -// if current_val == -1 { -// return Err(io::Error::last_os_error()); -// } - -// let res = unsafe { $set_func(val) }; -// if res == 0 { -// Ok(Some($name)) -// } else { -// unsafe { $set_func(current_val as u32) }; -// return Err(io::Error::last_os_error()); -// } -// } -// } - -// impl Drop for $name { -// fn drop(&mut self) { -// let res = unsafe { $set_func(0) }; -// error!( -// "fuse: failed to change credentials back to root: {}", -// io::Error::last_os_error(), -// ); -// } -// } -// }; -// } - #[cfg(target_os = "macos")] scoped_cred!(ScopedUid, libc::uid_t, libc::seteuid); #[cfg(target_os = "macos")] @@ -1206,34 +794,6 @@ fn set_creds( ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid))) } -#[cfg(target_os = "linux")] -struct CapFsetid {} - -#[cfg(target_os = "linux")] -impl Drop for CapFsetid { - fn drop(&mut self) { - if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) { - error!("fail to restore thread cap_fsetid: {}", e); - }; - } -} - -#[cfg(target_os = "linux")] -fn drop_cap_fsetid() -> io::Result> { - if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) - .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))? - { - return Ok(None); - } - caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| { - io::Error::new( - io::ErrorKind::PermissionDenied, - "failed to drop CAP_FSETID capability", - ) - })?; - Ok(Some(CapFsetid {})) -} - #[cfg(test)] mod tests { use super::*; @@ -1249,9 +809,9 @@ mod tests { use std::ops::Deref; use std::os::unix::prelude::MetadataExt; + #[cfg(target_os = "macos")] use std::fs; - #[cfg(target_os = "linux")] - use std::fs::Permissions; + #[cfg(target_os = "macos")] use std::os::unix::fs::PermissionsExt; #[cfg(target_os = "linux")] diff --git a/src/passthrough/passthrough_fs_linux.rs b/src/passthrough/passthrough_fs_linux.rs new file mode 100644 index 000000000..306fa6457 --- /dev/null +++ b/src/passthrough/passthrough_fs_linux.rs @@ -0,0 +1,327 @@ +use std::{ + ffi::{CStr, OsString}, + fs::File, + io, + ops::Deref, + os::{ + fd::{AsRawFd, RawFd}, + unix::ffi::OsStringExt, + }, + path::PathBuf, + sync::{atomic::Ordering, Arc}, +}; + +use vm_memory::bitmap::BitmapSlice; + +use crate::{abi::fuse_abi::Opcode, passthrough::util::einval}; + +use super::{ + file_handle::{FileHandle, OpenableFileHandle}, + inode_store::{InodeId, InodeStore}, + statx::{statx, StatExt}, + util::{enosys, eperm, openat, reopen_fd_through_proc, stat_fd}, + Inode, InodeData, InodeFile, InodeMap, PassthroughFs, MAX_HOST_INO, +}; + +pub type InoT = libc::ino64_t; +pub type InodeMode = u32; +pub type LibCStat = libc::stat64; +pub type OffT = libc::off64_t; +pub type StatVfs = libc::statvfs64; + +#[derive(Debug)] +pub enum InodeHandle { + File(File), + Handle(Arc), +} + +impl InodeHandle { + pub fn file_handle(&self) -> Option<&FileHandle> { + match self { + InodeHandle::File(_) => None, + InodeHandle::Handle(h) => Some(h.file_handle().deref()), + } + } + + pub fn get_file(&self) -> io::Result> { + match self { + InodeHandle::File(f) => Ok(InodeFile::Ref(f)), + InodeHandle::Handle(h) => { + let f = h.open(libc::O_PATH)?; + Ok(InodeFile::Owned(f)) + } + } + } + + pub fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result { + match self { + InodeHandle::File(f) => reopen_fd_through_proc(f, flags, proc_self_fd), + InodeHandle::Handle(h) => h.open(flags), + } + } + + pub fn stat(&self) -> io::Result { + match self { + InodeHandle::File(f) => stat_fd(f, None), + InodeHandle::Handle(_h) => { + let file = self.get_file()?; + stat_fd(&file, None) + } + } + } +} + +impl InodeData { + pub fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result { + self.handle.open_file(flags, proc_self_fd) + } +} + +impl InodeMap { + fn get_inode_locked( + inodes: &InodeStore, + id: &InodeId, + handle: Option<&FileHandle>, + ) -> Option { + match handle { + Some(h) => inodes.inode_by_handle(h).copied(), + None => inodes.inode_by_id(id).copied(), + } + } + + pub fn get_alt(&self, id: &InodeId, handle: Option<&FileHandle>) -> Option> { + // Do not expect poisoned lock here, so safe to unwrap(). + let inodes = self.inodes.read().unwrap(); + + Self::get_alt_locked(inodes.deref(), id, handle) + } + + pub fn get_alt_locked( + inodes: &InodeStore, + id: &InodeId, + handle: Option<&FileHandle>, + ) -> Option> { + handle + .and_then(|h| inodes.get_by_handle(h)) + .or_else(|| { + inodes.get_by_id(id).filter(|data| { + // When we have to fall back to looking up an inode by its IDs, ensure that + // we hit an entry that does not have a file handle. Entries with file + // handles must also have a handle alt key, so if we have not found it by + // that handle alt key, we must have found an entry with a mismatching + // handle; i.e. an entry for a different file, even though it has the same + // inode ID. + // (This can happen when we look up a new file that has reused the inode ID + // of some previously unlinked inode we still have in `.inodes`.) + handle.is_none() || data.handle.file_handle().is_none() + }) + }) + .map(Arc::clone) + } +} + +impl PassthroughFs { + pub fn keep_fds(&self) -> Vec { + vec![self.proc_self_fd.as_raw_fd()] + } + + fn readlinkat(dfd: i32, pathname: &CStr) -> io::Result { + let mut buf = Vec::with_capacity(libc::PATH_MAX as usize); + + // Safe because the kernel will only write data to buf and we check the return value + let buf_read = unsafe { + libc::readlinkat( + dfd, + pathname.as_ptr(), + buf.as_mut_ptr() as *mut libc::c_char, + buf.capacity(), + ) + }; + if buf_read < 0 { + error!("fuse: readlinkat error"); + return Err(io::Error::last_os_error()); + } + + // Safe because we trust the value returned by kernel. + unsafe { buf.set_len(buf_read as usize) }; + buf.shrink_to_fit(); + + // Be careful: + // - readlink() does not append a terminating null byte to buf + // - OsString instances are not NUL terminated + Ok(PathBuf::from(OsString::from_vec(buf))) + } + + /// Get the file pathname corresponding to the Inode + /// This function is used by Nydus blobfs + pub fn readlinkat_proc_file(&self, inode: Inode) -> io::Result { + use std::ffi::CString; + + let data = self.inode_map.get(inode)?; + let file = data.get_file()?; + let pathname = CString::new(format!("{}", file.as_raw_fd())) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + Self::readlinkat(self.proc_self_fd.as_raw_fd(), &pathname) + } + + pub fn open_file( + dfd: &impl AsRawFd, + pathname: &CStr, + flags: i32, + mode: u32, + ) -> io::Result { + openat(dfd, pathname, flags, mode) + } + + fn open_file_restricted( + &self, + dir: &impl AsRawFd, + pathname: &CStr, + flags: i32, + mode: u32, + ) -> io::Result { + let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags; + + // TODO + //if self.os_facts.has_openat2 { + // oslib::do_open_relative_to(dir, pathname, flags, mode) + //} else { + openat(dir, pathname, flags, mode) + //} + } + + /// Create a File or File Handle for `name` under directory `dir_fd` to support `lookup()`. + #[cfg(target_os = "linux")] + pub fn open_file_and_handle( + &self, + dir: &impl AsRawFd, + name: &CStr, + ) -> io::Result<(File, Option, StatExt)> { + let path_file = self.open_file_restricted(dir, name, libc::O_PATH, 0)?; + let st = statx(&path_file, None)?; + let handle = if self.cfg.inode_file_handles { + FileHandle::from_fd(&path_file)? + } else { + None + }; + + Ok((path_file, handle, st)) + } + + pub fn to_openable_handle(&self, fh: FileHandle) -> io::Result> { + fh.into_openable(&self.mount_fds, |fd, flags, _mode| { + reopen_fd_through_proc(&fd, flags, &self.proc_self_fd) + }) + .map(Arc::new) + .map_err(|e| { + if !e.silent() { + error!("{}", e); + } + e.into_inner() + }) + } + + pub fn allocate_inode( + &self, + inodes: &InodeStore, + id: &InodeId, + handle_opt: Option<&FileHandle>, + ) -> io::Result { + if !self.cfg.use_host_ino { + // If the inode has already been assigned before, the new inode is not reassigned, + // ensuring that the same file is always the same inode + Ok(InodeMap::get_inode_locked(inodes, id, handle_opt) + .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))) + } else { + let inode = if id.ino > MAX_HOST_INO { + // Prefer looking for previous mappings from memory + match InodeMap::get_inode_locked(inodes, id, handle_opt) { + Some(ino) => ino, + None => self.ino_allocator.get_unique_inode(id)?, + } + } else { + self.ino_allocator.get_unique_inode(id)? + }; + + Ok(inode) + } + } + + // When seal_size is set, we don't allow operations that could change file size nor allocate + // space beyond EOF + pub fn seal_size_check( + &self, + opcode: Opcode, + file_size: u64, + offset: u64, + size: u64, + mode: i32, + ) -> io::Result<()> { + if offset.checked_add(size).is_none() { + error!( + "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX", + opcode, offset, size + ); + return Err(einval()); + } + + match opcode { + // write should not exceed the file size. + Opcode::Write => { + if size + offset > file_size { + return Err(eperm()); + } + } + + #[cfg(target_os = "linux")] + Opcode::Fallocate => { + let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE); + match op { + // Allocate, punch and zero, must not change file size. + 0 | libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_ZERO_RANGE => { + if size + offset > file_size { + return Err(eperm()); + } + } + // collapse and insert will change file size, forbid. + libc::FALLOC_FL_COLLAPSE_RANGE | libc::FALLOC_FL_INSERT_RANGE => { + return Err(eperm()); + } + // Invalid operation + _ => return Err(einval()), + } + } + + // setattr operation should be handled in setattr handler. + _ => return Err(enosys()), + } + + Ok(()) + } +} + +pub struct CapFsetid {} + +impl Drop for CapFsetid { + fn drop(&mut self) { + if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) { + error!("fail to restore thread cap_fsetid: {}", e); + }; + } +} + +pub fn drop_cap_fsetid() -> io::Result> { + if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) + .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))? + { + return Ok(None); + } + caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| { + io::Error::new( + io::ErrorKind::PermissionDenied, + "failed to drop CAP_FSETID capability", + ) + })?; + Ok(Some(CapFsetid {})) +} diff --git a/src/passthrough/passthrough_fs_macos.rs b/src/passthrough/passthrough_fs_macos.rs new file mode 100644 index 000000000..25418f62a --- /dev/null +++ b/src/passthrough/passthrough_fs_macos.rs @@ -0,0 +1,149 @@ +#![allow(missing_docs)] + +use std::{ + ffi::{CStr, CString}, + fs::File, + io, + ops::Deref, + sync::{atomic::Ordering, Arc}, +}; + +use vm_memory::bitmap::BitmapSlice; + +use crate::{abi::fuse_abi::Opcode, passthrough::util::einval}; + +use super::{ + inode_store::{InodeId, InodeStore}, + stat::{open, stat, Stat}, + util::{enosys, eperm}, + Inode, InodeData, InodeFile, InodeMap, PassthroughFs, MAX_HOST_INO, +}; + +pub type InoT = libc::ino_t; +pub type InodeMode = u16; +pub type LibCStat = libc::stat; +pub type OffT = libc::off_t; +pub type StatVfs = libc::statvfs; + +#[derive(Debug)] +pub enum InodeHandle { + File(File, CString), +} + +impl InodeHandle { + pub fn get_file(&self) -> io::Result> { + match self { + InodeHandle::File(f, _) => Ok(InodeFile::Ref(f)), + } + } + + fn open_file(&self, flags: libc::c_int) -> io::Result { + match self { + InodeHandle::File(_, pathname) => open(pathname, flags, 0), + } + } + + pub fn stat(&self) -> io::Result { + match self { + InodeHandle::File(f, _) => stat(f), + } + } + + fn get_path(&self) -> io::Result { + match self { + InodeHandle::File(_, pathname) => Ok(pathname.clone()), + } + } +} + +impl InodeData { + pub fn open_file(&self, flags: libc::c_int) -> io::Result { + self.handle.open_file(flags) + } + + pub fn get_path(&self) -> io::Result { + self.handle.get_path() + } +} + +impl InodeMap { + pub fn get_inode_locked(inodes: &InodeStore, id: &InodeId) -> Option { + inodes.inode_by_id(id).copied() + } + + pub fn get_alt(&self, id: &InodeId) -> Option> { + let inodes = self.inodes.read().unwrap(); + + Self::get_alt_locked(inodes.deref(), id) + } + + pub fn get_alt_locked(inodes: &InodeStore, id: &InodeId) -> Option> { + inodes.get_by_id(id).map(Arc::clone) + } +} + +impl PassthroughFs { + pub fn open_file(&self, pathname: &CStr) -> io::Result<(File, Stat)> { + let path_file = self.open_file_restricted(pathname, libc::O_NOFOLLOW, 0o40777)?; + let st = stat(&path_file)?; + + Ok((path_file, st)) + } + + fn open_file_restricted(&self, pathname: &CStr, flags: i32, mode: u32) -> io::Result { + let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags; + open(pathname, flags, mode) + } + + pub fn allocate_inode(&self, inodes: &InodeStore, id: &InodeId) -> io::Result { + if !self.cfg.use_host_ino { + // If the inode has already been assigned before, the new inode is not reassigned, + // ensuring that the same file is always the same inode + Ok(InodeMap::get_inode_locked(inodes, id) + .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed))) + } else { + let inode = if id.ino > MAX_HOST_INO { + // Prefer looking for previous mappings from memory + match InodeMap::get_inode_locked(inodes, id) { + Some(ino) => ino, + None => self.ino_allocator.get_unique_inode(id)?, + } + } else { + self.ino_allocator.get_unique_inode(id)? + }; + + Ok(inode) + } + } + + pub fn seal_size_check( + &self, + opcode: Opcode, + file_size: u64, + offset: u64, + size: u64, + _mode: i32, + ) -> io::Result<()> { + if offset.checked_add(size).is_none() { + error!( + "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX", + opcode, offset, size + ); + return Err(einval()); + } + + match opcode { + // write should not exceed the file size. + Opcode::Write => { + if size + offset > file_size { + return Err(eperm()); + } + } + + // setattr operation should be handled in setattr handler. + _ => return Err(enosys()), + } + + Ok(()) + } +} diff --git a/src/passthrough/sync_io.rs b/src/passthrough/sync_io.rs index a414f7200..ae4d95104 100644 --- a/src/passthrough/sync_io.rs +++ b/src/passthrough/sync_io.rs @@ -8,16 +8,12 @@ use std::ffi::{CStr, CString}; use std::fs::File; use std::io; -#[cfg(target_os = "linux")] -use std::mem::size_of; -use std::mem::{self, ManuallyDrop, MaybeUninit}; +use std::mem::{ManuallyDrop, MaybeUninit}; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; -#[cfg(target_os = "linux")] -use super::os_compat::LinuxDirent64; #[cfg(target_os = "linux")] use super::util::stat_fd; use super::*; @@ -30,7 +26,6 @@ use crate::api::filesystem::{ Context, DirEntry, Entry, FileSystem, FsOptions, GetxattrReply, ListxattrReply, OpenOptions, SetattrValid, ZeroCopyReader, ZeroCopyWriter, }; -use crate::bytes_to_cstr; #[cfg(any(feature = "vhost-user-fs", feature = "virtiofs"))] use crate::transport::FsCacheReqHandler; @@ -64,195 +59,6 @@ impl PassthroughFs { Ok(()) } - fn do_readdir( - &self, - inode: Inode, - handle: Handle, - size: u32, - offset: u64, - add_entry: &mut dyn FnMut(DirEntry, RawFd) -> io::Result, - ) -> io::Result<()> { - if size == 0 { - return Ok(()); - } - - let mut buf = Vec::::with_capacity(size as usize); - let data = self.get_dirdata(handle, inode, libc::O_RDONLY)?; - - { - // Since we are going to work with the kernel offset, we have to acquire the file lock - // for both the `lseek64` and `getdents64` syscalls to ensure that no other thread - // changes the kernel offset while we are using it. - let (guard, dir) = data.get_file_mut(); - - // Safe because this doesn't modify any memory and we check the return value. - #[cfg(target_os = "linux")] - let res = unsafe { libc::lseek64(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; - #[cfg(target_os = "macos")] - let res = unsafe { libc::lseek(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; - if res < 0 { - return Err(io::Error::last_os_error()); - } - - // Safe because the kernel guarantees that it will only write to `buf` and we check the - // return value. - #[cfg(target_os = "linux")] - let res = unsafe { - libc::syscall( - libc::SYS_getdents64, - dir.as_raw_fd(), - buf.as_mut_ptr() as *mut LinuxDirent64, - size as libc::c_int, - ) - }; - #[cfg(target_os = "macos")] - let res = unsafe { - libc::read( - dir.as_raw_fd(), - buf.as_mut_ptr() as *mut libc::c_void, - size as libc::size_t, - ) - }; - if res < 0 { - return Err(io::Error::last_os_error()); - } - - // Safe because we trust the value returned by kernel. - unsafe { buf.set_len(res as usize) }; - - // Explicitly drop the lock so that it's not held while we fill in the fuse buffer. - mem::drop(guard); - } - - let mut rem = &buf[..]; - let orig_rem_len = rem.len(); - - #[cfg(target_os = "linux")] - while !rem.is_empty() { - // We only use debug asserts here because these values are coming from the kernel and we - // trust them implicitly. - debug_assert!( - rem.len() >= size_of::(), - "fuse: not enough space left in `rem`" - ); - - let (front, back) = rem.split_at(size_of::()); - - let dirent64 = LinuxDirent64::from_slice(front) - .expect("fuse: unable to get LinuxDirent64 from slice"); - - let namelen = dirent64.d_reclen as usize - size_of::(); - debug_assert!( - namelen <= back.len(), - "fuse: back is smaller than `namelen`" - ); - - let name = &back[..namelen]; - let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { - // We don't want to report the "." and ".." entries. However, returning `Ok(0)` will - // break the loop so return `Ok` with a non-zero value instead. - Ok(1) - } else { - // The Sys_getdents64 in kernel will pad the name with '\0' - // bytes up to 8-byte alignment, so @name may contain a few null - // terminators. This causes an extra lookup from fuse when - // called by readdirplus, because kernel path walking only takes - // name without null terminators, the dentry with more than 1 - // null terminators added by readdirplus doesn't satisfy the - // path walking. - let name = bytes_to_cstr(name) - .map_err(|e| { - error!("fuse: do_readdir: {:?}", e); - einval() - })? - .to_bytes(); - - add_entry( - DirEntry { - ino: dirent64.d_ino, - offset: dirent64.d_off as u64, - type_: u32::from(dirent64.d_ty), - name, - }, - data.borrow_fd().as_raw_fd(), - ) - }; - - debug_assert!( - rem.len() >= dirent64.d_reclen as usize, - "fuse: rem is smaller than `d_reclen`" - ); - - match res { - Ok(0) => break, - Ok(_) => rem = &rem[dirent64.d_reclen as usize..], - // If there's an error, we can only signal it if we haven't - // stored any entries yet - otherwise we'd end up with wrong - // lookup counts for the entries that are already in the - // buffer. So we return what we've collected until that point. - Err(e) if rem.len() == orig_rem_len => return Err(e), - Err(_) => return Ok(()), - } - } - - #[cfg(target_os = "macos")] - while !rem.is_empty() { - debug_assert!( - rem.len() >= mem::size_of::(), - "fuse: not enough space left in `rem`" - ); - - let (front, back) = rem.split_at(mem::size_of::()); - - let dirent = unsafe { *(front.as_ptr() as *const libc::dirent) }; - - let namelen = dirent.d_namlen as usize; - debug_assert!( - namelen <= back.len(), - "fuse: back is smaller than `namelen`" - ); - - let name = &back[..namelen]; - let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { - Ok(1) - } else { - let name = bytes_to_cstr(name) - .map_err(|e| { - error!("fuse: do_readdir: {:?}", e); - einval() - })? - .to_bytes(); - - add_entry( - DirEntry { - ino: dirent.d_ino, - #[cfg(target_os = "linux")] - offset: dirent.d_seekoff as u64, - #[cfg(target_os = "macos")] - offset: dirent.d_seekoff, - type_: dirent.d_type as u32, - name, - }, - data.borrow_fd().as_raw_fd(), - ) - }; - - debug_assert!( - rem.len() >= dirent.d_reclen as usize, - "fuse: rem is smaller than `d_reclen`" - ); - - match res { - Ok(0) => break, - Ok(_) => rem = &rem[dirent.d_reclen as usize..], - Err(e) if rem.len() == orig_rem_len => return Err(e), - Err(_) => return Ok(()), - } - } - - Ok(()) - } - fn do_open( &self, inode: Inode, @@ -311,45 +117,6 @@ impl PassthroughFs { Ok((Some(handle), opts, None)) } - fn do_getattr(&self, inode: Inode, handle: Option) -> io::Result<(LibCStat, Duration)> { - let st; - let data = self.inode_map.get(inode).map_err(|e| { - error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); - e - })?; - - // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle - // this case correctly. - - #[cfg(target_os = "linux")] - if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { - // Safe as we just checked handle - let hd = self.handle_map.get(handle.unwrap(), inode)?; - st = stat_fd(hd.get_file(), None); - } else { - st = data.handle.stat(); - } - - #[cfg(target_os = "macos")] - if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { - // Safe as we just checked handle - let hd = self.handle_map.get(handle.unwrap(), inode)?; - st = stat(hd.get_file()); - } else { - st = data.handle.stat(); - } - - let st = st.map_err(|e| { - error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); - e - })?; - #[cfg(target_os = "linux")] - return Ok((st, self.cfg.attr_timeout)); - - #[cfg(target_os = "macos")] - return Ok((st.st, self.cfg.attr_timeout)); - } - fn do_unlink(&self, parent: Inode, name: &CStr, flags: libc::c_int) -> io::Result<()> { let data = self.inode_map.get(parent)?; let file = data.get_file()?; @@ -363,7 +130,7 @@ impl PassthroughFs { } } - fn get_dirdata( + pub fn get_dirdata( &self, handle: Handle, inode: Inode, diff --git a/src/passthrough/sync_io_linux.rs b/src/passthrough/sync_io_linux.rs new file mode 100644 index 000000000..bd6d2f168 --- /dev/null +++ b/src/passthrough/sync_io_linux.rs @@ -0,0 +1,168 @@ +use std::{ + io, + mem::{self, size_of}, + os::fd::{AsRawFd, RawFd}, + sync::atomic::Ordering, + time::Duration, +}; +use vm_memory::{bitmap::BitmapSlice, ByteValued}; + +use crate::{ + api::{filesystem::DirEntry, CURRENT_DIR_CSTR, PARENT_DIR_CSTR}, + bytes_to_cstr, + passthrough::{os_compat::LinuxDirent64, util::einval}, +}; + +use super::{util::stat_fd, Handle, Inode, LibCStat, OffT, PassthroughFs}; + +impl PassthroughFs { + pub fn do_readdir( + &self, + inode: Inode, + handle: Handle, + size: u32, + offset: u64, + add_entry: &mut dyn FnMut(DirEntry, RawFd) -> io::Result, + ) -> io::Result<()> { + if size == 0 { + return Ok(()); + } + + let mut buf = Vec::::with_capacity(size as usize); + let data = self.get_dirdata(handle, inode, libc::O_RDONLY)?; + + { + // Since we are going to work with the kernel offset, we have to acquire the file lock + // for both the `lseek64` and `getdents64` syscalls to ensure that no other thread + // changes the kernel offset while we are using it. + let (guard, dir) = data.get_file_mut(); + + // Safe because this doesn't modify any memory and we check the return value. + let res = unsafe { libc::lseek64(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; + if res < 0 { + return Err(io::Error::last_os_error()); + } + + // Safe because the kernel guarantees that it will only write to `buf` and we check the + // return value. + let res = unsafe { + libc::syscall( + libc::SYS_getdents64, + dir.as_raw_fd(), + buf.as_mut_ptr() as *mut LinuxDirent64, + size as libc::c_int, + ) + }; + if res < 0 { + return Err(io::Error::last_os_error()); + } + + // Safe because we trust the value returned by kernel. + unsafe { buf.set_len(res as usize) }; + + // Explicitly drop the lock so that it's not held while we fill in the fuse buffer. + mem::drop(guard); + } + + let mut rem = &buf[..]; + let orig_rem_len = rem.len(); + + #[cfg(target_os = "linux")] + while !rem.is_empty() { + // We only use debug asserts here because these values are coming from the kernel and we + // trust them implicitly. + debug_assert!( + rem.len() >= size_of::(), + "fuse: not enough space left in `rem`" + ); + + let (front, back) = rem.split_at(size_of::()); + + let dirent64 = LinuxDirent64::from_slice(front) + .expect("fuse: unable to get LinuxDirent64 from slice"); + + let namelen = dirent64.d_reclen as usize - size_of::(); + debug_assert!( + namelen <= back.len(), + "fuse: back is smaller than `namelen`" + ); + + let name = &back[..namelen]; + let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { + // We don't want to report the "." and ".." entries. However, returning `Ok(0)` will + // break the loop so return `Ok` with a non-zero value instead. + Ok(1) + } else { + // The Sys_getdents64 in kernel will pad the name with '\0' + // bytes up to 8-byte alignment, so @name may contain a few null + // terminators. This causes an extra lookup from fuse when + // called by readdirplus, because kernel path walking only takes + // name without null terminators, the dentry with more than 1 + // null terminators added by readdirplus doesn't satisfy the + // path walking. + let name = bytes_to_cstr(name) + .map_err(|e| { + error!("fuse: do_readdir: {:?}", e); + einval() + })? + .to_bytes(); + + add_entry( + DirEntry { + ino: dirent64.d_ino, + offset: dirent64.d_off as u64, + type_: u32::from(dirent64.d_ty), + name, + }, + data.borrow_fd().as_raw_fd(), + ) + }; + + debug_assert!( + rem.len() >= dirent64.d_reclen as usize, + "fuse: rem is smaller than `d_reclen`" + ); + + match res { + Ok(0) => break, + Ok(_) => rem = &rem[dirent64.d_reclen as usize..], + // If there's an error, we can only signal it if we haven't + // stored any entries yet - otherwise we'd end up with wrong + // lookup counts for the entries that are already in the + // buffer. So we return what we've collected until that point. + Err(e) if rem.len() == orig_rem_len => return Err(e), + Err(_) => return Ok(()), + } + } + + Ok(()) + } + + pub fn do_getattr( + &self, + inode: Inode, + handle: Option, + ) -> io::Result<(LibCStat, Duration)> { + let st; + let data = self.inode_map.get(inode).map_err(|e| { + error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); + e + })?; + + // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle + // this case correctly. + if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { + // Safe as we just checked handle + let hd = self.handle_map.get(handle.unwrap(), inode)?; + st = stat_fd(hd.get_file(), None); + } else { + st = data.handle.stat(); + } + + let st = st.map_err(|e| { + error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); + e + })?; + Ok((st, self.cfg.attr_timeout)) + } +} diff --git a/src/passthrough/sync_io_macos.rs b/src/passthrough/sync_io_macos.rs new file mode 100644 index 000000000..2ef7b18a7 --- /dev/null +++ b/src/passthrough/sync_io_macos.rs @@ -0,0 +1,153 @@ +use std::{ + io, mem, + os::fd::{AsRawFd, RawFd}, + sync::atomic::Ordering, + time::Duration, +}; + +use vm_memory::bitmap::BitmapSlice; + +use crate::{ + api::{filesystem::DirEntry, CURRENT_DIR_CSTR, PARENT_DIR_CSTR}, + bytes_to_cstr, + passthrough::util::einval, +}; + +use super::{stat::stat, Handle, Inode, LibCStat, OffT, PassthroughFs}; + +impl PassthroughFs { + pub fn do_readdir( + &self, + inode: Inode, + handle: Handle, + size: u32, + offset: u64, + add_entry: &mut dyn FnMut(DirEntry, RawFd) -> io::Result, + ) -> io::Result<()> { + if size == 0 { + return Ok(()); + } + + let mut buf = Vec::::with_capacity(size as usize); + let data = self.get_dirdata(handle, inode, libc::O_RDONLY)?; + + { + // Since we are going to work with the kernel offset, we have to acquire the file lock + // for both the `lseek64` and `getdents64` syscalls to ensure that no other thread + // changes the kernel offset while we are using it. + let (guard, dir) = data.get_file_mut(); + + // Safe because this doesn't modify any memory and we check the return value. + let res = unsafe { libc::lseek(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; + if res < 0 { + return Err(io::Error::last_os_error()); + } + + // Safe because the kernel guarantees that it will only write to `buf` and we check the + // return value. + let res = unsafe { + libc::read( + dir.as_raw_fd(), + buf.as_mut_ptr() as *mut libc::c_void, + size as libc::size_t, + ) + }; + if res < 0 { + return Err(io::Error::last_os_error()); + } + + // Safe because we trust the value returned by kernel. + unsafe { buf.set_len(res as usize) }; + + // Explicitly drop the lock so that it's not held while we fill in the fuse buffer. + mem::drop(guard); + } + + let mut rem = &buf[..]; + let orig_rem_len = rem.len(); + + while !rem.is_empty() { + debug_assert!( + rem.len() >= mem::size_of::(), + "fuse: not enough space left in `rem`" + ); + + let (front, back) = rem.split_at(mem::size_of::()); + + let dirent = unsafe { *(front.as_ptr() as *const libc::dirent) }; + + let namelen = dirent.d_namlen as usize; + debug_assert!( + namelen <= back.len(), + "fuse: back is smaller than `namelen`" + ); + + let name = &back[..namelen]; + let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { + Ok(1) + } else { + let name = bytes_to_cstr(name) + .map_err(|e| { + error!("fuse: do_readdir: {:?}", e); + einval() + })? + .to_bytes(); + + add_entry( + DirEntry { + ino: dirent.d_ino, + #[cfg(target_os = "linux")] + offset: dirent.d_seekoff as u64, + #[cfg(target_os = "macos")] + offset: dirent.d_seekoff, + type_: dirent.d_type as u32, + name, + }, + data.borrow_fd().as_raw_fd(), + ) + }; + + debug_assert!( + rem.len() >= dirent.d_reclen as usize, + "fuse: rem is smaller than `d_reclen`" + ); + + match res { + Ok(0) => break, + Ok(_) => rem = &rem[dirent.d_reclen as usize..], + Err(e) if rem.len() == orig_rem_len => return Err(e), + Err(_) => return Ok(()), + } + } + + Ok(()) + } + + pub fn do_getattr( + &self, + inode: Inode, + handle: Option, + ) -> io::Result<(LibCStat, Duration)> { + let st; + let data = self.inode_map.get(inode).map_err(|e| { + error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); + e + })?; + + // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle + // this case correctly. + if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { + // Safe as we just checked handle + let hd = self.handle_map.get(handle.unwrap(), inode)?; + st = stat(hd.get_file()); + } else { + st = data.handle.stat(); + } + + let st = st.map_err(|e| { + error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); + e + })?; + Ok((st.st, self.cfg.attr_timeout)) + } +} diff --git a/tests/smoke.rs b/tests/smoke.rs index 8fa15a8a4..d0220c43f 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -19,7 +19,7 @@ mod fusedev_tests { use vmm_sys_util::tempdir::TempDir; #[cfg(target_os = "macos")] - use tempfile::{tempdir, tempdir_in, NamedTempFile, TempDir}; + use tempfile::tempdir; use crate::example::passthroughfs; From 6e88bd8edba4eddef6b198a5274a8234b043fa6f Mon Sep 17 00:00:00 2001 From: akitaSummer Date: Thu, 25 Jan 2024 10:22:58 +0800 Subject: [PATCH 6/8] fix: fix cr Signed-off-by: akitaSummer --- src/passthrough/file_handle.rs | 2 -- src/passthrough/passthrough_fs_linux.rs | 11 +++--- src/passthrough/passthrough_fs_macos.rs | 7 ++-- src/passthrough/sync_io.rs | 46 +++++++++++++++++++++++-- src/passthrough/sync_io_linux.rs | 33 +----------------- src/passthrough/sync_io_macos.rs | 35 +------------------ 6 files changed, 57 insertions(+), 77 deletions(-) diff --git a/src/passthrough/file_handle.rs b/src/passthrough/file_handle.rs index fa5379470..5eb00c282 100644 --- a/src/passthrough/file_handle.rs +++ b/src/passthrough/file_handle.rs @@ -320,10 +320,8 @@ impl OpenableFileHandle { #[cfg(test)] mod tests { use super::*; - #[cfg(target_os = "macos")] use nix::unistd::getuid; use std::ffi::CString; - #[cfg(target_os = "macos")] use std::io::Read; fn generate_c_file_handle( diff --git a/src/passthrough/passthrough_fs_linux.rs b/src/passthrough/passthrough_fs_linux.rs index 306fa6457..d57c31876 100644 --- a/src/passthrough/passthrough_fs_linux.rs +++ b/src/passthrough/passthrough_fs_linux.rs @@ -1,3 +1,8 @@ +// Copyright (C) 2023 Alibaba Cloud. All rights reserved. +// Copyright 2021 Red Hat, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. + use std::{ ffi::{CStr, OsString}, fs::File, @@ -25,7 +30,7 @@ use super::{ pub type InoT = libc::ino64_t; pub type InodeMode = u32; -pub type LibCStat = libc::stat64; +pub type LibcStat = libc::stat64; pub type OffT = libc::off64_t; pub type StatVfs = libc::statvfs64; @@ -60,7 +65,7 @@ impl InodeHandle { } } - pub fn stat(&self) -> io::Result { + pub fn stat(&self) -> io::Result { match self { InodeHandle::File(f) => stat_fd(f, None), InodeHandle::Handle(_h) => { @@ -192,7 +197,6 @@ impl PassthroughFs { } /// Create a File or File Handle for `name` under directory `dir_fd` to support `lookup()`. - #[cfg(target_os = "linux")] pub fn open_file_and_handle( &self, dir: &impl AsRawFd, @@ -274,7 +278,6 @@ impl PassthroughFs { } } - #[cfg(target_os = "linux")] Opcode::Fallocate => { let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE); match op { diff --git a/src/passthrough/passthrough_fs_macos.rs b/src/passthrough/passthrough_fs_macos.rs index 25418f62a..b6335d66e 100644 --- a/src/passthrough/passthrough_fs_macos.rs +++ b/src/passthrough/passthrough_fs_macos.rs @@ -1,4 +1,7 @@ -#![allow(missing_docs)] +// Copyright (C) 2023 Alibaba Cloud. All rights reserved. +// Copyright 2021 Red Hat, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-BSD-3-Clause file. use std::{ ffi::{CStr, CString}, @@ -21,7 +24,7 @@ use super::{ pub type InoT = libc::ino_t; pub type InodeMode = u16; -pub type LibCStat = libc::stat; +pub type LibcStat = libc::stat; pub type OffT = libc::off_t; pub type StatVfs = libc::statvfs; diff --git a/src/passthrough/sync_io.rs b/src/passthrough/sync_io.rs index ae4d95104..a15f69212 100644 --- a/src/passthrough/sync_io.rs +++ b/src/passthrough/sync_io.rs @@ -14,6 +14,8 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::Duration; +#[cfg(target_os = "macos")] +use super::stat::stat as stat_fd; #[cfg(target_os = "linux")] use super::util::stat_fd; use super::*; @@ -59,6 +61,44 @@ impl PassthroughFs { Ok(()) } + pub fn do_getattr( + &self, + inode: Inode, + handle: Option, + ) -> io::Result<(LibcStat, Duration)> { + let st; + let data = self.inode_map.get(inode).map_err(|e| { + error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); + e + })?; + + // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle + // this case correctly. + if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { + // Safe as we just checked handle + let hd = self.handle_map.get(handle.unwrap(), inode)?; + st = stat_fd( + hd.get_file(), + #[cfg(target_os = "linux")] + None, + ) + } else { + st = data.handle.stat(); + } + + let st = st.map_err(|e| { + error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); + e + })?; + Ok(( + #[cfg(target_os = "linux")] + st, + #[cfg(target_os = "macos")] + st.st, + self.cfg.attr_timeout, + )) + } + fn do_open( &self, inode: Inode, @@ -622,7 +662,7 @@ impl FileSystem for PassthroughFs { _ctx: &Context, inode: Inode, handle: Option, - ) -> io::Result<(LibCStat, Duration)> { + ) -> io::Result<(LibcStat, Duration)> { self.do_getattr(inode, handle) } @@ -630,10 +670,10 @@ impl FileSystem for PassthroughFs { &self, _ctx: &Context, inode: Inode, - attr: LibCStat, + attr: LibcStat, handle: Option, valid: SetattrValid, - ) -> io::Result<(LibCStat, Duration)> { + ) -> io::Result<(LibcStat, Duration)> { let inode_data = self.inode_map.get(inode)?; enum Data { diff --git a/src/passthrough/sync_io_linux.rs b/src/passthrough/sync_io_linux.rs index bd6d2f168..35c3017ee 100644 --- a/src/passthrough/sync_io_linux.rs +++ b/src/passthrough/sync_io_linux.rs @@ -2,8 +2,6 @@ use std::{ io, mem::{self, size_of}, os::fd::{AsRawFd, RawFd}, - sync::atomic::Ordering, - time::Duration, }; use vm_memory::{bitmap::BitmapSlice, ByteValued}; @@ -13,7 +11,7 @@ use crate::{ passthrough::{os_compat::LinuxDirent64, util::einval}, }; -use super::{util::stat_fd, Handle, Inode, LibCStat, OffT, PassthroughFs}; +use super::{Handle, Inode, OffT, PassthroughFs}; impl PassthroughFs { pub fn do_readdir( @@ -67,7 +65,6 @@ impl PassthroughFs { let mut rem = &buf[..]; let orig_rem_len = rem.len(); - #[cfg(target_os = "linux")] while !rem.is_empty() { // We only use debug asserts here because these values are coming from the kernel and we // trust them implicitly. @@ -137,32 +134,4 @@ impl PassthroughFs { Ok(()) } - - pub fn do_getattr( - &self, - inode: Inode, - handle: Option, - ) -> io::Result<(LibCStat, Duration)> { - let st; - let data = self.inode_map.get(inode).map_err(|e| { - error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); - e - })?; - - // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle - // this case correctly. - if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { - // Safe as we just checked handle - let hd = self.handle_map.get(handle.unwrap(), inode)?; - st = stat_fd(hd.get_file(), None); - } else { - st = data.handle.stat(); - } - - let st = st.map_err(|e| { - error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); - e - })?; - Ok((st, self.cfg.attr_timeout)) - } } diff --git a/src/passthrough/sync_io_macos.rs b/src/passthrough/sync_io_macos.rs index 2ef7b18a7..910aecb9e 100644 --- a/src/passthrough/sync_io_macos.rs +++ b/src/passthrough/sync_io_macos.rs @@ -1,8 +1,6 @@ use std::{ io, mem, os::fd::{AsRawFd, RawFd}, - sync::atomic::Ordering, - time::Duration, }; use vm_memory::bitmap::BitmapSlice; @@ -13,7 +11,7 @@ use crate::{ passthrough::util::einval, }; -use super::{stat::stat, Handle, Inode, LibCStat, OffT, PassthroughFs}; +use super::{Handle, Inode, OffT, PassthroughFs}; impl PassthroughFs { pub fn do_readdir( @@ -96,9 +94,6 @@ impl PassthroughFs { add_entry( DirEntry { ino: dirent.d_ino, - #[cfg(target_os = "linux")] - offset: dirent.d_seekoff as u64, - #[cfg(target_os = "macos")] offset: dirent.d_seekoff, type_: dirent.d_type as u32, name, @@ -122,32 +117,4 @@ impl PassthroughFs { Ok(()) } - - pub fn do_getattr( - &self, - inode: Inode, - handle: Option, - ) -> io::Result<(LibCStat, Duration)> { - let st; - let data = self.inode_map.get(inode).map_err(|e| { - error!("fuse: do_getattr ino {} Not find err {:?}", inode, e); - e - })?; - - // kernel sends 0 as handle in case of no_open, and it depends on fuse server to handle - // this case correctly. - if !self.no_open.load(Ordering::Relaxed) && handle.is_some() { - // Safe as we just checked handle - let hd = self.handle_map.get(handle.unwrap(), inode)?; - st = stat(hd.get_file()); - } else { - st = data.handle.stat(); - } - - let st = st.map_err(|e| { - error!("fuse: do_getattr stat failed ino {} err {:?}", inode, e); - e - })?; - Ok((st.st, self.cfg.attr_timeout)) - } } From 85fe44580bf7510bba512584d7e8740f133a3906 Mon Sep 17 00:00:00 2001 From: akitaSummer Date: Mon, 19 Feb 2024 10:33:56 +0800 Subject: [PATCH 7/8] fix: fix readdir libc bug Signed-off-by: akitaSummer --- src/passthrough/sync_io_macos.rs | 103 +++++++++---------------------- 1 file changed, 28 insertions(+), 75 deletions(-) diff --git a/src/passthrough/sync_io_macos.rs b/src/passthrough/sync_io_macos.rs index 910aecb9e..9b2e5ce30 100644 --- a/src/passthrough/sync_io_macos.rs +++ b/src/passthrough/sync_io_macos.rs @@ -1,15 +1,13 @@ use std::{ - io, mem, + ffi::CStr, + io, os::fd::{AsRawFd, RawFd}, + ptr, }; use vm_memory::bitmap::BitmapSlice; -use crate::{ - api::{filesystem::DirEntry, CURRENT_DIR_CSTR, PARENT_DIR_CSTR}, - bytes_to_cstr, - passthrough::util::einval, -}; +use crate::api::filesystem::DirEntry; use super::{Handle, Inode, OffT, PassthroughFs}; @@ -26,95 +24,50 @@ impl PassthroughFs { return Ok(()); } - let mut buf = Vec::::with_capacity(size as usize); let data = self.get_dirdata(handle, inode, libc::O_RDONLY)?; - { - // Since we are going to work with the kernel offset, we have to acquire the file lock - // for both the `lseek64` and `getdents64` syscalls to ensure that no other thread - // changes the kernel offset while we are using it. - let (guard, dir) = data.get_file_mut(); - - // Safe because this doesn't modify any memory and we check the return value. - let res = unsafe { libc::lseek(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; - if res < 0 { - return Err(io::Error::last_os_error()); - } - - // Safe because the kernel guarantees that it will only write to `buf` and we check the - // return value. - let res = unsafe { - libc::read( - dir.as_raw_fd(), - buf.as_mut_ptr() as *mut libc::c_void, - size as libc::size_t, - ) - }; - if res < 0 { - return Err(io::Error::last_os_error()); - } - - // Safe because we trust the value returned by kernel. - unsafe { buf.set_len(res as usize) }; - - // Explicitly drop the lock so that it's not held while we fill in the fuse buffer. - mem::drop(guard); + let (_guard, dir) = data.get_file_mut(); + if dir.metadata()?.is_dir() { + return Ok(()); + } + // Safe because this doesn't modify any memory and we check the return value. + let res = unsafe { libc::lseek(dir.as_raw_fd(), offset as OffT, libc::SEEK_SET) }; + if res < 0 { + return Err(io::Error::last_os_error()); } - let mut rem = &buf[..]; - let orig_rem_len = rem.len(); - - while !rem.is_empty() { - debug_assert!( - rem.len() >= mem::size_of::(), - "fuse: not enough space left in `rem`" - ); - - let (front, back) = rem.split_at(mem::size_of::()); + let dir = unsafe { libc::fdopendir(dir.as_raw_fd()) }; + loop { + let entry_ptr = unsafe { libc::readdir(dir) }; - let dirent = unsafe { *(front.as_ptr() as *const libc::dirent) }; + if entry_ptr.is_null() { + break; + } - let namelen = dirent.d_namlen as usize; - debug_assert!( - namelen <= back.len(), - "fuse: back is smaller than `namelen`" - ); + let entry: libc::dirent = unsafe { ptr::read(entry_ptr) }; - let name = &back[..namelen]; - let res = if name.starts_with(CURRENT_DIR_CSTR) || name.starts_with(PARENT_DIR_CSTR) { + let cstr = unsafe { CStr::from_ptr(entry.d_name.as_ptr()) }; + let name_str = cstr.to_str().expect("Failed to convert CStr to str"); + let res = if name_str == "." || name_str == ".." { Ok(1) } else { - let name = bytes_to_cstr(name) - .map_err(|e| { - error!("fuse: do_readdir: {:?}", e); - einval() - })? - .to_bytes(); - add_entry( DirEntry { - ino: dirent.d_ino, - offset: dirent.d_seekoff, - type_: dirent.d_type as u32, - name, + ino: entry.d_ino, + offset: entry.d_seekoff, + type_: entry.d_type as u32, + name: cstr.to_bytes(), }, data.borrow_fd().as_raw_fd(), ) }; - - debug_assert!( - rem.len() >= dirent.d_reclen as usize, - "fuse: rem is smaller than `d_reclen`" - ); - match res { Ok(0) => break, - Ok(_) => rem = &rem[dirent.d_reclen as usize..], - Err(e) if rem.len() == orig_rem_len => return Err(e), + Ok(_) => continue, Err(_) => return Ok(()), } } - + unsafe { libc::closedir(dir) }; Ok(()) } } From 39e1190d066b922e63205b7c2f7b57752ce64d7d Mon Sep 17 00:00:00 2001 From: akitaSummer Date: Mon, 19 Feb 2024 11:02:32 +0800 Subject: [PATCH 8/8] fix: fix conflicts Signed-off-by: akitaSummer --- src/passthrough/stat.rs | 12 ++++++++++++ src/passthrough/sync_io.rs | 12 +++++++++++- src/passthrough/sync_io_linux.rs | 12 ++++++++++++ src/passthrough/sync_io_macos.rs | 12 ++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/passthrough/stat.rs b/src/passthrough/stat.rs index 3410ad717..9898decb4 100644 --- a/src/passthrough/stat.rs +++ b/src/passthrough/stat.rs @@ -1,3 +1,15 @@ +// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Fuse passthrough file system, mirroring an existing FS hierarchy. +//! +//! This file system mirrors the existing file system hierarchy of the system, starting at the +//! root file system. This is implemented by just "passing through" all requests to the +//! corresponding underlying file system. +//! +//! The code is derived from the +//! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project, +//! with heavy modification/enhancements from Alibaba Cloud OS team. use std::{ ffi::{CStr, CString}, fs::File, diff --git a/src/passthrough/sync_io.rs b/src/passthrough/sync_io.rs index b12f946a1..f081c3677 100644 --- a/src/passthrough/sync_io.rs +++ b/src/passthrough/sync_io.rs @@ -256,7 +256,17 @@ impl FileSystem for PassthroughFs { self.import()?; } - let opts = FsOptions::FILE_OPS; + let opts = FsOptions::ASYNC_READ | FsOptions::BIG_WRITES | FsOptions::ATOMIC_O_TRUNC; + + if !self.cfg.do_import || self.cfg.writeback { + self.writeback.store(true, Ordering::Relaxed); + } + if !self.cfg.do_import || self.cfg.no_open { + self.no_open.store(true, Ordering::Relaxed); + } + if !self.cfg.do_import || self.cfg.no_opendir { + self.no_opendir.store(true, Ordering::Relaxed); + } Ok(opts) } diff --git a/src/passthrough/sync_io_linux.rs b/src/passthrough/sync_io_linux.rs index 35c3017ee..304597c31 100644 --- a/src/passthrough/sync_io_linux.rs +++ b/src/passthrough/sync_io_linux.rs @@ -1,3 +1,15 @@ +// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Fuse passthrough file system, mirroring an existing FS hierarchy. +//! +//! This file system mirrors the existing file system hierarchy of the system, starting at the +//! root file system. This is implemented by just "passing through" all requests to the +//! corresponding underlying file system. +//! +//! The code is derived from the +//! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project, +//! with heavy modification/enhancements from Alibaba Cloud OS team. use std::{ io, mem::{self, size_of}, diff --git a/src/passthrough/sync_io_macos.rs b/src/passthrough/sync_io_macos.rs index 9b2e5ce30..312427cf2 100644 --- a/src/passthrough/sync_io_macos.rs +++ b/src/passthrough/sync_io_macos.rs @@ -1,3 +1,15 @@ +// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Fuse passthrough file system, mirroring an existing FS hierarchy. +//! +//! This file system mirrors the existing file system hierarchy of the system, starting at the +//! root file system. This is implemented by just "passing through" all requests to the +//! corresponding underlying file system. +//! +//! The code is derived from the +//! [CrosVM](https://chromium.googlesource.com/chromiumos/platform/crosvm/) project, +//! with heavy modification/enhancements from Alibaba Cloud OS team. use std::{ ffi::CStr, io,