diff --git a/src/api/pseudo_fs.rs b/src/api/pseudo_fs.rs index fc66c8301..d331c3dd1 100644 --- a/src/api/pseudo_fs.rs +++ b/src/api/pseudo_fs.rs @@ -235,6 +235,12 @@ impl PseudoFs { self.inodes.store(Arc::new(hashmap)); } + pub fn get_parent_inode(&self, ino: u64) -> Option { + let _guard = self.lock.lock(); + let inodes = self.inodes.load(); + inodes.get(&ino).map(|o| o.parent) + } + #[allow(dead_code)] pub fn evict_inode(&self, ino: u64) { let _guard = self.lock.lock(); diff --git a/src/api/server/sync_io.rs b/src/api/server/sync_io.rs index 97b5c42fb..1d0433cb4 100644 --- a/src/api/server/sync_io.rs +++ b/src/api/server/sync_io.rs @@ -19,10 +19,44 @@ use crate::abi::virtio_fs::{RemovemappingIn, RemovemappingOne, SetupmappingIn}; use crate::api::filesystem::{ DirEntry, Entry, FileSystem, GetxattrReply, IoctlData, ListxattrReply, }; +#[cfg(feature = "fusedev")] +use crate::transport::FuseDevWriter; use crate::transport::{pagesize, FsCacheReqHandler, Reader, Writer}; use crate::{bytes_to_cstr, encode_io_error_kind, BitmapSlice, Error, Result}; impl Server { + #[cfg(feature = "fusedev")] + /// Use to send notify msg to kernel fuse + pub fn notify_inval_entry( + &self, + mut w: FuseDevWriter<'_, S>, + parent: u64, + name: &std::ffi::CStr, + ) -> Result { + let mut buffer_writer = w.split_at(0).map_err(Error::FailedToSplitWriter)?; + let mut entry = NotifyInvalEntryOut::default(); + let mut header = OutHeader::default(); + let name_with_null = name.to_bytes_with_nul(); + header.unique = 0; + header.error = NotifyOpcode::InvalEntry as i32; + header.len = std::mem::size_of::() as u32 + + std::mem::size_of::() as u32 + + name_with_null.len() as u32; + // the namelne don't contains the nul + entry.namelen = (name_with_null.len() - 1) as u32; + entry.parent = parent; + + buffer_writer + .write_obj(header) + .map_err(Error::FailedToWrite)?; + buffer_writer + .write_obj(entry) + .map_err(Error::FailedToWrite)?; + buffer_writer + .write(name_with_null) + .map_err(Error::FailedToWrite)?; + buffer_writer.commit(None).map_err(Error::InvalidMessage) + } /// Main entrance to handle requests from the transport layer. /// /// It receives Fuse requests from transport layers, parses the request according to Fuse ABI, diff --git a/src/api/vfs/mod.rs b/src/api/vfs/mod.rs index 9bec9557f..72dfcf250 100644 --- a/src/api/vfs/mod.rs +++ b/src/api/vfs/mod.rs @@ -292,6 +292,7 @@ pub struct Vfs { opts: ArcSwap, initialized: AtomicBool, lock: Mutex<()>, + remove_pseudo_root: bool, } impl Default for Vfs { @@ -311,9 +312,15 @@ impl Vfs { opts: ArcSwap::new(Arc::new(opts)), lock: Mutex::new(()), initialized: AtomicBool::new(false), + remove_pseudo_root: false, } } + /// mark remove pseudo root inode when umount + pub fn set_remove_pseudo_root(&mut self) { + self.remove_pseudo_root = true; + } + /// For sake of live-upgrade, only after negotiation is done, it's safe to persist /// state of vfs. pub fn initialized(&self) -> bool { @@ -388,7 +395,7 @@ impl Vfs { } /// Umount a backend file system at path - pub fn umount(&self, path: &str) -> VfsResult<()> { + pub fn umount(&self, path: &str) -> VfsResult<(u64, u64)> { // Serialize mount operations. Do not expect poisoned lock here. let _guard = self.lock.lock().unwrap(); let inode = self @@ -396,7 +403,13 @@ impl Vfs { .path_walk(path) .map_err(VfsError::PathWalk)? .ok_or_else(|| VfsError::NotFound(path.to_string()))?; - + let parent = self + .root + .get_parent_inode(inode) + .ok_or(VfsError::NotFound(format!( + "{}'s parent inode does not exist", + inode + )))?; let mut mountpoints = self.mountpoints.load().deref().deref().clone(); let fs_idx = mountpoints .get(&inode) @@ -406,7 +419,14 @@ impl Vfs { // 1. they can be reused later on // 2. during live upgrade, it is easier reconstruct pseudofs inodes since // we do not have to track pseudofs deletions - //self.root.evict_inode(inode); + // In order to make the hot upgrade of virtiofs easy, VFS will save pseudo + // inodes when umount for easy recovery. However, in the fuse scenario, if + // umount does not remove the pseudo inode, it will cause an invalid + // directory to be seen on the host, which is not friendly to users. So add + // this option to control this behavior. + if self.remove_pseudo_root { + self.root.evict_inode(inode); + } mountpoints.remove(&inode); self.mountpoints.store(Arc::new(mountpoints)); x.fs_idx @@ -423,7 +443,7 @@ impl Vfs { } self.superblocks.store(Arc::new(superblocks)); - Ok(()) + Ok((inode, parent)) } /// Get the mounted backend file system alongside the path if there's one. diff --git a/src/api/vfs/sync_io.rs b/src/api/vfs/sync_io.rs index 7bf175651..cd36a4dd5 100644 --- a/src/api/vfs/sync_io.rs +++ b/src/api/vfs/sync_io.rs @@ -91,7 +91,15 @@ impl FileSystem for Vfs { (Right(fs), idata) => fs.forget(ctx, idata.ino(), count), }, Err(e) => { - error!("vfs::forget: failed to get_real_rootfs {:?}", e); + // When a directory is umount and invalidate entry, a forget request is triggered, + // which cannot be forwarded to the backend file system. + // this situation is reasonable, so it is changed to warn here + warn!( + "vfs::forget: failed to get_real_rootfs {:?}, inode: {:?}, + maybe it is possible that the vfs submount was dropped by umount, + which is reasonable.", + e, inode + ); } } } diff --git a/src/lib.rs b/src/lib.rs index 0e8058c17..2eaaa9ffd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,10 @@ pub enum Error { InvalidXattrSize((u32, usize)), /// Invalid message that the server cannot handle properly. InvalidMessage(io::Error), + /// Failed to write buffer to writer. + FailedToWrite(io::Error), + /// Failed to split a writer. + FailedToSplitWriter(transport::Error), } impl error::Error for Error {} @@ -95,6 +99,8 @@ impl fmt::Display for Error { decoded value: size = {size}, value.len() = {len}" ), InvalidMessage(err) => write!(f, "cannot process fuse message: {err}"), + FailedToWrite(err) => write!(f, "cannot write to buffer: {err}"), + FailedToSplitWriter(err) => write!(f, "cannot split a writer: {err}"), } } } diff --git a/src/transport/fusedev/mod.rs b/src/transport/fusedev/mod.rs index 71f9ba6b7..0dd1abf38 100644 --- a/src/transport/fusedev/mod.rs +++ b/src/transport/fusedev/mod.rs @@ -157,10 +157,7 @@ impl<'a, S: BitmapSlice> FuseDevWriter<'a, S> { } }; - res.map_err(|e| { - error! {"fail to write to fuse device on commit: {}", e}; - io::Error::from_raw_os_error(e as i32) - }) + res.map_err(|e| io::Error::from_raw_os_error(e as i32)) } /// Return number of bytes already written to the internal buffer.