1use std::fs::{File, OpenOptions};
12use std::ops::Deref;
13use std::os::unix::fs::PermissionsExt;
14use std::os::unix::io::AsRawFd;
15use std::os::unix::net::UnixStream;
16use std::path::{Path, PathBuf};
17use std::sync::{Arc, Mutex};
18
19use mio::{Events, Poll, Token, Waker};
20use nix::errno::Errno;
21use nix::fcntl::{fcntl, FcntlArg, FdFlag, OFlag};
22use nix::mount::{mount, umount2, MntFlags, MsFlags};
23use nix::poll::{poll, PollFd, PollFlags};
24use nix::sys::epoll::{epoll_ctl, EpollEvent, EpollFlags, EpollOp};
25use nix::unistd::{getgid, getuid, read};
26
27use super::{
28 super::pagesize,
29 Error::{IoError, SessionFailure},
30 FuseBuf, FuseDevWriter, Reader, Result, FUSE_HEADER_SIZE, FUSE_KERN_BUF_PAGES,
31};
32
33const POLL_EVENTS_CAPACITY: usize = 1024;
35
36const FUSE_DEVICE: &str = "/dev/fuse";
37const FUSE_FSTYPE: &str = "fuse";
38const FUSERMOUNT_BIN: &str = "fusermount3";
39
40const EXIT_FUSE_EVENT: Token = Token(0);
41const FUSE_DEV_EVENT: Token = Token(1);
42
43pub struct FuseSession {
45 mountpoint: PathBuf,
46 fsname: String,
47 subtype: String,
48 file: Option<File>,
49 keep_alive: Option<UnixStream>,
51 bufsize: usize,
52 readonly: bool,
53 wakers: Mutex<Vec<Arc<Waker>>>,
54 auto_unmount: bool,
55 allow_other: bool,
56 target_mntns: Option<libc::pid_t>,
57 fusermount: String,
59}
60
61impl FuseSession {
62 pub fn new(
64 mountpoint: &Path,
65 fsname: &str,
66 subtype: &str,
67 readonly: bool,
68 ) -> Result<FuseSession> {
69 FuseSession::new_with_autounmount(mountpoint, fsname, subtype, readonly, false)
70 }
71
72 pub fn new_with_autounmount(
74 mountpoint: &Path,
75 fsname: &str,
76 subtype: &str,
77 readonly: bool,
78 auto_unmount: bool,
79 ) -> Result<FuseSession> {
80 let dest = mountpoint
81 .canonicalize()
82 .map_err(|_| SessionFailure(format!("invalid mountpoint {mountpoint:?}")))?;
83 if !dest.is_dir() {
84 return Err(SessionFailure(format!("{dest:?} is not a directory")));
85 }
86
87 Ok(FuseSession {
88 mountpoint: dest,
89 fsname: fsname.to_owned(),
90 subtype: subtype.to_owned(),
91 file: None,
92 keep_alive: None,
93 bufsize: FUSE_KERN_BUF_PAGES * pagesize() + FUSE_HEADER_SIZE,
94 readonly,
95 wakers: Mutex::new(Vec::new()),
96 auto_unmount,
97 target_mntns: None,
98 fusermount: FUSERMOUNT_BIN.to_string(),
99 allow_other: true,
100 })
101 }
102
103 pub fn set_target_mntns(&mut self, pid: Option<libc::pid_t>) {
106 self.target_mntns = pid;
107 }
108
109 pub fn set_fusermount(&mut self, bin: &str) {
111 self.fusermount = bin.to_string();
112 }
113
114 pub fn set_allow_other(&mut self, allow_other: bool) {
118 self.allow_other = allow_other;
119 }
120
121 pub fn get_fusermount(&self) -> &str {
123 self.fusermount.as_str()
124 }
125
126 pub fn get_fuse_file(&self) -> Option<&File> {
128 self.file.as_ref()
129 }
130
131 pub fn set_fuse_file(&mut self, file: File) {
133 self.file = Some(file);
134 }
135
136 pub fn clone_fuse_file(&self) -> Result<File> {
138 let mut old_fd = self
139 .file
140 .as_ref()
141 .ok_or(SessionFailure(
142 "fuse session file doesn't exist".to_string(),
143 ))?
144 .as_raw_fd();
145
146 let cloned_file = OpenOptions::new()
147 .create(false)
148 .read(true)
149 .write(true)
150 .open(FUSE_DEVICE)
151 .map_err(|e| SessionFailure(format!("open {FUSE_DEVICE}: {e}")))?;
152
153 nix::ioctl_read!(clone_fuse_fd, 229, 0, i32);
158
159 unsafe { clone_fuse_fd(cloned_file.as_raw_fd(), (&mut old_fd) as *mut i32) }
160 .map_err(|e| SessionFailure(format!("failed to clone fuse file: {:?}", e)))?;
161
162 Ok(cloned_file)
163 }
164
165 pub fn mountpoint(&self) -> &Path {
167 &self.mountpoint
168 }
169
170 pub fn fsname(&self) -> &str {
172 &self.fsname
173 }
174
175 pub fn subtype(&self) -> &str {
177 &self.subtype
178 }
179
180 pub fn bufsize(&self) -> usize {
182 self.bufsize
183 }
184
185 pub fn mount(&mut self) -> Result<()> {
187 let mut flags = MsFlags::MS_NOSUID | MsFlags::MS_NODEV | MsFlags::MS_NOATIME;
188 if self.readonly {
189 flags |= MsFlags::MS_RDONLY;
190 }
191 let (file, socket) = fuse_kern_mount(
192 &self.mountpoint,
193 &self.fsname,
194 &self.subtype,
195 flags,
196 self.auto_unmount,
197 self.allow_other,
198 self.target_mntns,
199 &self.fusermount,
200 )?;
201
202 fcntl(file.as_raw_fd(), FcntlArg::F_SETFL(OFlag::O_NONBLOCK))
203 .map_err(|e| SessionFailure(format!("set fd nonblocking: {e}")))?;
204 self.file = Some(file);
205 self.keep_alive = socket;
206
207 Ok(())
208 }
209
210 pub fn umount(&mut self) -> Result<()> {
212 if let (None, Some(file)) = (self.keep_alive.take(), self.file.take()) {
215 if let Some(mountpoint) = self.mountpoint.to_str() {
216 fuse_kern_umount(mountpoint, file, self.fusermount.as_str())
217 } else {
218 Err(SessionFailure("invalid mountpoint".to_string()))
219 }
220 } else {
221 Ok(())
222 }
223 }
224
225 pub fn new_channel(&self) -> Result<FuseChannel> {
227 if let Some(file) = &self.file {
228 let file = file
229 .try_clone()
230 .map_err(|e| SessionFailure(format!("dup fd: {e}")))?;
231 let channel = FuseChannel::new(file, self.bufsize)?;
232 let waker = channel.get_waker();
233 self.add_waker(waker)?;
234
235 Ok(channel)
236 } else {
237 Err(SessionFailure("invalid fuse session".to_string()))
238 }
239 }
240
241 pub fn wake(&self) -> Result<()> {
243 let wakers = self
244 .wakers
245 .lock()
246 .map_err(|e| SessionFailure(format!("lock wakers: {e}")))?;
247 for waker in wakers.iter() {
248 waker
249 .wake()
250 .map_err(|e| SessionFailure(format!("wake channel: {e}")))?;
251 }
252 Ok(())
253 }
254
255 fn add_waker(&self, waker: Arc<Waker>) -> Result<()> {
256 let mut wakers = self
257 .wakers
258 .lock()
259 .map_err(|e| SessionFailure(format!("lock wakers: {e}")))?;
260 wakers.push(waker);
261 Ok(())
262 }
263}
264
265impl Drop for FuseSession {
266 fn drop(&mut self) {
267 let _ = self.umount();
268 }
269}
270
271pub struct FuseChannel {
275 file: File,
276 poll: Poll,
277 waker: Arc<Waker>,
278 buf: Vec<u8>,
279}
280
281impl FuseChannel {
282 fn new(file: File, bufsize: usize) -> Result<Self> {
283 let poll = Poll::new().map_err(|e| SessionFailure(format!("epoll create: {e}")))?;
284 let waker = Waker::new(poll.registry(), EXIT_FUSE_EVENT)
285 .map_err(|e| SessionFailure(format!("epoll register session fd: {e}")))?;
286 let waker = Arc::new(waker);
287
288 let epoll = poll.as_raw_fd();
292 let mut event = EpollEvent::new(EpollFlags::EPOLLIN, usize::from(FUSE_DEV_EVENT) as u64);
293 epoll_ctl(
294 epoll,
295 EpollOp::EpollCtlAdd,
296 file.as_raw_fd(),
297 Some(&mut event),
298 )
299 .map_err(|e| SessionFailure(format!("epoll register channel fd: {e}")))?;
300
301 Ok(FuseChannel {
302 file,
303 poll,
304 waker,
305 buf: vec![0x0u8; bufsize],
306 })
307 }
308
309 fn get_waker(&self) -> Arc<Waker> {
310 self.waker.clone()
311 }
312
313 pub fn get_request(&mut self) -> Result<Option<(Reader, FuseDevWriter)>> {
320 let mut events = Events::with_capacity(POLL_EVENTS_CAPACITY);
321 let mut need_exit = false;
322 loop {
323 let mut fusereq_available = false;
324 match self.poll.poll(&mut events, None) {
325 Ok(_) => {}
326 Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
327 Err(e) => return Err(SessionFailure(format!("epoll wait: {e}"))),
328 }
329
330 for event in events.iter() {
331 if event.is_readable() {
332 match event.token() {
333 EXIT_FUSE_EVENT => need_exit = true,
334 FUSE_DEV_EVENT => fusereq_available = true,
335 x => {
336 error!("unexpected epoll event");
337 return Err(SessionFailure(format!("unexpected epoll event: {}", x.0)));
338 }
339 }
340 } else if event.is_error() {
341 info!("FUSE channel already closed!");
342 return Err(SessionFailure("epoll error".to_string()));
343 } else {
344 panic!("unknown epoll result events");
346 }
347 }
348
349 if need_exit {
352 info!("Will exit from fuse service");
353 return Ok(None);
354 }
355 if fusereq_available {
356 let fd = self.file.as_raw_fd();
357 match read(fd, &mut self.buf) {
358 Ok(len) => {
359 let buf = unsafe {
366 std::slice::from_raw_parts_mut(self.buf.as_mut_ptr(), self.buf.len())
367 };
368 let reader =
370 Reader::from_fuse_buffer(FuseBuf::new(&mut self.buf[..len])).unwrap();
371 let writer = FuseDevWriter::new(fd, buf).unwrap();
372 return Ok(Some((reader, writer)));
373 }
374 Err(e) => match e {
375 Errno::ENOENT => {
376 trace!("restart reading due to ENOENT");
378 continue;
379 }
380 Errno::EAGAIN => {
381 trace!("restart reading due to EAGAIN");
382 continue;
383 }
384 Errno::EINTR => {
385 trace!("syscall interrupted");
386 continue;
387 }
388 Errno::ENODEV => {
389 info!("fuse filesystem umounted");
390 return Ok(None);
391 }
392 e => {
393 warn! {"read fuse dev failed on fd {}: {}", fd, e};
394 return Err(SessionFailure(format!("read new request: {e:?}")));
395 }
396 },
397 }
398 }
399 }
400 }
401}
402
403#[allow(clippy::too_many_arguments)]
405fn fuse_kern_mount(
406 mountpoint: &Path,
407 fsname: &str,
408 subtype: &str,
409 flags: MsFlags,
410 auto_unmount: bool,
411 allow_other: bool,
412 target_mntns: Option<libc::pid_t>,
413 fusermount: &str,
414) -> Result<(File, Option<UnixStream>)> {
415 let file = OpenOptions::new()
416 .create(false)
417 .read(true)
418 .write(true)
419 .open(FUSE_DEVICE)
420 .map_err(|e| SessionFailure(format!("open {FUSE_DEVICE}: {e}")))?;
421 let meta = mountpoint
422 .metadata()
423 .map_err(|e| SessionFailure(format!("stat {mountpoint:?}: {e}")))?;
424 let mut opts = format!(
425 "default_permissions,fd={},rootmode={:o},user_id={},group_id={}",
426 file.as_raw_fd(),
427 meta.permissions().mode() & libc::S_IFMT,
428 getuid(),
429 getgid(),
430 );
431 if allow_other {
432 opts.push_str(",allow_other");
433 }
434 let mut fstype = String::from(FUSE_FSTYPE);
435 if !subtype.is_empty() {
436 fstype.push('.');
437 fstype.push_str(subtype);
438 }
439
440 if let Some(mountpoint) = mountpoint.to_str() {
441 info!(
442 "mount source {} dest {} with fstype {} opts {} fd {}",
443 fsname,
444 mountpoint,
445 fstype,
446 opts,
447 file.as_raw_fd(),
448 );
449 }
450
451 if auto_unmount || target_mntns.is_some() {
455 fuse_fusermount_mount(
456 mountpoint,
457 fsname,
458 subtype,
459 opts,
460 flags,
461 auto_unmount,
462 target_mntns,
463 fusermount,
464 )
465 } else {
466 match mount(
467 Some(fsname),
468 mountpoint,
469 Some(fstype.deref()),
470 flags,
471 Some(opts.deref()),
472 ) {
473 Ok(()) => Ok((file, None)),
474 Err(Errno::EPERM) => fuse_fusermount_mount(
475 mountpoint,
476 fsname,
477 subtype,
478 opts,
479 flags,
480 auto_unmount,
481 target_mntns,
482 fusermount,
483 ),
484 Err(e) => Err(SessionFailure(format!(
485 "failed to mount {mountpoint:?}: {e}"
486 ))),
487 }
488 }
489}
490
491fn msflags_to_string(flags: MsFlags) -> String {
492 [
493 (MsFlags::MS_RDONLY, ("rw", "ro")),
494 (MsFlags::MS_NOSUID, ("suid", "nosuid")),
495 (MsFlags::MS_NODEV, ("dev", "nodev")),
496 (MsFlags::MS_NOEXEC, ("exec", "noexec")),
497 (MsFlags::MS_SYNCHRONOUS, ("async", "sync")),
498 (MsFlags::MS_NOATIME, ("atime", "noatime")),
499 ]
500 .map(
501 |(flag, (neg, pos))| {
502 if flags.contains(flag) {
503 pos
504 } else {
505 neg
506 }
507 },
508 )
509 .join(",")
510}
511
512#[allow(clippy::too_many_arguments)]
514fn fuse_fusermount_mount(
515 mountpoint: &Path,
516 fsname: &str,
517 subtype: &str,
518 opts: String,
519 flags: MsFlags,
520 auto_unmount: bool,
521 target_mntns: Option<libc::pid_t>,
522 fusermount: &str,
523) -> Result<(File, Option<UnixStream>)> {
524 let mut opts = vec![format!("fsname={fsname}"), opts, msflags_to_string(flags)];
525 if !subtype.is_empty() {
526 opts.push(format!("subtype={subtype}"));
527 }
528 if auto_unmount {
529 opts.push("auto_unmount".to_owned());
530 }
531 let opts = opts.join(",");
532
533 let (send, recv) = UnixStream::pair().unwrap();
534
535 fcntl(send.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))
540 .map_err(|e| SessionFailure(format!("Failed to remove close-on-exec flag: {e}")))?;
541
542 let mut cmd = match target_mntns {
543 Some(pid) => {
544 let mut c = std::process::Command::new("nsenter");
545 c.arg("-t")
546 .arg(format!("{}", pid))
547 .arg("-m")
548 .arg(fusermount);
549 c
550 }
551 None => std::process::Command::new(fusermount),
552 };
553 let mut proc = cmd
555 .env("_FUSE_COMMFD", format!("{}", send.as_raw_fd()))
556 .arg("-o")
557 .arg(opts)
558 .arg("--")
559 .arg(mountpoint)
560 .spawn()
561 .map_err(IoError)?;
562
563 if auto_unmount {
564 std::thread::spawn(move || {
565 let _ = proc.wait();
566 });
567 } else {
568 match proc.wait().map_err(IoError)?.code() {
569 Some(0) => {}
570 exit_code => {
571 return Err(SessionFailure(format!(
572 "Unexpected exit code when running fusermount: {exit_code:?}"
573 )))
574 }
575 }
576 }
577 drop(send);
578
579 match vmm_sys_util::sock_ctrl_msg::ScmSocket::recv_with_fd(&recv, &mut [0u8; 8]).map_err(
580 |e| {
581 SessionFailure(format!(
582 "Unexpected error when receiving fuse file descriptor from fusermount: {}",
583 e
584 ))
585 },
586 )? {
587 (_recv_bytes, Some(file)) => Ok((file, if auto_unmount { Some(recv) } else { None })),
588 (recv_bytes, None) => Err(SessionFailure(format!(
589 "fusermount did not send a file descriptor. We received {recv_bytes} bytes."
590 ))),
591 }
592}
593
594fn fuse_kern_umount(mountpoint: &str, file: File, fusermount: &str) -> Result<()> {
596 let mut fds = [PollFd::new(file.as_raw_fd(), PollFlags::empty())];
597
598 if poll(&mut fds, 0).is_ok() {
599 if let Some(event) = fds[0].revents() {
602 if event == PollFlags::POLLERR {
603 return Ok(());
604 }
605 }
606 }
607
608 drop(file);
611 match umount2(mountpoint, MntFlags::MNT_DETACH) {
612 Ok(()) => Ok(()),
613 Err(Errno::EPERM) => fuse_fusermount_umount(mountpoint, fusermount),
614 Err(e) => Err(SessionFailure(format!(
615 "failed to umount {mountpoint}: {e}"
616 ))),
617 }
618}
619
620fn fuse_fusermount_umount(mountpoint: &str, fusermount: &str) -> Result<()> {
622 match std::process::Command::new(fusermount)
623 .arg("--unmount")
624 .arg("--quiet")
625 .arg("--lazy")
626 .arg("--")
627 .arg(mountpoint)
628 .status()
629 .map_err(IoError)?
630 .code()
631 {
632 Some(0) => Ok(()),
633 exit_code => Err(SessionFailure(format!(
634 "Unexpected exit code when unmounting via running fusermount: {exit_code:?}"
635 ))),
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use std::fs::File;
643 use std::os::unix::io::FromRawFd;
644 use std::path::Path;
645 use vmm_sys_util::tempdir::TempDir;
646
647 #[test]
648 fn test_new_session() {
649 let se = FuseSession::new(Path::new("haha"), "foo", "bar", true);
650 assert!(se.is_err());
651
652 let dir = TempDir::new().unwrap();
653 let se = FuseSession::new(dir.as_path(), "foo", "bar", false);
654 assert!(se.is_ok());
655 }
656
657 #[test]
658 fn test_new_channel() {
659 let fd = nix::unistd::dup(std::io::stdout().as_raw_fd()).unwrap();
660 let file = unsafe { File::from_raw_fd(fd) };
661 let _ = FuseChannel::new(file, 3).unwrap();
662 }
663
664 #[test]
665 fn test_fusermount() {
666 let dir = TempDir::new().unwrap();
667 let se = FuseSession::new(dir.as_path(), "foo", "bar", true);
668 assert!(se.is_ok());
669 let mut se = se.unwrap();
670 assert_eq!(se.get_fusermount(), FUSERMOUNT_BIN);
671
672 se.set_fusermount("fusermount");
673 assert_eq!(se.get_fusermount(), "fusermount");
674 }
675
676 #[test]
677 fn test_clone_fuse_file() {
678 let dir = TempDir::new().unwrap();
679 let mut se = FuseSession::new(dir.as_path(), "foo", "bar", true).unwrap();
680 se.mount().unwrap();
681
682 let cloned_file = se.clone_fuse_file().unwrap();
683 assert!(cloned_file.as_raw_fd() > 0);
684
685 se.umount().unwrap();
686 se.set_fuse_file(cloned_file);
687 se.mount().unwrap();
688 }
689}
690
691#[cfg(feature = "async_io")]
692pub use asyncio::FuseDevTask;
693
694#[cfg(feature = "async_io")]
695mod asyncio {
697 use std::os::unix::io::RawFd;
698 use std::sync::Arc;
699
700 use crate::api::filesystem::AsyncFileSystem;
701 use crate::api::server::Server;
702 use crate::transport::{FuseBuf, Reader, Writer};
703
704 pub struct FuseDevTask<F: AsyncFileSystem + Sync> {
722 fd: RawFd,
723 buf: Vec<u8>,
724 state: AsyncExecutorState,
725 server: Arc<Server<F>>,
726 }
727
728 impl<F: AsyncFileSystem + Sync> FuseDevTask<F> {
729 pub fn new(
740 buf_size: usize,
741 fd: RawFd,
742 server: Arc<Server<F>>,
743 state: AsyncExecutorState,
744 ) -> Self {
745 FuseDevTask {
746 fd,
747 server,
748 state,
749 buf: vec![0x0u8; buf_size],
750 }
751 }
752
753 pub async fn poll_handler(&mut self) {
763 let drive = AsyncDriver::default();
765
766 while !self.state.quiescing() {
767 let result = AsyncUtil::read(drive.clone(), self.fd, &mut self.buf, 0).await;
768 match result {
769 Ok(len) => {
770 let buf = unsafe {
777 std::slice::from_raw_parts_mut(self.buf.as_mut_ptr(), self.buf.len())
778 };
779 let reader =
781 Reader::<()>::new(FuseBuf::new(&mut self.buf[0..len])).unwrap();
782 let writer = Writer::new(self.fd, buf).unwrap();
783 let result = unsafe {
784 self.server
785 .async_handle_message(drive.clone(), reader, writer, None, None)
786 .await
787 };
788
789 if let Err(e) = result {
790 error!("failed to handle fuse request, {}", e);
792 }
793 }
794 Err(e) => {
795 error!("failed to read request from fuse device fd, {}", e);
797 }
798 }
799 }
800
801 self.state.report();
805 }
806 }
807
808 impl<F: AsyncFileSystem + Sync> Clone for FuseDevTask<F> {
809 fn clone(&self) -> Self {
810 FuseDevTask {
811 fd: self.fd,
812 server: self.server.clone(),
813 state: self.state.clone(),
814 buf: vec![0x0u8; self.buf.capacity()],
815 }
816 }
817 }
818
819 #[cfg(test)]
820 mod tests {
821 use std::os::unix::io::AsRawFd;
822
823 use super::*;
824 use crate::api::{Vfs, VfsOptions};
825 use crate::async_util::{AsyncDriver, AsyncExecutor};
826
827 #[test]
828 fn test_fuse_task() {
829 let state = AsyncExecutorState::new();
830 let fs = Vfs::<AsyncDriver, ()>::new(VfsOptions::default());
831 let _server = Arc::new(Server::<Vfs<AsyncDriver, ()>, AsyncDriver, ()>::new(fs));
832 let file = vmm_sys_util::tempfile::TempFile::new().unwrap();
833 let _fd = file.as_file().as_raw_fd();
834
835 let mut executor = AsyncExecutor::new(32);
836 executor.setup().unwrap();
837
838 for _i in 0..10 {
855 executor.run_once(false).unwrap();
856 }
857
858 state.quiesce();
860 drop(file);
862
863 for _i in 0..10 {
864 executor.run_once(false).unwrap();
865 }
866 }
867 }
868}