1use std::any::Any;
15use std::collections::{btree_map, BTreeMap};
16use std::ffi::{CStr, CString, OsString};
17use std::fs::File;
18use std::io;
19use std::marker::PhantomData;
20use std::ops::{Deref, DerefMut};
21use std::os::fd::{AsFd, BorrowedFd};
22use std::os::unix::ffi::OsStringExt;
23use std::os::unix::io::{AsRawFd, RawFd};
24use std::path::PathBuf;
25use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
26use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockWriteGuard};
27use std::time::Duration;
28
29use vm_memory::{bitmap::BitmapSlice, ByteValued};
30
31pub use self::config::{CachePolicy, Config};
32use self::file_handle::{FileHandle, OpenableFileHandle};
33use self::inode_store::{InodeId, InodeStore};
34use self::mount_fd::MountFds;
35use self::statx::{statx, StatExt};
36use self::util::{
37 ebadf, einval, enosys, eperm, is_dir, is_safe_inode, openat, reopen_fd_through_proc, stat_fd,
38 UniqueInodeGenerator,
39};
40use crate::abi::fuse_abi as fuse;
41use crate::abi::fuse_abi::Opcode;
42use crate::api::filesystem::Entry;
43use crate::api::{
44 validate_path_component, BackendFileSystem, CURRENT_DIR_CSTR, EMPTY_CSTR, PARENT_DIR_CSTR,
45 PROC_SELF_FD_CSTR, SLASH_ASCII, VFS_MAX_INO,
46};
47
48#[cfg(feature = "async-io")]
49mod async_io;
50mod config;
51mod file_handle;
52mod inode_store;
53mod mount_fd;
54mod os_compat;
55mod overlay;
56mod statx;
57mod sync_io;
58mod util;
59
60type Inode = u64;
61type Handle = u64;
62
63const MAX_HOST_INO: u64 = 0x7fff_ffff_ffff;
65
66#[derive(Debug)]
74enum InodeFile<'a> {
75 Owned(File),
76 Ref(&'a File),
77}
78
79impl AsRawFd for InodeFile<'_> {
80 fn as_raw_fd(&self) -> RawFd {
83 match self {
84 Self::Owned(file) => file.as_raw_fd(),
85 Self::Ref(file_ref) => file_ref.as_raw_fd(),
86 }
87 }
88}
89
90impl AsFd for InodeFile<'_> {
91 fn as_fd(&self) -> BorrowedFd<'_> {
92 match self {
93 Self::Owned(file) => file.as_fd(),
94 Self::Ref(file_ref) => file_ref.as_fd(),
95 }
96 }
97}
98
99#[derive(Debug)]
100enum InodeHandle {
101 File(File),
102 Handle(Arc<OpenableFileHandle>),
103}
104
105impl InodeHandle {
106 fn file_handle(&self) -> Option<&FileHandle> {
107 match self {
108 InodeHandle::File(_) => None,
109 InodeHandle::Handle(h) => Some(h.file_handle().deref()),
110 }
111 }
112
113 fn get_file(&self) -> io::Result<InodeFile<'_>> {
114 match self {
115 InodeHandle::File(f) => Ok(InodeFile::Ref(f)),
116 InodeHandle::Handle(h) => {
117 let f = h.open(libc::O_PATH)?;
118 Ok(InodeFile::Owned(f))
119 }
120 }
121 }
122
123 fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result<File> {
124 match self {
125 InodeHandle::File(f) => reopen_fd_through_proc(f, flags, proc_self_fd),
126 InodeHandle::Handle(h) => h.open(flags),
127 }
128 }
129
130 fn stat(&self) -> io::Result<libc::stat64> {
131 match self {
132 InodeHandle::File(f) => stat_fd(f, None),
133 InodeHandle::Handle(_h) => {
134 let file = self.get_file()?;
135 stat_fd(&file, None)
136 }
137 }
138 }
139}
140
141#[derive(Debug)]
143pub struct InodeData {
144 inode: Inode,
145 handle: InodeHandle,
147 id: InodeId,
148 refcount: AtomicU64,
149 mode: u32,
151}
152
153impl InodeData {
154 fn new(inode: Inode, f: InodeHandle, refcount: u64, id: InodeId, mode: u32) -> Self {
155 InodeData {
156 inode,
157 handle: f,
158 id,
159 refcount: AtomicU64::new(refcount),
160 mode,
161 }
162 }
163
164 fn get_file(&self) -> io::Result<InodeFile<'_>> {
165 self.handle.get_file()
166 }
167
168 fn open_file(&self, flags: libc::c_int, proc_self_fd: &File) -> io::Result<File> {
169 self.handle.open_file(flags, proc_self_fd)
170 }
171}
172
173struct InodeMap {
175 inodes: RwLock<InodeStore>,
176}
177
178impl InodeMap {
179 fn new() -> Self {
180 InodeMap {
181 inodes: RwLock::new(Default::default()),
182 }
183 }
184
185 fn clear(&self) {
186 self.inodes.write().unwrap().clear();
188 }
189
190 fn get(&self, inode: Inode) -> io::Result<Arc<InodeData>> {
191 self.inodes
193 .read()
194 .unwrap()
195 .get(&inode)
196 .map(Arc::clone)
197 .ok_or_else(ebadf)
198 }
199
200 fn get_inode_locked(
201 inodes: &InodeStore,
202 id: &InodeId,
203 handle: Option<&FileHandle>,
204 ) -> Option<Inode> {
205 match handle {
206 Some(h) => inodes.inode_by_handle(h).copied(),
207 None => inodes.inode_by_id(id).copied(),
208 }
209 }
210
211 fn get_alt(&self, id: &InodeId, handle: Option<&FileHandle>) -> Option<Arc<InodeData>> {
212 let inodes = self.inodes.read().unwrap();
214
215 Self::get_alt_locked(inodes.deref(), id, handle)
216 }
217
218 fn get_alt_locked(
219 inodes: &InodeStore,
220 id: &InodeId,
221 handle: Option<&FileHandle>,
222 ) -> Option<Arc<InodeData>> {
223 handle
224 .and_then(|h| inodes.get_by_handle(h))
225 .or_else(|| {
226 inodes.get_by_id(id).filter(|data| {
227 handle.is_none() || data.handle.file_handle().is_none()
236 })
237 })
238 .map(Arc::clone)
239 }
240
241 fn get_map_mut(&self) -> RwLockWriteGuard<InodeStore> {
242 self.inodes.write().unwrap()
244 }
245
246 fn insert(&self, data: Arc<InodeData>) {
247 let mut inodes = self.get_map_mut();
248
249 Self::insert_locked(inodes.deref_mut(), data)
250 }
251
252 fn insert_locked(inodes: &mut InodeStore, data: Arc<InodeData>) {
253 inodes.insert(data);
254 }
255}
256
257struct HandleData {
258 inode: Inode,
259 file: File,
260 lock: Mutex<()>,
261 open_flags: AtomicU32,
262}
263
264impl HandleData {
265 fn new(inode: Inode, file: File, flags: u32) -> Self {
266 HandleData {
267 inode,
268 file,
269 lock: Mutex::new(()),
270 open_flags: AtomicU32::new(flags),
271 }
272 }
273
274 fn get_file(&self) -> &File {
275 &self.file
276 }
277
278 fn get_file_mut(&self) -> (MutexGuard<()>, &File) {
279 (self.lock.lock().unwrap(), &self.file)
280 }
281
282 fn borrow_fd(&self) -> BorrowedFd {
283 self.file.as_fd()
284 }
285
286 fn get_flags(&self) -> u32 {
287 self.open_flags.load(Ordering::Relaxed)
288 }
289
290 fn set_flags(&self, flags: u32) {
291 self.open_flags.store(flags, Ordering::Relaxed);
292 }
293}
294
295struct HandleMap {
296 handles: RwLock<BTreeMap<Handle, Arc<HandleData>>>,
297}
298
299impl HandleMap {
300 fn new() -> Self {
301 HandleMap {
302 handles: RwLock::new(BTreeMap::new()),
303 }
304 }
305
306 fn clear(&self) {
307 self.handles.write().unwrap().clear();
309 }
310
311 fn insert(&self, handle: Handle, data: HandleData) {
312 self.handles.write().unwrap().insert(handle, Arc::new(data));
314 }
315
316 fn release(&self, handle: Handle, inode: Inode) -> io::Result<()> {
317 let mut handles = self.handles.write().unwrap();
319
320 if let btree_map::Entry::Occupied(e) = handles.entry(handle) {
321 if e.get().inode == inode {
322 e.remove();
325 return Ok(());
326 }
327 }
328
329 Err(ebadf())
330 }
331
332 fn get(&self, handle: Handle, inode: Inode) -> io::Result<Arc<HandleData>> {
333 self.handles
335 .read()
336 .unwrap()
337 .get(&handle)
338 .filter(|hd| hd.inode == inode)
339 .map(Arc::clone)
340 .ok_or_else(ebadf)
341 }
342}
343
344pub struct PassthroughFs<S: BitmapSlice + Send + Sync = ()> {
352 inode_map: InodeMap,
357 next_inode: AtomicU64,
358
359 handle_map: HandleMap,
362 next_handle: AtomicU64,
363
364 ino_allocator: UniqueInodeGenerator,
366 mount_fds: MountFds,
368
369 proc_self_fd: File,
374
375 writeback: AtomicBool,
378
379 no_open: AtomicBool,
381
382 no_opendir: AtomicBool,
384
385 killpriv_v2: AtomicBool,
387
388 no_readdir: AtomicBool,
390
391 seal_size: AtomicBool,
393
394 perfile_dax: AtomicBool,
397
398 dir_entry_timeout: Duration,
399 dir_attr_timeout: Duration,
400
401 cfg: Config,
402
403 phantom: PhantomData<S>,
404}
405
406impl<S: BitmapSlice + Send + Sync> PassthroughFs<S> {
407 pub fn new(mut cfg: Config) -> io::Result<PassthroughFs<S>> {
409 if cfg.no_open && cfg.cache_policy != CachePolicy::Always {
410 warn!("passthroughfs: no_open only work with cache=always, reset to open mode");
411 cfg.no_open = false;
412 }
413 if cfg.writeback && cfg.cache_policy == CachePolicy::Never {
414 warn!(
415 "passthroughfs: writeback cache conflicts with cache=none, reset to no_writeback"
416 );
417 cfg.writeback = false;
418 }
419
420 let proc_self_fd_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_SELF_FD_CSTR) };
422 let proc_self_fd = Self::open_file(
423 &libc::AT_FDCWD,
424 proc_self_fd_cstr,
425 libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
426 0,
427 )?;
428
429 let (dir_entry_timeout, dir_attr_timeout) =
430 match (cfg.dir_entry_timeout, cfg.dir_attr_timeout) {
431 (Some(e), Some(a)) => (e, a),
432 (Some(e), None) => (e, cfg.attr_timeout),
433 (None, Some(a)) => (cfg.entry_timeout, a),
434 (None, None) => (cfg.entry_timeout, cfg.attr_timeout),
435 };
436
437 let mount_fds = MountFds::new(None)?;
438
439 Ok(PassthroughFs {
440 inode_map: InodeMap::new(),
441 next_inode: AtomicU64::new(fuse::ROOT_ID + 1),
442 ino_allocator: UniqueInodeGenerator::new(),
443
444 handle_map: HandleMap::new(),
445 next_handle: AtomicU64::new(1),
446
447 mount_fds,
448 proc_self_fd,
449
450 writeback: AtomicBool::new(false),
451 no_open: AtomicBool::new(false),
452 no_opendir: AtomicBool::new(false),
453 killpriv_v2: AtomicBool::new(false),
454 no_readdir: AtomicBool::new(cfg.no_readdir),
455 seal_size: AtomicBool::new(cfg.seal_size),
456 perfile_dax: AtomicBool::new(false),
457 dir_entry_timeout,
458 dir_attr_timeout,
459 cfg,
460
461 phantom: PhantomData,
462 })
463 }
464
465 pub fn import(&self) -> io::Result<()> {
467 let root = CString::new(self.cfg.root_dir.as_str()).expect("CString::new failed");
468
469 let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &libc::AT_FDCWD, &root)
470 .map_err(|e| {
471 error!("fuse: import: failed to get file or handle: {:?}", e);
472 e
473 })?;
474 let id = InodeId::from_stat(&st);
475 let handle = if let Some(h) = handle_opt {
476 InodeHandle::Handle(self.to_openable_handle(h)?)
477 } else {
478 InodeHandle::File(path_fd)
479 };
480
481 unsafe { libc::umask(0o000) };
485
486 self.inode_map.insert(Arc::new(InodeData::new(
488 fuse::ROOT_ID,
489 handle,
490 2,
491 id,
492 st.st.st_mode,
493 )));
494
495 Ok(())
496 }
497
498 pub fn keep_fds(&self) -> Vec<RawFd> {
500 vec![self.proc_self_fd.as_raw_fd()]
501 }
502
503 fn readlinkat(dfd: i32, pathname: &CStr) -> io::Result<PathBuf> {
504 let mut buf = Vec::with_capacity(libc::PATH_MAX as usize);
505
506 let buf_read = unsafe {
508 libc::readlinkat(
509 dfd,
510 pathname.as_ptr(),
511 buf.as_mut_ptr() as *mut libc::c_char,
512 buf.capacity(),
513 )
514 };
515 if buf_read < 0 {
516 error!("fuse: readlinkat error");
517 return Err(io::Error::last_os_error());
518 }
519
520 unsafe { buf.set_len(buf_read as usize) };
522 buf.shrink_to_fit();
523
524 Ok(PathBuf::from(OsString::from_vec(buf)))
528 }
529
530 pub fn readlinkat_proc_file(&self, inode: Inode) -> io::Result<PathBuf> {
533 let data = self.inode_map.get(inode)?;
534 let file = data.get_file()?;
535 let pathname = CString::new(format!("{}", file.as_raw_fd()))
536 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
537
538 Self::readlinkat(self.proc_self_fd.as_raw_fd(), &pathname)
539 }
540
541 fn create_file_excl(
542 dir: &impl AsRawFd,
543 pathname: &CStr,
544 flags: i32,
545 mode: u32,
546 ) -> io::Result<Option<File>> {
547 match openat(dir, pathname, flags | libc::O_CREAT | libc::O_EXCL, mode) {
548 Ok(file) => Ok(Some(file)),
549 Err(err) => {
550 if err.kind() == io::ErrorKind::AlreadyExists {
552 if (flags & libc::O_EXCL) != 0 {
553 return Err(err);
554 }
555 return Ok(None);
556 }
557 Err(err)
558 }
559 }
560 }
561
562 fn open_file(dfd: &impl AsRawFd, pathname: &CStr, flags: i32, mode: u32) -> io::Result<File> {
563 openat(dfd, pathname, flags, mode)
564 }
565
566 fn open_file_restricted(
567 &self,
568 dir: &impl AsRawFd,
569 pathname: &CStr,
570 flags: i32,
571 mode: u32,
572 ) -> io::Result<File> {
573 let flags = libc::O_NOFOLLOW | libc::O_CLOEXEC | flags;
574
575 openat(dir, pathname, flags, mode)
580 }
582
583 fn open_file_and_handle(
585 &self,
586 dir: &impl AsRawFd,
587 name: &CStr,
588 ) -> io::Result<(File, Option<FileHandle>, StatExt)> {
589 let path_file = self.open_file_restricted(dir, name, libc::O_PATH, 0)?;
590 let st = statx(&path_file, None)?;
591 let handle = if self.cfg.inode_file_handles {
592 FileHandle::from_fd(&path_file)?
593 } else {
594 None
595 };
596
597 Ok((path_file, handle, st))
598 }
599
600 fn to_openable_handle(&self, fh: FileHandle) -> io::Result<Arc<OpenableFileHandle>> {
601 fh.into_openable(&self.mount_fds, |fd, flags, _mode| {
602 reopen_fd_through_proc(&fd, flags, &self.proc_self_fd)
603 })
604 .map(Arc::new)
605 .map_err(|e| {
606 if !e.silent() {
607 error!("{}", e);
608 }
609 e.into_inner()
610 })
611 }
612
613 fn allocate_inode(
614 &self,
615 inodes: &InodeStore,
616 id: &InodeId,
617 handle_opt: Option<&FileHandle>,
618 ) -> io::Result<Inode> {
619 if !self.cfg.use_host_ino {
620 Ok(InodeMap::get_inode_locked(inodes, id, handle_opt)
623 .unwrap_or_else(|| self.next_inode.fetch_add(1, Ordering::Relaxed)))
624 } else {
625 let inode = if id.ino > MAX_HOST_INO {
626 match InodeMap::get_inode_locked(inodes, id, handle_opt) {
628 Some(ino) => ino,
629 None => self.ino_allocator.get_unique_inode(id)?,
630 }
631 } else {
632 self.ino_allocator.get_unique_inode(id)?
633 };
634
635 Ok(inode)
636 }
637 }
638
639 fn do_lookup(&self, parent: Inode, name: &CStr) -> io::Result<Entry> {
640 let name =
641 if parent == fuse::ROOT_ID && name.to_bytes_with_nul().starts_with(PARENT_DIR_CSTR) {
642 CStr::from_bytes_with_nul(CURRENT_DIR_CSTR).unwrap()
644 } else {
645 name
646 };
647
648 let dir = self.inode_map.get(parent)?;
649 let dir_file = dir.get_file()?;
650 let (path_fd, handle_opt, st) = Self::open_file_and_handle(self, &dir_file, name)?;
651 let id = InodeId::from_stat(&st);
652
653 let mut found = None;
654 'search: loop {
655 match self.inode_map.get_alt(&id, handle_opt.as_ref()) {
656 None => break 'search,
658 Some(data) => {
659 let curr = data.refcount.load(Ordering::Acquire);
660 if curr == 0 {
662 continue 'search;
663 }
664
665 let new = curr.saturating_add(1);
667
668 if data
670 .refcount
671 .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
672 .is_ok()
673 {
674 found = Some(data.inode);
675 break;
676 }
677 }
678 }
679 }
680
681 let inode = if let Some(v) = found {
682 v
683 } else {
684 let handle = if let Some(h) = handle_opt.clone() {
685 InodeHandle::Handle(self.to_openable_handle(h)?)
686 } else {
687 InodeHandle::File(path_fd)
688 };
689
690 let mut inodes = self.inode_map.get_map_mut();
692
693 match InodeMap::get_alt_locked(inodes.deref(), &id, handle_opt.as_ref()) {
698 Some(data) => {
699 data.refcount.fetch_add(1, Ordering::Relaxed);
702 data.inode
703 }
704 None => {
705 let inode = self.allocate_inode(inodes.deref(), &id, handle_opt.as_ref())?;
706
707 if inode > VFS_MAX_INO {
708 error!("fuse: max inode number reached: {}", VFS_MAX_INO);
709 return Err(io::Error::new(
710 io::ErrorKind::Other,
711 format!("max inode number reached: {VFS_MAX_INO}"),
712 ));
713 }
714
715 InodeMap::insert_locked(
716 inodes.deref_mut(),
717 Arc::new(InodeData::new(inode, handle, 1, id, st.st.st_mode)),
718 );
719 inode
720 }
721 }
722 };
723
724 let (entry_timeout, attr_timeout) = if is_dir(st.st.st_mode) {
725 (self.dir_entry_timeout, self.dir_attr_timeout)
726 } else {
727 (self.cfg.entry_timeout, self.cfg.attr_timeout)
728 };
729
730 let mut attr_flags: u32 = 0;
732 if let Some(dax_file_size) = self.cfg.dax_file_size {
733 if self.perfile_dax.load(Ordering::Relaxed)
735 && st.st.st_size >= 0x0
736 && st.st.st_size as u64 >= dax_file_size
737 {
738 attr_flags |= fuse::FUSE_ATTR_DAX;
739 }
740 }
741
742 Ok(Entry {
743 inode,
744 generation: 0,
745 attr: st.st,
746 attr_flags,
747 attr_timeout,
748 entry_timeout,
749 })
750 }
751
752 fn forget_one(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
753 if inode == fuse::ROOT_ID {
755 return;
756 }
757
758 if let Some(data) = inodes.get(&inode) {
759 loop {
764 let curr = data.refcount.load(Ordering::Acquire);
765
766 let new = curr.saturating_sub(count);
769
770 if data
772 .refcount
773 .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
774 .is_ok()
775 {
776 if new == 0 {
777 let keep_mapping = !self.cfg.use_host_ino || inode > MAX_HOST_INO;
781 inodes.remove(&inode, keep_mapping);
782 }
783 break;
784 }
785 }
786 }
787 }
788
789 fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
790 self.handle_map.release(handle, inode)
791 }
792
793 fn validate_path_component(&self, name: &CStr) -> io::Result<()> {
796 if !self.cfg.do_import {
798 return Ok(());
799 }
800 validate_path_component(name)
801 }
802
803 fn seal_size_check(
806 &self,
807 opcode: Opcode,
808 file_size: u64,
809 offset: u64,
810 size: u64,
811 mode: i32,
812 ) -> io::Result<()> {
813 if offset.checked_add(size).is_none() {
814 error!(
815 "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX",
816 opcode, offset, size
817 );
818 return Err(einval());
819 }
820
821 match opcode {
822 Opcode::Write => {
824 if size + offset > file_size {
825 return Err(eperm());
826 }
827 }
828
829 Opcode::Fallocate => {
830 let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE);
831 match op {
832 0 | libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_ZERO_RANGE => {
834 if size + offset > file_size {
835 return Err(eperm());
836 }
837 }
838 libc::FALLOC_FL_COLLAPSE_RANGE | libc::FALLOC_FL_INSERT_RANGE => {
840 return Err(eperm());
841 }
842 _ => return Err(einval()),
844 }
845 }
846
847 _ => return Err(enosys()),
849 }
850
851 Ok(())
852 }
853
854 fn get_writeback_open_flags(&self, flags: i32) -> i32 {
855 let mut new_flags = flags;
856 let writeback = self.writeback.load(Ordering::Relaxed);
857
858 if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
862 new_flags &= !libc::O_ACCMODE;
863 new_flags |= libc::O_RDWR;
864 }
865
866 if writeback && flags & libc::O_APPEND != 0 {
873 new_flags &= !libc::O_APPEND;
874 }
875
876 new_flags
877 }
878}
879
880#[cfg(not(feature = "async-io"))]
881impl<S: BitmapSlice + Send + Sync + 'static> BackendFileSystem for PassthroughFs<S> {
882 fn mount(&self) -> io::Result<(Entry, u64)> {
883 let entry = self.do_lookup(fuse::ROOT_ID, &CString::new(".").unwrap())?;
884 Ok((entry, VFS_MAX_INO))
885 }
886
887 fn as_any(&self) -> &dyn Any {
888 self
889 }
890}
891
892macro_rules! scoped_cred {
893 ($name:ident, $ty:ty, $syscall_nr:expr) => {
894 #[derive(Debug)]
895 pub(crate) struct $name;
896
897 impl $name {
898 fn new(val: $ty) -> io::Result<Option<$name>> {
901 if val == 0 {
902 return Ok(None);
904 }
905
906 let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) };
921 if res == 0 {
922 Ok(Some($name))
923 } else {
924 Err(io::Error::last_os_error())
925 }
926 }
927 }
928
929 impl Drop for $name {
930 fn drop(&mut self) {
931 let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) };
932 if res < 0 {
933 error!(
934 "fuse: failed to change credentials back to root: {}",
935 io::Error::last_os_error(),
936 );
937 }
938 }
939 }
940 };
941}
942scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid);
943scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid);
944
945fn set_creds(
946 uid: libc::uid_t,
947 gid: libc::gid_t,
948) -> io::Result<(Option<ScopedUid>, Option<ScopedGid>)> {
949 ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid)))
952}
953
954struct CapFsetid {}
955
956impl Drop for CapFsetid {
957 fn drop(&mut self) {
958 if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) {
959 error!("fail to restore thread cap_fsetid: {}", e);
960 };
961 }
962}
963
964fn drop_cap_fsetid() -> io::Result<Option<CapFsetid>> {
965 if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID)
966 .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))?
967 {
968 return Ok(None);
969 }
970 caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| {
971 io::Error::new(
972 io::ErrorKind::PermissionDenied,
973 "failed to drop CAP_FSETID capability",
974 )
975 })?;
976 Ok(Some(CapFsetid {}))
977}
978
979#[cfg(test)]
980mod tests {
981 use super::*;
982 use crate::abi::fuse_abi::CreateIn;
983 use crate::api::filesystem::*;
984 use crate::api::{Vfs, VfsOptions};
985 use caps::{CapSet, Capability};
986 use log;
987 use std::io::{Read, Seek, SeekFrom, Write};
988 use std::ops::Deref;
989 use std::os::unix::prelude::MetadataExt;
990 use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile};
991
992 fn prepare_passthroughfs() -> PassthroughFs {
993 let source = TempDir::new().expect("Cannot create temporary directory.");
994 let parent_path =
995 TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
996 let _child_path =
997 TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
998
999 let fs_cfg = Config {
1000 writeback: true,
1001 do_import: true,
1002 no_open: true,
1003 inode_file_handles: false,
1004 root_dir: source
1005 .as_path()
1006 .to_str()
1007 .expect("source path to string")
1008 .to_string(),
1009 ..Default::default()
1010 };
1011 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1012 fs.import().unwrap();
1013
1014 fs
1015 }
1016
1017 fn passthroughfs_no_open(cfg: bool) {
1018 let opts = VfsOptions {
1019 no_open: cfg,
1020 ..Default::default()
1021 };
1022
1023 let vfs = &Vfs::new(opts);
1024 vfs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1026
1027 let fs_cfg = Config {
1028 do_import: false,
1029 no_open: cfg,
1030 ..Default::default()
1031 };
1032 let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1033 fs.import().unwrap();
1034 vfs.mount(Box::new(fs), "/submnt/A").unwrap();
1035
1036 let p_fs = vfs.get_rootfs("/submnt/A").unwrap().unwrap();
1037 let any_fs = p_fs.deref().as_any();
1038 any_fs
1039 .downcast_ref::<PassthroughFs>()
1040 .map(|fs| {
1041 assert_eq!(fs.no_open.load(Ordering::Relaxed), cfg);
1042 })
1043 .unwrap();
1044 }
1045
1046 #[test]
1047 fn test_passthroughfs_no_open() {
1048 passthroughfs_no_open(true);
1049 passthroughfs_no_open(false);
1050 }
1051
1052 #[test]
1053 fn test_passthroughfs_inode_file_handles() {
1054 log::set_max_level(log::LevelFilter::Trace);
1055
1056 match caps::has_cap(None, CapSet::Effective, Capability::CAP_DAC_READ_SEARCH) {
1057 Ok(false) | Err(_) => {
1058 println!("invoking open_by_handle_at needs CAP_DAC_READ_SEARCH");
1059 return;
1060 }
1061 Ok(true) => {}
1062 }
1063
1064 let source = TempDir::new().expect("Cannot create temporary directory.");
1065 let parent_path =
1066 TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1067 let child_path =
1068 TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1069
1070 let fs_cfg = Config {
1071 writeback: true,
1072 do_import: true,
1073 no_open: true,
1074 inode_file_handles: true,
1075 root_dir: source
1076 .as_path()
1077 .to_str()
1078 .expect("source path to string")
1079 .to_string(),
1080 ..Default::default()
1081 };
1082 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1083 fs.import().unwrap();
1084
1085 let ctx = Context::default();
1086
1087 let parent = CString::new(
1089 parent_path
1090 .as_path()
1091 .file_name()
1092 .unwrap()
1093 .to_str()
1094 .expect("path to string"),
1095 )
1096 .unwrap();
1097 let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1098 let p_inode = p_entry.inode;
1099
1100 let child = CString::new(
1101 child_path
1102 .as_path()
1103 .file_name()
1104 .unwrap()
1105 .to_str()
1106 .expect("path to string"),
1107 )
1108 .unwrap();
1109 let c_entry = fs.lookup(&ctx, p_inode, &child).unwrap();
1110
1111 let (_, duration) = fs.getattr(&ctx, c_entry.inode, None).unwrap();
1116 assert_eq!(duration, fs.cfg.attr_timeout);
1117
1118 fs.destroy();
1119 }
1120
1121 #[test]
1122 fn test_lookup_escape_root() {
1123 let fs = prepare_passthroughfs();
1124 let ctx = Context::default();
1125
1126 let name = CString::new("..").unwrap();
1127 let entry = fs.lookup(&ctx, ROOT_ID, &name).unwrap();
1128 assert_eq!(entry.inode, ROOT_ID);
1129 }
1130
1131 #[test]
1132 fn test_get_writeback_open_flags() {
1133 let mut fs = prepare_passthroughfs();
1137 fs.writeback = AtomicBool::new(true);
1138 fs.no_open = AtomicBool::new(false);
1139
1140 assert!(fs.writeback.load(Ordering::Relaxed));
1141 assert!(!fs.no_open.load(Ordering::Relaxed));
1142
1143 let flags = libc::O_RDWR;
1144 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1145
1146 let flags = libc::O_RDONLY;
1147 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1148
1149 let flags = libc::O_WRONLY;
1150 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1151
1152 let flags = libc::O_RDWR | libc::O_APPEND;
1153 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1154
1155 let flags = libc::O_RDONLY | libc::O_APPEND;
1156 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1157
1158 let flags = libc::O_WRONLY | libc::O_APPEND;
1159 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1160
1161 let mut fs = prepare_passthroughfs();
1163 fs.writeback = AtomicBool::new(false);
1164 fs.no_open = AtomicBool::new(false);
1165
1166 assert!(!fs.writeback.load(Ordering::Relaxed));
1167 assert!(!fs.no_open.load(Ordering::Relaxed));
1168
1169 let flags = libc::O_RDWR;
1170 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1171
1172 let flags = libc::O_RDONLY;
1173 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1174
1175 let flags = libc::O_WRONLY;
1176 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_WRONLY);
1177
1178 let flags = libc::O_RDWR | libc::O_APPEND;
1179 assert_eq!(
1180 fs.get_writeback_open_flags(flags),
1181 libc::O_RDWR | libc::O_APPEND
1182 );
1183
1184 let flags = libc::O_RDONLY | libc::O_APPEND;
1185 assert_eq!(
1186 fs.get_writeback_open_flags(flags),
1187 libc::O_RDONLY | libc::O_APPEND
1188 );
1189
1190 let flags = libc::O_WRONLY | libc::O_APPEND;
1191 assert_eq!(
1192 fs.get_writeback_open_flags(flags),
1193 libc::O_WRONLY | libc::O_APPEND
1194 );
1195 }
1196
1197 #[test]
1198 fn test_writeback_open_and_create() {
1199 let source = TempDir::new().expect("Cannot create temporary directory.");
1202 let _ = std::process::Command::new("sh")
1203 .arg("-c")
1204 .arg(format!("touch {}/existfile", source.as_path().to_str().unwrap()).as_str())
1205 .output()
1206 .unwrap();
1207 let fs_cfg = Config {
1208 writeback: true,
1209 do_import: true,
1210 no_open: false,
1211 inode_file_handles: false,
1212 root_dir: source
1213 .as_path()
1214 .to_str()
1215 .expect("source path to string")
1216 .to_string(),
1217 ..Default::default()
1218 };
1219 let mut fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1220 fs.writeback = AtomicBool::new(true);
1221 fs.no_open = AtomicBool::new(false);
1222 fs.import().unwrap();
1223
1224 assert!(fs.writeback.load(Ordering::Relaxed));
1225 assert!(!fs.no_open.load(Ordering::Relaxed));
1226
1227 let ctx = Context::default();
1228
1229 let fname = CString::new("testfile").unwrap();
1231 let args = CreateIn {
1232 flags: libc::O_WRONLY as u32,
1233 mode: 0644,
1234 umask: 0,
1235 fuse_flags: 0,
1236 };
1237 let (entry, handle, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1238 let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1239 let (_guard, mut f) = handle_data.get_file_mut();
1240 let mut buf = [0; 4];
1241 let n = f.read(&mut buf).unwrap();
1243 assert_eq!(n, 0);
1244
1245 let fname = CString::new("existfile").unwrap();
1247 let entry = fs.lookup(&ctx, ROOT_ID, &fname).unwrap();
1248 let (handle, _, _) = fs
1249 .open(&ctx, entry.inode, libc::O_WRONLY as u32, 0)
1250 .unwrap();
1251 let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1252 let (_guard, mut f) = handle_data.get_file_mut();
1253 let mut buf = [0; 4];
1254 let n = f.read(&mut buf).unwrap();
1255 assert_eq!(n, 0);
1256 }
1257
1258 #[test]
1259 fn test_passthroughfs_dir_timeout() {
1260 log::set_max_level(log::LevelFilter::Trace);
1261
1262 let source = TempDir::new().expect("Cannot create temporary directory.");
1263 let parent_path =
1264 TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1265 let child_path =
1266 TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1267
1268 let fs_cfg = Config {
1270 writeback: false,
1271 do_import: true,
1272 no_open: false,
1273 root_dir: source
1274 .as_path()
1275 .to_str()
1276 .expect("source path to string")
1277 .to_string(),
1278 cache_policy: CachePolicy::Never,
1279 entry_timeout: Duration::from_secs(0),
1280 attr_timeout: Duration::from_secs(0),
1281 dir_entry_timeout: Some(Duration::from_secs(1)),
1282 dir_attr_timeout: Some(Duration::from_secs(2)),
1283 ..Default::default()
1284 };
1285 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1286 fs.import().unwrap();
1287
1288 let ctx = Context::default();
1289
1290 let parent = CString::new(
1292 parent_path
1293 .as_path()
1294 .file_name()
1295 .unwrap()
1296 .to_str()
1297 .expect("path to string"),
1298 )
1299 .unwrap();
1300 let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1301 assert_eq!(p_entry.entry_timeout, Duration::from_secs(1));
1302 assert_eq!(p_entry.attr_timeout, Duration::from_secs(2));
1303
1304 let child = CString::new(
1306 child_path
1307 .as_path()
1308 .file_name()
1309 .unwrap()
1310 .to_str()
1311 .expect("path to string"),
1312 )
1313 .unwrap();
1314 let c_entry = fs.lookup(&ctx, p_entry.inode, &child).unwrap();
1315 assert_eq!(c_entry.entry_timeout, Duration::from_secs(0));
1316 assert_eq!(c_entry.attr_timeout, Duration::from_secs(0));
1317
1318 fs.destroy();
1319 }
1320
1321 #[test]
1322 fn test_stable_inode() {
1323 use std::os::unix::fs::MetadataExt;
1324 let source = TempDir::new().expect("Cannot create temporary directory.");
1325 let child_path = TempFile::new_in(source.as_path()).expect("Cannot create temporary file.");
1326 let child = CString::new(
1327 child_path
1328 .as_path()
1329 .file_name()
1330 .unwrap()
1331 .to_str()
1332 .expect("path to string"),
1333 )
1334 .unwrap();
1335 let meta = child_path.as_file().metadata().unwrap();
1336 let ctx = Context::default();
1337 {
1338 let fs_cfg = Config {
1339 writeback: true,
1340 do_import: true,
1341 no_open: true,
1342 inode_file_handles: false,
1343 root_dir: source
1344 .as_path()
1345 .to_str()
1346 .expect("source path to string")
1347 .to_string(),
1348 ..Default::default()
1349 };
1350 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1351 fs.import().unwrap();
1352 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1353 assert_eq!(entry.inode, ROOT_ID + 1);
1354 fs.forget(&ctx, entry.inode, 1);
1355 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1356 assert_eq!(entry.inode, ROOT_ID + 1);
1357 }
1358 {
1359 let fs_cfg = Config {
1360 writeback: true,
1361 do_import: true,
1362 no_open: true,
1363 inode_file_handles: false,
1364 root_dir: source
1365 .as_path()
1366 .to_str()
1367 .expect("source path to string")
1368 .to_string(),
1369 use_host_ino: true,
1370 ..Default::default()
1371 };
1372 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1373 fs.import().unwrap();
1374 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1375 assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1376 fs.forget(&ctx, entry.inode, 1);
1377 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1378 assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1379 }
1380 }
1381
1382 #[test]
1383 fn test_allocation_inode_locked() {
1384 {
1385 let fs = prepare_passthroughfs();
1386 let m = InodeStore::default();
1387 let id = InodeId {
1388 ino: MAX_HOST_INO + 1,
1389 dev: 1,
1390 mnt: 1,
1391 };
1392
1393 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1395 assert_eq!(inode, 2);
1396 }
1397
1398 {
1399 let mut fs = prepare_passthroughfs();
1400 fs.cfg.use_host_ino = true;
1401 let m = InodeStore::default();
1402 let id = InodeId {
1403 ino: 12345,
1404 dev: 1,
1405 mnt: 1,
1406 };
1407 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1409 assert_eq!(inode & MAX_HOST_INO, 12345)
1410 }
1411
1412 {
1413 let mut fs = prepare_passthroughfs();
1414 fs.cfg.use_host_ino = true;
1415 let mut m = InodeStore::default();
1416 let id = InodeId {
1417 ino: MAX_HOST_INO + 1,
1418 dev: 1,
1419 mnt: 1,
1420 };
1421 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1423 assert_eq!(inode & MAX_HOST_INO, 2);
1424 let file = TempFile::new().expect("Cannot create temporary file.");
1425 let mode = file.as_file().metadata().unwrap().mode();
1426 let inode_data =
1427 InodeData::new(inode, InodeHandle::File(file.into_file()), 1, id, mode);
1428 m.insert(Arc::new(inode_data));
1429 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1430 assert_eq!(inode & MAX_HOST_INO, 2);
1431 }
1432 }
1433
1434 #[test]
1435 fn test_validate_virtiofs_config() {
1436 let fs_cfg = Config {
1438 writeback: true,
1439 cache_policy: CachePolicy::Never,
1440 ..Default::default()
1441 };
1442 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1443 assert!(!fs.cfg.writeback);
1444
1445 let fs_cfg = Config {
1447 no_open: true,
1448 cache_policy: CachePolicy::Never,
1449 ..Default::default()
1450 };
1451 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1452 assert!(!fs.cfg.no_open);
1453
1454 let fs_cfg = Config {
1456 no_open: true,
1457 cache_policy: CachePolicy::Auto,
1458 ..Default::default()
1459 };
1460 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1461 assert!(!fs.cfg.no_open);
1462
1463 let fs_cfg = Config {
1465 no_open: true,
1466 cache_policy: CachePolicy::Always,
1467 ..Default::default()
1468 };
1469 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1470 assert!(fs.cfg.no_open);
1471
1472 let fs_cfg = Config {
1474 no_open: true,
1475 writeback: true,
1476 cache_policy: CachePolicy::Never,
1477 ..Default::default()
1478 };
1479 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1480 assert!(!fs.cfg.no_open);
1481 assert!(!fs.cfg.writeback);
1482 }
1483
1484 #[test]
1485 fn test_generic_read_write_noopen() {
1486 let tmpdir = TempDir::new().expect("Cannot create temporary directory.");
1487 let fs_cfg = Config {
1489 do_import: false,
1490 no_open: true,
1491 root_dir: tmpdir.as_path().to_string_lossy().to_string(),
1492 ..Default::default()
1493 };
1494 let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1495 fs.import().unwrap();
1496 fs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1497 fs.mount().unwrap();
1498
1499 let ctx = Context::default();
1501 let createin = CreateIn {
1502 flags: libc::O_CREAT as u32,
1503 mode: 0o644,
1504 umask: 0,
1505 fuse_flags: 0,
1506 };
1507 let file_name = CString::new("test_file").unwrap();
1508
1509 let (entry, _, _, _) = fs
1510 .create(&ctx, ROOT_ID, file_name.as_c_str(), createin)
1511 .unwrap();
1512 let ino = entry.inode;
1513 assert_ne!(ino, 0);
1514 assert_ne!(ino, ROOT_ID);
1515
1516 let data = b"hello world";
1518 let buffer_file = TempFile::new().expect("Cannot create temporary file.");
1520 let mut buffer_file = buffer_file.into_file();
1521 buffer_file.write_all(data).unwrap();
1522 let _ = buffer_file.flush();
1523
1524 let mut newbuf = Vec::new();
1526 buffer_file.seek(SeekFrom::Start(0)).unwrap();
1527 buffer_file.read_to_end(&mut newbuf).unwrap();
1528 assert_eq!(newbuf, data);
1529
1530 buffer_file.seek(SeekFrom::Start(0)).unwrap();
1532 let write_sz = fs
1533 .write(
1534 &ctx,
1535 ino,
1536 0,
1537 &mut buffer_file,
1538 data.len() as u32,
1539 0,
1540 None,
1541 false,
1542 0,
1543 0,
1544 )
1545 .unwrap();
1546 assert_eq!(write_sz, data.len());
1547
1548 let read_buffer_file = TempFile::new().expect("Cannot create temporary file.");
1550 let mut read_buffer_file = read_buffer_file.into_file();
1551 let read_sz = fs
1552 .read(
1553 &ctx,
1554 ino,
1555 0,
1556 &mut read_buffer_file,
1557 data.len() as u32,
1558 0,
1559 None,
1560 0,
1561 )
1562 .unwrap();
1563 assert_eq!(read_sz, data.len());
1564
1565 read_buffer_file.seek(SeekFrom::Start(0)).unwrap();
1566 let mut newbuf = Vec::new();
1567 read_buffer_file.read_to_end(&mut newbuf).unwrap();
1568 assert_eq!(newbuf, data);
1569 }
1570}