1use config::{CachePolicy, Config};
2use file_handle::{FileHandle, OpenableFileHandle};
3
4use futures::executor::block_on;
5use inode_store::{InodeId, InodeStore};
6use libc::{self, statx_timestamp};
7
8use moka::future::Cache;
9use rfuse3::{Errno, raw::reply::ReplyEntry};
10use uuid::Uuid;
11
12use crate::passthrough::mmap::{MmapCachedValue, MmapChunkKey};
13use crate::util::convert_stat64_to_file_attr;
14use mount_fd::MountFds;
15use statx::StatExt;
16use std::cmp;
17use std::io::Result;
18use std::ops::DerefMut;
19use std::os::unix::ffi::OsStrExt;
20use std::path::Path;
21use tracing::error;
22use tracing::{debug, warn};
23
24use std::sync::atomic::{AtomicBool, AtomicU32};
25use std::{
26 collections::{BTreeMap, btree_map},
27 ffi::{CStr, CString, OsString},
28 fs::File,
29 io::{self, Error},
30 marker::PhantomData,
31 os::{
32 fd::{AsFd, AsRawFd, BorrowedFd, RawFd},
33 unix::ffi::OsStringExt,
34 },
35 path::PathBuf,
36 sync::Arc,
37 sync::atomic::{AtomicU64, Ordering},
38 time::Duration,
39};
40use util::{
41 UniqueInodeGenerator, ebadf, is_dir, openat, reopen_fd_through_proc, stat_fd,
42 validate_path_component,
43};
44
45use vm_memory::bitmap::BitmapSlice;
46
47use nix::sys::resource::{Resource, getrlimit};
48
49mod async_io;
50mod config;
51mod file_handle;
52mod inode_store;
53mod mmap;
54mod mount_fd;
55pub mod newlogfs;
56mod os_compat;
57mod statx;
58mod util;
59
60pub const CURRENT_DIR_CSTR: &[u8] = b".\0";
62pub const PARENT_DIR_CSTR: &[u8] = b"..\0";
64pub const VFS_MAX_INO: u64 = 0xff_ffff_ffff_ffff;
65const MOUNT_INFO_FILE: &str = "/proc/self/mountinfo";
66pub const EMPTY_CSTR: &[u8] = b"\0";
67pub const PROC_SELF_FD_CSTR: &[u8] = b"/proc/self/fd\0";
68pub const ROOT_ID: u64 = 1;
69use tokio::sync::{Mutex, MutexGuard, RwLock};
70
71#[derive(Debug, Clone)]
72pub struct PassthroughArgs<P, M>
73where
74 P: AsRef<Path>,
75 M: AsRef<str>,
76{
77 pub root_dir: P,
78 pub mapping: Option<M>,
79}
80
81pub async fn new_passthroughfs_layer<P: AsRef<Path>, M: AsRef<str>>(
82 args: PassthroughArgs<P, M>,
83) -> Result<PassthroughFs> {
84 let mut config = Config {
85 root_dir: args.root_dir.as_ref().to_path_buf(),
86 xattr: true,
88 do_import: true,
89 ..Default::default()
90 };
91 if let Some(mapping) = args.mapping {
92 config.mapping = mapping
93 .as_ref()
94 .parse()
95 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
96 }
97
98 let fs = PassthroughFs::<()>::new(config)?;
99
100 fs.import().await?;
101 Ok(fs)
102}
103
104type Inode = u64;
105type Handle = u64;
106
107const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff;
109
110#[derive(Debug)]
118enum InodeFile<'a> {
119 Owned(File),
120 Ref(&'a File),
121}
122
123impl AsRawFd for InodeFile<'_> {
124 fn as_raw_fd(&self) -> RawFd {
127 match self {
128 Self::Owned(file) => file.as_raw_fd(),
129 Self::Ref(file_ref) => file_ref.as_raw_fd(),
130 }
131 }
132}
133
134impl AsFd for InodeFile<'_> {
135 fn as_fd(&self) -> BorrowedFd<'_> {
136 match self {
137 Self::Owned(file) => file.as_fd(),
138 Self::Ref(file_ref) => file_ref.as_fd(),
139 }
140 }
141}
142
143#[derive(Debug)]
144#[allow(dead_code)]
145enum InodeHandle {
146 File(File),
148 Handle(Arc<OpenableFileHandle>),
149}
150
151impl InodeHandle {
152 fn file_handle(&self) -> Option<&FileHandle> {
153 match self {
154 InodeHandle::File(_) => None,
155 InodeHandle::Handle(h) => Some(h.file_handle()),
156 }
157 }
158
159 fn get_file(&self) -> Result<InodeFile<'_>> {
160 match self {
161 InodeHandle::File(f) => Ok(InodeFile::Ref(f)),
162 InodeHandle::Handle(h) => {
163 let f = h.open(libc::O_PATH)?;
164 Ok(InodeFile::Owned(f))
165 }
166 }
167 }
168
169 fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> Result<File> {
170 match self {
171 InodeHandle::File(f) => reopen_fd_through_proc(f, flags, proc_self_fd),
172 InodeHandle::Handle(h) => h.open(flags),
173 }
174 }
175
176 fn stat(&self) -> Result<libc::stat64> {
177 match self {
178 InodeHandle::File(f) => stat_fd(f, None),
179 InodeHandle::Handle(_h) => {
180 let file = self.get_file()?;
181 stat_fd(&file, None)
182 }
183 }
184 }
185}
186
187#[derive(Debug)]
189pub struct InodeData {
190 inode: Inode,
191 handle: InodeHandle,
193 id: InodeId,
194 refcount: AtomicU64,
195 mode: u32,
197 btime: statx_timestamp,
198}
199
200impl InodeData {
201 fn new(
202 inode: Inode,
203 f: InodeHandle,
204 refcount: u64,
205 id: InodeId,
206 mode: u32,
207 btime: statx_timestamp,
208 ) -> Self {
209 InodeData {
210 inode,
211 handle: f,
212 id,
213 refcount: AtomicU64::new(refcount),
214 mode,
215 btime,
216 }
217 }
218
219 fn get_file(&self) -> Result<InodeFile<'_>> {
220 self.handle.get_file()
221 }
222
223 fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> Result<File> {
224 self.handle.open_file(flags, proc_self_fd)
225 }
226}
227
228struct InodeMap {
230 pub inodes: RwLock<InodeStore>,
231}
232
233impl InodeMap {
234 fn new() -> Self {
235 InodeMap {
236 inodes: RwLock::new(Default::default()),
237 }
238 }
239
240 async fn clear(&self) {
241 self.inodes.write().await.clear();
243 }
244
245 async fn get(&self, inode: Inode) -> Result<Arc<InodeData>> {
246 self.inodes
248 .read()
249 .await
250 .get(&inode)
251 .cloned()
252 .ok_or_else(ebadf)
253 }
254
255 fn get_inode_locked(inodes: &InodeStore, handle: &Arc<FileHandle>) -> Option<Inode> {
256 inodes.inode_by_handle(handle).copied()
257 }
258
259 async fn get_alt(&self, id: &InodeId, handle: &Arc<FileHandle>) -> Option<Arc<InodeData>> {
260 let inodes = self.inodes.read().await;
262
263 Self::get_alt_locked(&inodes, id, handle)
264 }
265
266 fn get_alt_locked(
267 inodes: &InodeStore,
268 id: &InodeId,
269 handle: &Arc<FileHandle>,
270 ) -> Option<Arc<InodeData>> {
271 inodes
272 .get_by_handle(handle)
273 .or_else(|| {
274 inodes.get_by_id(id).filter(|data| {
275 data.handle.file_handle().is_none()
284 })
285 })
286 .cloned()
287 }
288
289 async fn insert(&self, data: Arc<InodeData>) {
290 let mut inodes = self.inodes.write().await;
291
292 Self::insert_locked(&mut inodes, data)
293 }
294
295 fn insert_locked(inodes: &mut InodeStore, data: Arc<InodeData>) {
296 inodes.insert(data);
297 }
298}
299
300struct HandleData {
301 inode: Inode,
302 file: File,
303 lock: Mutex<()>,
304 open_flags: AtomicU32,
305}
306
307impl HandleData {
308 fn new(inode: Inode, file: File, flags: u32) -> Self {
309 HandleData {
310 inode,
311 file,
312 lock: Mutex::new(()),
313 open_flags: AtomicU32::new(flags),
314 }
315 }
316
317 fn get_file(&self) -> &File {
318 &self.file
319 }
320
321 async fn get_file_mut(&self) -> (MutexGuard<'_, ()>, &File) {
322 (self.lock.lock().await, &self.file)
323 }
324
325 fn borrow_fd(&self) -> BorrowedFd<'_> {
326 self.file.as_fd()
327 }
328
329 async fn get_flags(&self) -> u32 {
330 self.open_flags.load(Ordering::Relaxed)
331 }
332
333 async fn set_flags(&self, flags: u32) {
334 self.open_flags.store(flags, Ordering::Relaxed);
335 }
336}
337
338struct HandleMap {
339 handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>,
340}
341
342impl HandleMap {
343 fn new() -> Self {
344 HandleMap {
345 handles: RwLock::new(BTreeMap::new()),
346 }
347 }
348
349 async fn clear(&self) {
350 self.handles.write().await.clear();
352 }
353
354 async fn insert(&self, handle: Handle, data: HandleData) {
355 self.handles.write().await.insert(handle, Arc::new(data));
357 }
358
359 async fn release(&self, handle: Handle, inode: Inode) -> Result<()> {
360 let mut handles = self.handles.write().await;
362
363 if let btree_map::Entry::Occupied(e) = handles.entry(handle)
364 && e.get().inode == inode
365 {
366 e.remove();
369
370 return Ok(());
371 }
372
373 Err(ebadf())
374 }
375
376 async fn get(&self, handle: Handle, inode: Inode) -> Result<Arc<HandleData>> {
377 self.handles
379 .read()
380 .await
381 .get(&handle)
382 .filter(|hd| hd.inode == inode)
383 .cloned()
384 .ok_or_else(ebadf)
385 }
386}
387
388#[derive(Debug, Hash, Eq, PartialEq)]
389struct FileUniqueKey(u64, statx_timestamp);
390
391pub struct PassthroughFs<S: BitmapSlice + Send + Sync = ()> {
399 inode_map: InodeMap,
404 next_inode: AtomicU64,
405
406 handle_map: HandleMap,
409 next_handle: AtomicU64,
410
411 ino_allocator: UniqueInodeGenerator,
413 mount_fds: MountFds,
415
416 proc_self_fd: File,
421
422 writeback: AtomicBool,
425
426 no_open: AtomicBool,
428
429 no_opendir: AtomicBool,
431
432 no_readdir: AtomicBool,
437
438 seal_size: AtomicBool,
440
441 dir_entry_timeout: Duration,
445 dir_attr_timeout: Duration,
446
447 cfg: Config,
448
449 _uuid: Uuid,
450
451 phantom: PhantomData<S>,
452
453 handle_cache: Cache<FileUniqueKey, Arc<FileHandle>>,
454
455 mmap_chunks: Cache<MmapChunkKey, Arc<RwLock<mmap::MmapCachedValue>>>,
456}
457
458impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
459 pub fn new(mut cfg: Config) -> Result<PassthroughFs<S>> {
461 if cfg.no_open && cfg.cache_policy != CachePolicy::Always {
462 warn!("passthroughfs: no_open only work with cache=always, reset to open mode");
463 cfg.no_open = false;
464 }
465 if cfg.writeback && cfg.cache_policy == CachePolicy::Never {
466 warn!(
467 "passthroughfs: writeback cache conflicts with cache=none, reset to no_writeback"
468 );
469 cfg.writeback = false;
470 }
471
472 let proc_self_fd_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_SELF_FD_CSTR) };
474 let proc_self_fd = Self::open_file(
475 &libc::AT_FDCWD,
476 proc_self_fd_cstr,
477 libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
478 0,
479 )?;
480
481 let (dir_entry_timeout, dir_attr_timeout) =
482 match (cfg.dir_entry_timeout, cfg.dir_attr_timeout) {
483 (Some(e), Some(a)) => (e, a),
484 (Some(e), None) => (e, cfg.attr_timeout),
485 (None, Some(a)) => (cfg.entry_timeout, a),
486 (None, None) => (cfg.entry_timeout, cfg.attr_timeout),
487 };
488
489 let mount_fds = MountFds::new(None)?;
490
491 let fd_limit = match getrlimit(Resource::RLIMIT_NOFILE) {
492 Ok((soft, _)) => soft,
493 Err(_) => 65536,
494 };
495
496 let max_mmap_size = if cfg.use_mmap { cfg.max_mmap_size } else { 0 };
497
498 let mmap_cache_builder = Cache::builder()
499 .max_capacity(max_mmap_size)
500 .weigher(
501 |_key: &MmapChunkKey, value: &Arc<RwLock<mmap::MmapCachedValue>>| -> u32 {
502 let guard = block_on(value.read());
503 match &*guard {
504 MmapCachedValue::Mmap(mmap) => mmap.len() as u32,
505 MmapCachedValue::MmapMut(mmap_mut) => mmap_mut.len() as u32,
506 }
507 },
508 )
509 .time_to_idle(Duration::from_millis(60));
510
511 Ok(PassthroughFs {
512 inode_map: InodeMap::new(),
513 next_inode: AtomicU64::new(ROOT_ID + 1),
514 ino_allocator: UniqueInodeGenerator::new(),
515
516 handle_map: HandleMap::new(),
517 next_handle: AtomicU64::new(1),
518
519 mount_fds,
520 proc_self_fd,
521
522 writeback: AtomicBool::new(false),
523 no_open: AtomicBool::new(false),
524 no_opendir: AtomicBool::new(false),
525 no_readdir: AtomicBool::new(cfg.no_readdir),
527 seal_size: AtomicBool::new(cfg.seal_size),
528 dir_entry_timeout,
530 dir_attr_timeout,
531 cfg,
532
533 _uuid: Uuid::new_v4(),
534
535 phantom: PhantomData,
536
537 handle_cache: moka::future::Cache::new(fd_limit),
538
539 mmap_chunks: mmap_cache_builder.build(),
540 })
541 }
542
543 pub async fn import(&self) -> Result<()> {
545 let root =
546 CString::new(self.cfg.root_dir.as_os_str().as_bytes()).expect("Invalid root_dir");
547
548 let (h, st) = Self::open_file_and_handle(self, &libc::AT_FDCWD, &root)
549 .await
550 .map_err(|e| {
551 error!("fuse: import: failed to get file or handle: {e:?}");
552 e
553 })?;
554 let id = InodeId::from_stat(&st);
555 let handle = InodeHandle::Handle(self.to_openable_handle(h)?);
556
557 unsafe { libc::umask(0o000) };
561
562 self.inode_map
564 .insert(Arc::new(InodeData::new(
565 ROOT_ID,
566 handle,
567 2,
568 id,
569 st.st.st_mode,
570 st.btime
571 .ok_or_else(|| io::Error::other("birth time not available"))?,
572 )))
573 .await;
574
575 Ok(())
576 }
577
578 pub fn keep_fds(&self) -> Vec<RawFd> {
580 vec![self.proc_self_fd.as_raw_fd()]
581 }
582
583 fn readlinkat(dfd: i32, pathname: &CStr) -> Result<PathBuf> {
584 let mut buf = Vec::with_capacity(libc::PATH_MAX as usize);
585
586 let buf_read = unsafe {
588 libc::readlinkat(
589 dfd,
590 pathname.as_ptr(),
591 buf.as_mut_ptr() as *mut libc::c_char,
592 buf.capacity(),
593 )
594 };
595 if buf_read < 0 {
596 error!("fuse: readlinkat error");
597 return Err(Error::last_os_error());
598 }
599
600 unsafe { buf.set_len(buf_read as usize) };
602 buf.shrink_to_fit();
603
604 Ok(PathBuf::from(OsString::from_vec(buf)))
608 }
609
610 pub async fn readlinkat_proc_file(&self, inode: Inode) -> Result<PathBuf> {
613 let data = self.inode_map.get(inode).await?;
614 let file = data.get_file()?;
615 let pathname = CString::new(format!("{}", file.as_raw_fd()))
616 .map_err(|e| Error::new(io::ErrorKind::InvalidData, e))?;
617
618 Self::readlinkat(self.proc_self_fd.as_raw_fd(), &pathname)
619 }
620
621 fn create_file_excl(
622 dir: &impl AsRawFd,
623 pathname: &CStr,
624 flags: i32,
625 mode: u32,
626 ) -> io::Result<Option<File>> {
627 match openat(dir, pathname, flags | libc::O_CREAT | libc::O_EXCL, mode) {
628 Ok(file) => Ok(Some(file)),
629 Err(err) => {
630 if err.kind() == io::ErrorKind::AlreadyExists {
632 if (flags & libc::O_EXCL) != 0 {
633 return Err(err);
634 }
635 return Ok(None);
636 }
637 Err(err)
638 }
639 }
640 }
641
642 fn open_file(dfd: &impl AsRawFd, pathname: &CStr, flags: i32, mode: u32) -> io::Result<File> {
643 openat(dfd, pathname, flags, mode)
644 }
645
646 fn open_file_restricted(
647 &self,
648 dir: &impl AsRawFd,
649 pathname: &CStr,
650 flags: i32,
651 mode: u32,
652 ) -> io::Result<File> {
653 let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags;
654
655 openat(dir, pathname, flags, mode)
660 }
662
663 async fn open_file_and_handle(
665 &self,
666 dir: &impl AsRawFd,
667 name: &CStr,
668 ) -> io::Result<(Arc<FileHandle>, StatExt)> {
669 let path_file = self.open_file_restricted(dir, name, libc::O_PATH, 0)?;
670 let st = statx::statx(&path_file, None)?;
671
672 let btime_is_valid = match st.btime {
673 Some(ts) => ts.tv_sec != 0 || ts.tv_nsec != 0,
674 None => false,
675 };
676
677 if btime_is_valid {
678 let key = FileUniqueKey(st.st.st_ino, st.btime.unwrap());
679 let cache = self.handle_cache.clone();
680 if let Some(h) = cache.get(&key).await {
681 Ok((h, st))
682 } else if let Some(handle_from_fd) = FileHandle::from_fd(&path_file)? {
683 let handle_arc = Arc::new(handle_from_fd);
684 cache.insert(key, Arc::clone(&handle_arc)).await;
685 Ok((handle_arc, st))
686 } else {
687 Err(Error::new(
688 io::ErrorKind::NotFound,
689 "Failed to create file handle",
690 ))
691 }
692 } else {
693 let handle = {
694 if let Some(handle_from_fd) = FileHandle::from_fd(&path_file)? {
695 handle_from_fd
696 } else {
697 return Err(Error::new(io::ErrorKind::NotFound, "File not found"));
698 }
699 };
700 let handle_arc = Arc::new(handle);
701 Ok((handle_arc, st))
702 }
703 }
704
705 fn to_openable_handle(&self, fh: Arc<FileHandle>) -> io::Result<Arc<OpenableFileHandle>> {
706 (*Arc::as_ref(&fh))
707 .clone()
708 .into_openable(&self.mount_fds, |fd, flags, _mode| {
709 reopen_fd_through_proc(&fd, flags, &self.proc_self_fd)
710 })
711 .map(Arc::new)
712 .map_err(|e| {
713 if !e.silent() {
714 error!("{e}");
715 }
716 e.into_inner()
717 })
718 }
719
720 async fn allocate_inode(
721 &self,
722 inodes: &InodeStore,
723 id: &InodeId,
724 handle: &Arc<FileHandle>,
725 ) -> io::Result<Inode> {
726 if !self.cfg.use_host_ino {
727 match InodeMap::get_inode_locked(inodes, handle) {
730 Some(a) => Ok(a),
731 None => Ok(self.next_inode.fetch_add(1, Ordering::Relaxed)),
732 }
733 } else {
734 let inode = if id.ino > MAX_HOST_INO {
735 match InodeMap::get_inode_locked(inodes, handle) {
737 Some(ino) => ino,
738 None => self.ino_allocator.get_unique_inode(id)?,
739 }
740 } else {
741 self.ino_allocator.get_unique_inode(id)?
742 };
743 Ok(inode)
745 }
746 }
747
748 async fn do_lookup(
749 &self,
750 parent: Inode,
751 name: &CStr,
752 ) -> std::result::Result<ReplyEntry, Errno> {
753 let name = if parent == ROOT_ID && name.to_bytes_with_nul().starts_with(PARENT_DIR_CSTR) {
754 CStr::from_bytes_with_nul(CURRENT_DIR_CSTR).unwrap()
756 } else {
757 name
758 };
759
760 let dir = self.inode_map.get(parent).await?;
761 let dir_file = dir.get_file()?;
762 let (handle_arc, st) = self.open_file_and_handle(&dir_file, name).await?;
763 let id = InodeId::from_stat(&st);
764 debug!(
765 "do_lookup: parent: {}, name: {}, handle_arc: {:?}, id: {:?}",
766 parent,
767 name.to_string_lossy(),
768 handle_arc,
769 id
770 );
771
772 let mut found = None;
773 'search: loop {
774 match self.inode_map.get_alt(&id, &handle_arc).await {
775 None => break 'search,
777 Some(data) => {
778 let curr = data.refcount.load(Ordering::Acquire);
779 if curr == 0 {
781 continue 'search;
782 }
783
784 let new = curr.saturating_add(1);
786
787 if data
789 .refcount
790 .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
791 .is_ok()
792 {
793 found = Some(data.inode);
794 break;
795 }
796 }
797 }
798 }
799
800 let inode = if let Some(v) = found {
801 v
802 } else {
803 let handle_arc_clone = Arc::clone(&handle_arc);
805 let handle_clone =
806 InodeHandle::Handle(self.to_openable_handle(Arc::clone(&handle_arc))?);
807
808 let mut inodes = self.inode_map.inodes.write().await;
810
811 match InodeMap::get_alt_locked(&inodes, &id, &handle_arc_clone) {
817 Some(data) => {
818 data.refcount.fetch_add(1, Ordering::Relaxed);
822 data.inode
823 }
824 None => {
825 let inode = self.allocate_inode(&inodes, &id, &handle_arc).await?;
826 if inode > VFS_MAX_INO {
829 error!("fuse: max inode number reached: {VFS_MAX_INO}");
830 return Err(io::Error::other(format!(
831 "max inode number reached: {VFS_MAX_INO}"
832 ))
833 .into());
834 }
835
836 InodeMap::insert_locked(
837 inodes.deref_mut(),
838 Arc::new(InodeData::new(
839 inode,
840 handle_clone,
841 1,
842 id,
843 st.st.st_mode,
844 st.btime
845 .ok_or_else(|| io::Error::other("birth time not available"))?,
846 )),
847 );
848
849 inode
850 }
851 }
852 };
853
854 let (entry_timeout, _) = if is_dir(st.st.st_mode) {
855 (self.dir_entry_timeout, self.dir_attr_timeout)
856 } else {
857 (self.cfg.entry_timeout, self.cfg.attr_timeout)
858 };
859
860 let mut attr_temp = convert_stat64_to_file_attr(st.st);
872 attr_temp.ino = inode;
873 attr_temp.uid = self.cfg.mapping.find_mapping(attr_temp.uid, true, true);
874 attr_temp.gid = self.cfg.mapping.find_mapping(attr_temp.gid, true, false);
875 Ok(ReplyEntry {
876 ttl: entry_timeout,
877 attr: attr_temp,
878 generation: 0,
879 })
880 }
881
882 async fn forget_one(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
883 if inode == ROOT_ID {
885 return;
886 }
887
888 if let Some(data) = inodes.get(&inode) {
889 loop {
894 let curr = data.refcount.load(Ordering::Acquire);
895
896 let new = curr.saturating_sub(count);
899
900 if data
902 .refcount
903 .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
904 .is_ok()
905 {
906 if new == 0 {
907 if data.handle.file_handle().is_some()
908 && (data.btime.tv_sec != 0 || data.btime.tv_nsec != 0)
909 {
910 let key = FileUniqueKey(data.id.ino, data.btime);
911 let cache = self.handle_cache.clone();
912 cache.invalidate(&key).await;
913 }
914 let keep_mapping = !self.cfg.use_host_ino || data.id.ino > MAX_HOST_INO;
918 inodes.remove(&inode, keep_mapping);
919 }
920 break;
921 }
922 }
923 }
924 }
925
926 async fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
927 self.handle_map.release(handle, inode).await
928 }
929
930 fn validate_path_component(&self, name: &CStr) -> io::Result<()> {
933 if !self.cfg.do_import {
935 return Ok(());
936 }
937 validate_path_component(name)
938 }
939
940 async fn get_writeback_open_flags(&self, flags: i32) -> i32 {
992 let mut new_flags = flags;
993 let writeback = self.writeback.load(Ordering::Relaxed);
994
995 if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
999 new_flags &= !libc::O_ACCMODE;
1000 new_flags |= libc::O_RDWR;
1001 }
1002
1003 if writeback && flags & libc::O_APPEND != 0 {
1010 new_flags &= !libc::O_APPEND;
1011 }
1012
1013 new_flags
1014 }
1015
1016 async fn get_mmap(
1017 &self,
1018 inode: Inode,
1019 offset: u64,
1020 file: &File,
1021 ) -> Option<(Arc<RwLock<mmap::MmapCachedValue>>, u64)> {
1022 let file_size = file.metadata().unwrap().len();
1023 let key = MmapChunkKey::new(inode, offset, file_size);
1024 let aligned_offset = key.aligned_offset;
1025
1026 if let Some(cached) = self.mmap_chunks.get(&key).await {
1027 let guard = cached.read().await;
1028 let cache_len = match &*guard {
1029 MmapCachedValue::Mmap(mmap) => mmap.len() as u64,
1030 MmapCachedValue::MmapMut(mmap_mut) => mmap_mut.len() as u64,
1031 };
1032 if offset < key.aligned_offset + cache_len {
1033 return Some((cached.clone(), key.aligned_offset));
1034 }
1035 }
1036
1037 let mmap = match mmap::create_mmap(offset, file).await {
1038 Ok(v) => v,
1039 Err(e) => {
1040 error!("Failed to create mmap:{e}");
1041 return None;
1042 }
1043 };
1044 self.mmap_chunks.insert(key, mmap.clone()).await;
1045 Some((mmap, aligned_offset))
1046 }
1047
1048 async fn read_from_mmap(
1049 &self,
1050 inode: Inode,
1051 offset: u64,
1052 size: u64,
1053 file: &File,
1054 buf: &mut [u8],
1055 ) -> Result<usize> {
1056 if buf.len() < size as usize {
1058 return Err(std::io::Error::new(
1059 std::io::ErrorKind::InvalidInput,
1060 format!("Buffer too small: {} < {}", buf.len(), size),
1061 ));
1062 }
1063
1064 let file_size = file.metadata()?.len();
1065
1066 if offset >= file_size {
1068 return Ok(0); }
1070
1071 let max_readable = file_size - offset;
1073 let actual_size = cmp::min(size, max_readable) as usize;
1074
1075 let mut len = actual_size;
1076 let mut current_offset = offset;
1077 let mut buf_offset = 0;
1078
1079 while len > 0 {
1080 let (chunk, chunk_start_offset) = match self.get_mmap(inode, current_offset, file).await
1081 {
1082 Some((chunk, aligned_offset)) => (chunk, aligned_offset),
1083 None => {
1084 return Err(std::io::Error::other("Failed to get mmap chunk"));
1085 }
1086 };
1087
1088 let chunk_guard = chunk.read().await;
1089 match &*chunk_guard {
1090 MmapCachedValue::Mmap(mmap) => {
1091 let chunk_len = mmap.len();
1092
1093 let copy_start = (current_offset - chunk_start_offset) as usize;
1095
1096 let remaining_in_chunk = chunk_len - copy_start;
1098 let copy_len = cmp::min(len, remaining_in_chunk);
1099
1100 let copy_len = cmp::min(copy_len, buf.len() - buf_offset);
1102
1103 if copy_len == 0 {
1104 break; }
1106
1107 buf[buf_offset..buf_offset + copy_len]
1109 .copy_from_slice(&mmap[copy_start..copy_start + copy_len]);
1110
1111 buf_offset += copy_len;
1112 len -= copy_len;
1113 current_offset += copy_len as u64;
1114 }
1115 MmapCachedValue::MmapMut(mmap_mut) => {
1116 let chunk_len = mmap_mut.len();
1117
1118 let copy_start = (current_offset - chunk_start_offset) as usize;
1120
1121 let remaining_in_chunk = chunk_len - copy_start;
1123 let copy_len = cmp::min(len, remaining_in_chunk);
1124
1125 let copy_len = cmp::min(copy_len, buf.len() - buf_offset);
1127
1128 if copy_len == 0 {
1129 break; }
1131
1132 buf[buf_offset..buf_offset + copy_len]
1134 .copy_from_slice(&mmap_mut[copy_start..copy_start + copy_len]);
1135
1136 buf_offset += copy_len;
1137 len -= copy_len;
1138 current_offset += copy_len as u64;
1139 }
1140 }
1141 }
1142 Ok(buf_offset)
1143 }
1144
1145 async fn write_to_mmap(
1146 &self,
1147 inode: Inode,
1148 offset: u64,
1149 data: &[u8],
1150 file: &File,
1151 ) -> Result<usize> {
1152 let file_size = file.metadata()?.len();
1153 let len = data.len();
1154
1155 if offset + len as u64 > file_size {
1157 let raw_fd = file.as_raw_fd();
1158 let res = unsafe { libc::ftruncate(raw_fd, (offset + len as u64) as i64) };
1159
1160 if res < 0 {
1161 return Err(std::io::Error::other("error to ftruncate"));
1162 }
1163
1164 self.invalidate_mmap_cache(inode, file_size).await;
1165 }
1166
1167 let mut remaining = len;
1168 let mut current_offset = offset;
1169 let mut data_offset = 0;
1170
1171 while remaining > 0 {
1172 let (chunk, chunk_start_offset) = match self.get_mmap(inode, current_offset, file).await
1173 {
1174 Some((chunk, aligned_offset)) => (chunk, aligned_offset),
1175 None => {
1176 return Err(std::io::Error::other("Failed to get mmap chunk"));
1177 }
1178 };
1179
1180 let mut chunk_guard = chunk.write().await;
1181 match &mut *chunk_guard {
1182 MmapCachedValue::Mmap(_) => {
1183 return Err(std::io::Error::new(
1184 std::io::ErrorKind::PermissionDenied,
1185 "Cannot write to read-only mmap",
1186 ));
1187 }
1188 MmapCachedValue::MmapMut(mmap_mut) => {
1189 let chunk_len = mmap_mut.len();
1190
1191 let copy_start = (current_offset - chunk_start_offset) as usize;
1193
1194 let remaining_in_chunk = chunk_len - copy_start;
1196 let copy_len = cmp::min(remaining, remaining_in_chunk);
1197
1198 let copy_len = cmp::min(copy_len, data.len() - data_offset);
1200
1201 if copy_len == 0 {
1202 break; }
1204
1205 mmap_mut[copy_start..copy_start + copy_len]
1207 .copy_from_slice(&data[data_offset..data_offset + copy_len]);
1208
1209 data_offset += copy_len;
1210 remaining -= copy_len;
1211 current_offset += copy_len as u64;
1212 mmap_mut.flush_async_range(copy_start, copy_len)?;
1213 }
1214 }
1215 }
1216 Ok(data_offset)
1217 }
1218
1219 async fn invalidate_mmap_cache(&self, inode: Inode, old_size: u64) {
1220 let keys_to_remove: Vec<_> = self
1221 .mmap_chunks
1222 .iter()
1223 .filter(|item| {
1224 let key = item.0.clone();
1225 key.inode == inode && key.aligned_offset + mmap::MAX_WINDOW_SIZE as u64 >= old_size
1226 })
1227 .collect();
1228
1229 for item in keys_to_remove {
1230 self.mmap_chunks.invalidate(item.0.as_ref()).await;
1231 }
1232 }
1233}
1234
1235#[cfg(test)]
1236#[allow(unused_imports)]
1237mod tests {
1238 use crate::{
1239 passthrough::{PassthroughArgs, PassthroughFs, ROOT_ID, new_passthroughfs_layer},
1240 unwrap_or_skip_eperm,
1241 };
1242 use std::ffi::{CStr, OsStr, OsString};
1243
1244 use nix::unistd::{Gid, Uid, getgid, getuid};
1245 use rfuse3::{
1246 MountOptions,
1247 raw::{Filesystem, Request, Session},
1248 };
1249
1250 macro_rules! pass {
1251 () => {
1252 ()
1253 };
1254 ($($tt:tt)*) => {
1255 ()
1256 };
1257 }
1258
1259 #[tokio::test]
1264 async fn test_passthrough() {
1265 let temp_dir = std::env::temp_dir().join("libfuse_passthrough_test");
1266 let source_dir = temp_dir.join("src");
1267 let mount_dir = temp_dir.join("mnt");
1268 let _ = std::fs::create_dir_all(&source_dir);
1269 let _ = std::fs::create_dir_all(&mount_dir);
1270
1271 let args = PassthroughArgs {
1272 root_dir: source_dir.clone(),
1273 mapping: None::<&str>,
1274 };
1275 let fs = match super::new_passthroughfs_layer(args).await {
1276 Ok(fs) => fs,
1277 Err(e) => {
1278 eprintln!("skip test_passthrough: init failed: {e:?}");
1279 return;
1280 }
1281 };
1282
1283 let uid = unsafe { libc::getuid() };
1284 let gid = unsafe { libc::getgid() };
1285
1286 let mut mount_options = MountOptions::default();
1287 mount_options.force_readdir_plus(true).uid(uid).gid(gid);
1288 let mount_path = OsString::from(mount_dir.to_str().unwrap());
1291
1292 let session = Session::new(mount_options);
1293 let mount_handle =
1294 unwrap_or_skip_eperm!(session.mount(fs, mount_path).await, "mount passthrough fs");
1295
1296 let _ = mount_handle.unmount().await; let _ = std::fs::remove_dir_all(&temp_dir);
1301 }
1302
1303 #[tokio::test]
1335 async fn test_lookup_and_getattr() {
1336 pass!()
1337 }
1338 #[tokio::test]
1379 async fn test_create() {
1380 pass!()
1381 }
1382 }