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 .cloned()
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 .cloned()
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 .cloned()
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::other(format!(
710 "max inode number reached: {VFS_MAX_INO}"
711 )));
712 }
713
714 InodeMap::insert_locked(
715 inodes.deref_mut(),
716 Arc::new(InodeData::new(inode, handle, 1, id, st.st.st_mode)),
717 );
718 inode
719 }
720 }
721 };
722
723 let (entry_timeout, attr_timeout) = if is_dir(st.st.st_mode) {
724 (self.dir_entry_timeout, self.dir_attr_timeout)
725 } else {
726 (self.cfg.entry_timeout, self.cfg.attr_timeout)
727 };
728
729 let mut attr_flags: u32 = 0;
731 if let Some(dax_file_size) = self.cfg.dax_file_size {
732 if self.perfile_dax.load(Ordering::Relaxed)
734 && st.st.st_size >= 0x0
735 && st.st.st_size as u64 >= dax_file_size
736 {
737 attr_flags |= fuse::FUSE_ATTR_DAX;
738 }
739 }
740
741 Ok(Entry {
742 inode,
743 generation: 0,
744 attr: st.st,
745 attr_flags,
746 attr_timeout,
747 entry_timeout,
748 })
749 }
750
751 fn forget_one(&self, inodes: &mut InodeStore, inode: Inode, count: u64) {
752 if inode == fuse::ROOT_ID {
754 return;
755 }
756
757 if let Some(data) = inodes.get(&inode) {
758 loop {
763 let curr = data.refcount.load(Ordering::Acquire);
764
765 let new = curr.saturating_sub(count);
768
769 if data
771 .refcount
772 .compare_exchange(curr, new, Ordering::AcqRel, Ordering::Acquire)
773 .is_ok()
774 {
775 if new == 0 {
776 let keep_mapping = !self.cfg.use_host_ino || data.id.ino > MAX_HOST_INO;
780 inodes.remove(&inode, keep_mapping);
781 }
782 break;
783 }
784 }
785 }
786 }
787
788 fn do_release(&self, inode: Inode, handle: Handle) -> io::Result<()> {
789 self.handle_map.release(handle, inode)
790 }
791
792 fn validate_path_component(&self, name: &CStr) -> io::Result<()> {
795 if !self.cfg.do_import {
797 return Ok(());
798 }
799 validate_path_component(name)
800 }
801
802 fn seal_size_check(
805 &self,
806 opcode: Opcode,
807 file_size: u64,
808 offset: u64,
809 size: u64,
810 mode: i32,
811 ) -> io::Result<()> {
812 if offset.checked_add(size).is_none() {
813 error!(
814 "fuse: {:?}: invalid `offset` + `size` ({}+{}) overflows u64::MAX",
815 opcode, offset, size
816 );
817 return Err(einval());
818 }
819
820 match opcode {
821 Opcode::Write => {
823 if size + offset > file_size {
824 return Err(eperm());
825 }
826 }
827
828 Opcode::Fallocate => {
829 let op = mode & !(libc::FALLOC_FL_KEEP_SIZE | libc::FALLOC_FL_UNSHARE_RANGE);
830 match op {
831 0 | libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_ZERO_RANGE => {
833 if size + offset > file_size {
834 return Err(eperm());
835 }
836 }
837 libc::FALLOC_FL_COLLAPSE_RANGE | libc::FALLOC_FL_INSERT_RANGE => {
839 return Err(eperm());
840 }
841 _ => return Err(einval()),
843 }
844 }
845
846 _ => return Err(enosys()),
848 }
849
850 Ok(())
851 }
852
853 fn get_writeback_open_flags(&self, flags: i32) -> i32 {
854 let mut new_flags = flags;
855 let writeback = self.writeback.load(Ordering::Relaxed);
856
857 if writeback && flags & libc::O_ACCMODE == libc::O_WRONLY {
861 new_flags &= !libc::O_ACCMODE;
862 new_flags |= libc::O_RDWR;
863 }
864
865 if writeback && flags & libc::O_APPEND != 0 {
872 new_flags &= !libc::O_APPEND;
873 }
874
875 new_flags
876 }
877}
878
879#[cfg(not(feature = "async-io"))]
880impl<S: BitmapSlice + Send + Sync + 'static> BackendFileSystem for PassthroughFs<S> {
881 fn mount(&self) -> io::Result<(Entry, u64)> {
882 let entry = self.do_lookup(fuse::ROOT_ID, &CString::new(".").unwrap())?;
883 Ok((entry, VFS_MAX_INO))
884 }
885
886 fn as_any(&self) -> &dyn Any {
887 self
888 }
889}
890
891macro_rules! scoped_cred {
892 ($name:ident, $ty:ty, $syscall_nr:expr) => {
893 #[derive(Debug)]
894 pub(crate) struct $name;
895
896 impl $name {
897 fn new(val: $ty) -> io::Result<Option<$name>> {
900 if val == 0 {
901 return Ok(None);
903 }
904
905 let res = unsafe { libc::syscall($syscall_nr, -1, val, -1) };
920 if res == 0 {
921 Ok(Some($name))
922 } else {
923 Err(io::Error::last_os_error())
924 }
925 }
926 }
927
928 impl Drop for $name {
929 fn drop(&mut self) {
930 let res = unsafe { libc::syscall($syscall_nr, -1, 0, -1) };
931 if res < 0 {
932 error!(
933 "fuse: failed to change credentials back to root: {}",
934 io::Error::last_os_error(),
935 );
936 }
937 }
938 }
939 };
940}
941scoped_cred!(ScopedUid, libc::uid_t, libc::SYS_setresuid);
942scoped_cred!(ScopedGid, libc::gid_t, libc::SYS_setresgid);
943
944fn set_creds(
945 uid: libc::uid_t,
946 gid: libc::gid_t,
947) -> io::Result<(Option<ScopedUid>, Option<ScopedGid>)> {
948 ScopedGid::new(gid).and_then(|gid| Ok((ScopedUid::new(uid)?, gid)))
951}
952
953struct CapFsetid {}
954
955impl Drop for CapFsetid {
956 fn drop(&mut self) {
957 if let Err(e) = caps::raise(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID) {
958 error!("fail to restore thread cap_fsetid: {}", e);
959 };
960 }
961}
962
963fn drop_cap_fsetid() -> io::Result<Option<CapFsetid>> {
964 if !caps::has_cap(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID)
965 .map_err(|_e| io::Error::new(io::ErrorKind::PermissionDenied, "no CAP_FSETID capability"))?
966 {
967 return Ok(None);
968 }
969 caps::drop(None, caps::CapSet::Effective, caps::Capability::CAP_FSETID).map_err(|_e| {
970 io::Error::new(
971 io::ErrorKind::PermissionDenied,
972 "failed to drop CAP_FSETID capability",
973 )
974 })?;
975 Ok(Some(CapFsetid {}))
976}
977
978#[cfg(test)]
979mod tests {
980 use super::*;
981 use crate::abi::fuse_abi::CreateIn;
982 use crate::api::filesystem::*;
983 use crate::api::{Vfs, VfsOptions};
984 use caps::{CapSet, Capability};
985 use log;
986 use std::io::{Read, Seek, SeekFrom, Write};
987 use std::ops::Deref;
988 use std::os::unix::prelude::MetadataExt;
989 use vmm_sys_util::{tempdir::TempDir, tempfile::TempFile};
990
991 fn prepare_passthroughfs() -> PassthroughFs {
992 let source = TempDir::new().expect("Cannot create temporary directory.");
993 let parent_path =
994 TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
995 let _child_path =
996 TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
997
998 let fs_cfg = Config {
999 writeback: true,
1000 do_import: true,
1001 no_open: true,
1002 inode_file_handles: false,
1003 root_dir: source
1004 .as_path()
1005 .to_str()
1006 .expect("source path to string")
1007 .to_string(),
1008 ..Default::default()
1009 };
1010 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1011 fs.import().unwrap();
1012
1013 fs
1014 }
1015
1016 fn passthroughfs_no_open(cfg: bool) {
1017 let opts = VfsOptions {
1018 no_open: cfg,
1019 ..Default::default()
1020 };
1021
1022 let vfs = &Vfs::new(opts);
1023 vfs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1025
1026 let fs_cfg = Config {
1027 do_import: false,
1028 no_open: cfg,
1029 ..Default::default()
1030 };
1031 let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1032 fs.import().unwrap();
1033 vfs.mount(Box::new(fs), "/submnt/A").unwrap();
1034
1035 let (p_fs, _) = vfs.get_rootfs("/submnt/A").unwrap().unwrap();
1036 let any_fs = p_fs.deref().as_any();
1037 any_fs
1038 .downcast_ref::<PassthroughFs>()
1039 .map(|fs| {
1040 assert_eq!(fs.no_open.load(Ordering::Relaxed), cfg);
1041 })
1042 .unwrap();
1043 }
1044
1045 #[test]
1046 fn test_passthroughfs_no_open() {
1047 passthroughfs_no_open(true);
1048 passthroughfs_no_open(false);
1049 }
1050
1051 #[test]
1052 fn test_passthroughfs_inode_file_handles() {
1053 log::set_max_level(log::LevelFilter::Trace);
1054
1055 match caps::has_cap(None, CapSet::Effective, Capability::CAP_DAC_READ_SEARCH) {
1056 Ok(false) | Err(_) => {
1057 println!("invoking open_by_handle_at needs CAP_DAC_READ_SEARCH");
1058 return;
1059 }
1060 Ok(true) => {}
1061 }
1062
1063 let source = TempDir::new().expect("Cannot create temporary directory.");
1064 let parent_path =
1065 TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1066 let child_path =
1067 TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1068
1069 let fs_cfg = Config {
1070 writeback: true,
1071 do_import: true,
1072 no_open: true,
1073 inode_file_handles: true,
1074 root_dir: source
1075 .as_path()
1076 .to_str()
1077 .expect("source path to string")
1078 .to_string(),
1079 ..Default::default()
1080 };
1081 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1082 fs.import().unwrap();
1083
1084 let ctx = Context::default();
1085
1086 let parent = CString::new(
1088 parent_path
1089 .as_path()
1090 .file_name()
1091 .unwrap()
1092 .to_str()
1093 .expect("path to string"),
1094 )
1095 .unwrap();
1096 let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1097 let p_inode = p_entry.inode;
1098
1099 let child = CString::new(
1100 child_path
1101 .as_path()
1102 .file_name()
1103 .unwrap()
1104 .to_str()
1105 .expect("path to string"),
1106 )
1107 .unwrap();
1108 let c_entry = fs.lookup(&ctx, p_inode, &child).unwrap();
1109
1110 let (_, duration) = fs.getattr(&ctx, c_entry.inode, None).unwrap();
1115 assert_eq!(duration, fs.cfg.attr_timeout);
1116
1117 fs.destroy();
1118 }
1119
1120 #[test]
1121 fn test_lookup_escape_root() {
1122 let fs = prepare_passthroughfs();
1123 let ctx = Context::default();
1124
1125 let name = CString::new("..").unwrap();
1126 let entry = fs.lookup(&ctx, ROOT_ID, &name).unwrap();
1127 assert_eq!(entry.inode, ROOT_ID);
1128 }
1129
1130 #[test]
1131 fn test_get_writeback_open_flags() {
1132 let mut fs = prepare_passthroughfs();
1136 fs.writeback = AtomicBool::new(true);
1137 fs.no_open = AtomicBool::new(false);
1138
1139 assert!(fs.writeback.load(Ordering::Relaxed));
1140 assert!(!fs.no_open.load(Ordering::Relaxed));
1141
1142 let flags = libc::O_RDWR;
1143 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1144
1145 let flags = libc::O_RDONLY;
1146 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1147
1148 let flags = libc::O_WRONLY;
1149 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1150
1151 let flags = libc::O_RDWR | libc::O_APPEND;
1152 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1153
1154 let flags = libc::O_RDONLY | libc::O_APPEND;
1155 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1156
1157 let flags = libc::O_WRONLY | libc::O_APPEND;
1158 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1159
1160 let mut fs = prepare_passthroughfs();
1162 fs.writeback = AtomicBool::new(false);
1163 fs.no_open = AtomicBool::new(false);
1164
1165 assert!(!fs.writeback.load(Ordering::Relaxed));
1166 assert!(!fs.no_open.load(Ordering::Relaxed));
1167
1168 let flags = libc::O_RDWR;
1169 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDWR);
1170
1171 let flags = libc::O_RDONLY;
1172 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_RDONLY);
1173
1174 let flags = libc::O_WRONLY;
1175 assert_eq!(fs.get_writeback_open_flags(flags), libc::O_WRONLY);
1176
1177 let flags = libc::O_RDWR | libc::O_APPEND;
1178 assert_eq!(
1179 fs.get_writeback_open_flags(flags),
1180 libc::O_RDWR | libc::O_APPEND
1181 );
1182
1183 let flags = libc::O_RDONLY | libc::O_APPEND;
1184 assert_eq!(
1185 fs.get_writeback_open_flags(flags),
1186 libc::O_RDONLY | libc::O_APPEND
1187 );
1188
1189 let flags = libc::O_WRONLY | libc::O_APPEND;
1190 assert_eq!(
1191 fs.get_writeback_open_flags(flags),
1192 libc::O_WRONLY | libc::O_APPEND
1193 );
1194 }
1195
1196 #[test]
1197 fn test_writeback_open_and_create() {
1198 let source = TempDir::new().expect("Cannot create temporary directory.");
1201 let _ = std::process::Command::new("sh")
1202 .arg("-c")
1203 .arg(format!("touch {}/existfile", source.as_path().to_str().unwrap()).as_str())
1204 .output()
1205 .unwrap();
1206 let fs_cfg = Config {
1207 writeback: true,
1208 do_import: true,
1209 no_open: false,
1210 inode_file_handles: false,
1211 root_dir: source
1212 .as_path()
1213 .to_str()
1214 .expect("source path to string")
1215 .to_string(),
1216 ..Default::default()
1217 };
1218 let mut fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1219 fs.writeback = AtomicBool::new(true);
1220 fs.no_open = AtomicBool::new(false);
1221 fs.import().unwrap();
1222
1223 assert!(fs.writeback.load(Ordering::Relaxed));
1224 assert!(!fs.no_open.load(Ordering::Relaxed));
1225
1226 let ctx = Context::default();
1227
1228 let fname = CString::new("testfile").unwrap();
1230 let args = CreateIn {
1231 flags: libc::O_WRONLY as u32,
1232 mode: 0644,
1233 umask: 0,
1234 fuse_flags: 0,
1235 };
1236 let (entry, handle, _, _) = fs.create(&ctx, ROOT_ID, &fname, args).unwrap();
1237 let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1238 let (_guard, mut f) = handle_data.get_file_mut();
1239 let mut buf = [0; 4];
1240 let n = f.read(&mut buf).unwrap();
1242 assert_eq!(n, 0);
1243
1244 let fname = CString::new("existfile").unwrap();
1246 let entry = fs.lookup(&ctx, ROOT_ID, &fname).unwrap();
1247 let (handle, _, _) = fs
1248 .open(&ctx, entry.inode, libc::O_WRONLY as u32, 0)
1249 .unwrap();
1250 let handle_data = fs.handle_map.get(handle.unwrap(), entry.inode).unwrap();
1251 let (_guard, mut f) = handle_data.get_file_mut();
1252 let mut buf = [0; 4];
1253 let n = f.read(&mut buf).unwrap();
1254 assert_eq!(n, 0);
1255 }
1256
1257 #[test]
1258 fn test_passthroughfs_dir_timeout() {
1259 log::set_max_level(log::LevelFilter::Trace);
1260
1261 let source = TempDir::new().expect("Cannot create temporary directory.");
1262 let parent_path =
1263 TempDir::new_in(source.as_path()).expect("Cannot create temporary directory.");
1264 let child_path =
1265 TempFile::new_in(parent_path.as_path()).expect("Cannot create temporary file.");
1266
1267 let fs_cfg = Config {
1269 writeback: false,
1270 do_import: true,
1271 no_open: false,
1272 root_dir: source
1273 .as_path()
1274 .to_str()
1275 .expect("source path to string")
1276 .to_string(),
1277 cache_policy: CachePolicy::Never,
1278 entry_timeout: Duration::from_secs(0),
1279 attr_timeout: Duration::from_secs(0),
1280 dir_entry_timeout: Some(Duration::from_secs(1)),
1281 dir_attr_timeout: Some(Duration::from_secs(2)),
1282 ..Default::default()
1283 };
1284 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1285 fs.import().unwrap();
1286
1287 let ctx = Context::default();
1288
1289 let parent = CString::new(
1291 parent_path
1292 .as_path()
1293 .file_name()
1294 .unwrap()
1295 .to_str()
1296 .expect("path to string"),
1297 )
1298 .unwrap();
1299 let p_entry = fs.lookup(&ctx, ROOT_ID, &parent).unwrap();
1300 assert_eq!(p_entry.entry_timeout, Duration::from_secs(1));
1301 assert_eq!(p_entry.attr_timeout, Duration::from_secs(2));
1302
1303 let child = CString::new(
1305 child_path
1306 .as_path()
1307 .file_name()
1308 .unwrap()
1309 .to_str()
1310 .expect("path to string"),
1311 )
1312 .unwrap();
1313 let c_entry = fs.lookup(&ctx, p_entry.inode, &child).unwrap();
1314 assert_eq!(c_entry.entry_timeout, Duration::from_secs(0));
1315 assert_eq!(c_entry.attr_timeout, Duration::from_secs(0));
1316
1317 fs.destroy();
1318 }
1319
1320 #[test]
1321 fn test_stable_inode() {
1322 use std::os::unix::fs::MetadataExt;
1323 let source = TempDir::new().expect("Cannot create temporary directory.");
1324 let child_path = TempFile::new_in(source.as_path()).expect("Cannot create temporary file.");
1325 let child = CString::new(
1326 child_path
1327 .as_path()
1328 .file_name()
1329 .unwrap()
1330 .to_str()
1331 .expect("path to string"),
1332 )
1333 .unwrap();
1334 let meta = child_path.as_file().metadata().unwrap();
1335 let ctx = Context::default();
1336 {
1337 let fs_cfg = Config {
1338 writeback: true,
1339 do_import: true,
1340 no_open: true,
1341 inode_file_handles: false,
1342 root_dir: source
1343 .as_path()
1344 .to_str()
1345 .expect("source path to string")
1346 .to_string(),
1347 ..Default::default()
1348 };
1349 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1350 fs.import().unwrap();
1351 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1352 assert_eq!(entry.inode, ROOT_ID + 1);
1353 fs.forget(&ctx, entry.inode, 1);
1354 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1355 assert_eq!(entry.inode, ROOT_ID + 1);
1356 }
1357 {
1358 let fs_cfg = Config {
1359 writeback: true,
1360 do_import: true,
1361 no_open: true,
1362 inode_file_handles: false,
1363 root_dir: source
1364 .as_path()
1365 .to_str()
1366 .expect("source path to string")
1367 .to_string(),
1368 use_host_ino: true,
1369 ..Default::default()
1370 };
1371 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1372 fs.import().unwrap();
1373 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1374 assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1375 let inode_store = fs.inode_map.get_map_mut();
1376 let inode_data = inode_store.get(&entry.inode).unwrap();
1377 assert!(inode_store.inode_by_id(&inode_data.id).is_some());
1378 let id = inode_data.id.clone();
1379 drop(inode_store);
1380
1381 fs.forget(&ctx, entry.inode, 1);
1382 let inode_store = fs.inode_map.get_map_mut();
1383 assert!(inode_store.get(&entry.inode).is_none());
1384 assert!(inode_store.inode_by_id(&id).is_none());
1385 drop(inode_store);
1386
1387 let entry = fs.lookup(&ctx, ROOT_ID, &child).unwrap();
1388 assert_eq!(entry.inode & MAX_HOST_INO, meta.ino());
1389 }
1390 }
1391
1392 #[test]
1393 fn test_allocation_inode_locked() {
1394 {
1395 let fs = prepare_passthroughfs();
1396 let m = InodeStore::default();
1397 let id = InodeId {
1398 ino: MAX_HOST_INO + 1,
1399 dev: 1,
1400 mnt: 1,
1401 };
1402
1403 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1405 assert_eq!(inode, 2);
1406 }
1407
1408 {
1409 let mut fs = prepare_passthroughfs();
1410 fs.cfg.use_host_ino = true;
1411 let m = InodeStore::default();
1412 let id = InodeId {
1413 ino: 12345,
1414 dev: 1,
1415 mnt: 1,
1416 };
1417 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1419 assert_eq!(inode & MAX_HOST_INO, 12345)
1420 }
1421
1422 {
1423 let mut fs = prepare_passthroughfs();
1424 fs.cfg.use_host_ino = true;
1425 let mut m = InodeStore::default();
1426 let id = InodeId {
1427 ino: MAX_HOST_INO + 1,
1428 dev: 1,
1429 mnt: 1,
1430 };
1431 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1433 assert_eq!(inode & MAX_HOST_INO, 2);
1434 let file = TempFile::new().expect("Cannot create temporary file.");
1435 let mode = file.as_file().metadata().unwrap().mode();
1436 let inode_data =
1437 InodeData::new(inode, InodeHandle::File(file.into_file()), 1, id, mode);
1438 m.insert(Arc::new(inode_data));
1439 let inode = fs.allocate_inode(&m, &id, None).unwrap();
1440 assert_eq!(inode & MAX_HOST_INO, 2);
1441 }
1442 }
1443
1444 #[test]
1445 fn test_validate_virtiofs_config() {
1446 let fs_cfg = Config {
1448 writeback: true,
1449 cache_policy: CachePolicy::Never,
1450 ..Default::default()
1451 };
1452 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1453 assert!(!fs.cfg.writeback);
1454
1455 let fs_cfg = Config {
1457 no_open: true,
1458 cache_policy: CachePolicy::Never,
1459 ..Default::default()
1460 };
1461 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1462 assert!(!fs.cfg.no_open);
1463
1464 let fs_cfg = Config {
1466 no_open: true,
1467 cache_policy: CachePolicy::Auto,
1468 ..Default::default()
1469 };
1470 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1471 assert!(!fs.cfg.no_open);
1472
1473 let fs_cfg = Config {
1475 no_open: true,
1476 cache_policy: CachePolicy::Always,
1477 ..Default::default()
1478 };
1479 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1480 assert!(fs.cfg.no_open);
1481
1482 let fs_cfg = Config {
1484 no_open: true,
1485 writeback: true,
1486 cache_policy: CachePolicy::Never,
1487 ..Default::default()
1488 };
1489 let fs = PassthroughFs::<()>::new(fs_cfg).unwrap();
1490 assert!(!fs.cfg.no_open);
1491 assert!(!fs.cfg.writeback);
1492 }
1493
1494 #[test]
1495 fn test_generic_read_write_noopen() {
1496 let tmpdir = TempDir::new().expect("Cannot create temporary directory.");
1497 let fs_cfg = Config {
1499 do_import: false,
1500 no_open: true,
1501 root_dir: tmpdir.as_path().to_string_lossy().to_string(),
1502 ..Default::default()
1503 };
1504 let fs = PassthroughFs::<()>::new(fs_cfg.clone()).unwrap();
1505 fs.import().unwrap();
1506 fs.init(FsOptions::ZERO_MESSAGE_OPEN).unwrap();
1507 fs.mount().unwrap();
1508
1509 let ctx = Context::default();
1511 let createin = CreateIn {
1512 flags: libc::O_CREAT as u32,
1513 mode: 0o644,
1514 umask: 0,
1515 fuse_flags: 0,
1516 };
1517 let file_name = CString::new("test_file").unwrap();
1518
1519 let (entry, _, _, _) = fs
1520 .create(&ctx, ROOT_ID, file_name.as_c_str(), createin)
1521 .unwrap();
1522 let ino = entry.inode;
1523 assert_ne!(ino, 0);
1524 assert_ne!(ino, ROOT_ID);
1525
1526 let data = b"hello world";
1528 let buffer_file = TempFile::new().expect("Cannot create temporary file.");
1530 let mut buffer_file = buffer_file.into_file();
1531 buffer_file.write_all(data).unwrap();
1532 let _ = buffer_file.flush();
1533
1534 let mut newbuf = Vec::new();
1536 buffer_file.seek(SeekFrom::Start(0)).unwrap();
1537 buffer_file.read_to_end(&mut newbuf).unwrap();
1538 assert_eq!(newbuf, data);
1539
1540 buffer_file.seek(SeekFrom::Start(0)).unwrap();
1542 let write_sz = fs
1543 .write(
1544 &ctx,
1545 ino,
1546 0,
1547 &mut buffer_file,
1548 data.len() as u32,
1549 0,
1550 None,
1551 false,
1552 0,
1553 0,
1554 )
1555 .unwrap();
1556 assert_eq!(write_sz, data.len());
1557
1558 let read_buffer_file = TempFile::new().expect("Cannot create temporary file.");
1560 let mut read_buffer_file = read_buffer_file.into_file();
1561 let read_sz = fs
1562 .read(
1563 &ctx,
1564 ino,
1565 0,
1566 &mut read_buffer_file,
1567 data.len() as u32,
1568 0,
1569 None,
1570 0,
1571 )
1572 .unwrap();
1573 assert_eq!(read_sz, data.len());
1574
1575 read_buffer_file.seek(SeekFrom::Start(0)).unwrap();
1576 let mut newbuf = Vec::new();
1577 read_buffer_file.read_to_end(&mut newbuf).unwrap();
1578 assert_eq!(newbuf, data);
1579 }
1580}