1#![warn(missing_debug_implementations)]
7#![allow(missing_docs)]
8
9use super::is_mounted;
10use super::mount_options::{MountOption, option_to_string};
11use libc::c_int;
12use log::{debug, error};
13use std::ffi::{CStr, CString, OsStr};
14use std::fs::{File, OpenOptions};
15use std::io;
16use std::io::{Error, ErrorKind, Read};
17use std::os::unix::ffi::OsStrExt;
18use std::os::unix::fs::PermissionsExt;
19use std::os::unix::io::{AsRawFd, FromRawFd};
20use std::os::unix::net::UnixStream;
21use std::path::Path;
22use std::process::{Command, Stdio};
23use std::sync::Arc;
24use std::{mem, ptr};
25
26const FUSERMOUNT_BIN: &str = "fusermount";
27const FUSERMOUNT3_BIN: &str = "fusermount3";
28const FUSERMOUNT_COMM_ENV: &str = "_FUSE_COMMFD";
29
30#[derive(Debug)]
31pub struct Mount {
32 mountpoint: CString,
33 auto_unmount_socket: Option<UnixStream>,
34 fuse_device: Arc<File>,
35}
36impl Mount {
37 pub fn new(mountpoint: &Path, options: &[MountOption]) -> io::Result<(Arc<File>, Mount)> {
38 let mountpoint = mountpoint.canonicalize()?;
39 let (file, sock) = fuse_mount_pure(mountpoint.as_os_str(), options)?;
40 let file = Arc::new(file);
41 Ok((
42 file.clone(),
43 Mount {
44 mountpoint: CString::new(mountpoint.as_os_str().as_bytes())?,
45 auto_unmount_socket: sock,
46 fuse_device: file,
47 },
48 ))
49 }
50}
51
52impl Drop for Mount {
53 fn drop(&mut self) {
54 use std::io::ErrorKind::PermissionDenied;
55 if !is_mounted(&self.fuse_device) {
56 return;
60 }
61 if let Some(sock) = mem::take(&mut self.auto_unmount_socket) {
62 drop(sock);
63 return;
65 }
66 if let Err(err) = super::libc_umount(&self.mountpoint) {
67 if err.kind() == PermissionDenied {
68 fuse_unmount_pure(&self.mountpoint)
71 } else {
72 error!("Unmount failed: {}", err)
73 }
74 }
75 }
76}
77
78fn fuse_mount_pure(
79 mountpoint: &OsStr,
80 options: &[MountOption],
81) -> Result<(File, Option<UnixStream>), io::Error> {
82 if options.contains(&MountOption::AutoUnmount) {
83 return fuse_mount_fusermount(mountpoint, options);
85 }
86
87 let res = fuse_mount_sys(mountpoint, options)?;
88 match res {
89 Some(file) => Ok((file, None)),
90 _ => {
91 fuse_mount_fusermount(mountpoint, options)
93 }
94 }
95}
96
97fn fuse_unmount_pure(mountpoint: &CStr) {
98 #[cfg(target_os = "linux")]
99 unsafe {
100 let result = libc::umount2(mountpoint.as_ptr(), libc::MNT_DETACH);
101 if result == 0 {
102 return;
103 }
104 }
105 #[cfg(target_os = "macos")]
106 unsafe {
107 let result = libc::unmount(mountpoint.as_ptr(), libc::MNT_FORCE);
108 if result == 0 {
109 return;
110 }
111 }
112
113 let mut builder = Command::new(detect_fusermount_bin());
114 builder.stdout(Stdio::piped()).stderr(Stdio::piped());
115 builder
116 .arg("-u")
117 .arg("-q")
118 .arg("-z")
119 .arg("--")
120 .arg(OsStr::new(&mountpoint.to_string_lossy().into_owned()));
121
122 if let Ok(output) = builder.output() {
123 debug!("fusermount: {}", String::from_utf8_lossy(&output.stdout));
124 debug!("fusermount: {}", String::from_utf8_lossy(&output.stderr));
125 }
126}
127
128fn detect_fusermount_bin() -> String {
129 for name in [
130 FUSERMOUNT3_BIN.to_string(),
131 FUSERMOUNT_BIN.to_string(),
132 format!("/bin/{FUSERMOUNT3_BIN}"),
133 format!("/bin/{FUSERMOUNT_BIN}"),
134 ]
135 .iter()
136 {
137 if Command::new(name).arg("-h").output().is_ok() {
138 return name.to_string();
139 }
140 }
141 FUSERMOUNT3_BIN.to_string()
143}
144
145fn receive_fusermount_message(socket: &UnixStream) -> Result<File, Error> {
146 let mut io_vec_buf = [0u8];
147 let mut io_vec = libc::iovec {
148 iov_base: io_vec_buf.as_mut_ptr() as *mut libc::c_void,
149 iov_len: io_vec_buf.len(),
150 };
151 let cmsg_buffer_len = unsafe { libc::CMSG_SPACE(mem::size_of::<c_int>() as libc::c_uint) };
152 let mut cmsg_buffer = vec![0u8; cmsg_buffer_len as usize];
153 let mut message: libc::msghdr;
154 #[cfg(all(target_os = "linux", not(target_env = "musl")))]
155 {
156 message = libc::msghdr {
157 msg_name: ptr::null_mut(),
158 msg_namelen: 0,
159 msg_iov: &mut io_vec,
160 msg_iovlen: 1,
161 msg_control: cmsg_buffer.as_mut_ptr() as *mut libc::c_void,
162 msg_controllen: cmsg_buffer.len(),
163 msg_flags: 0,
164 };
165 }
166 #[cfg(all(target_os = "linux", target_env = "musl"))]
167 {
168 message = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
169 message.msg_name = ptr::null_mut();
170 message.msg_namelen = 0;
171 message.msg_iov = &mut io_vec;
172 message.msg_iovlen = 1;
173 message.msg_control = (&mut cmsg_buffer).as_mut_ptr() as *mut libc::c_void;
174 message.msg_controllen = cmsg_buffer.len() as u32;
175 message.msg_flags = 0;
176 }
177 #[cfg(any(
178 target_os = "macos",
179 target_os = "freebsd",
180 target_os = "dragonfly",
181 target_os = "openbsd",
182 target_os = "netbsd"
183 ))]
184 {
185 message = libc::msghdr {
186 msg_name: ptr::null_mut(),
187 msg_namelen: 0,
188 msg_iov: &mut io_vec,
189 msg_iovlen: 1,
190 msg_control: (&mut cmsg_buffer).as_mut_ptr() as *mut libc::c_void,
191 msg_controllen: cmsg_buffer.len() as u32,
192 msg_flags: 0,
193 };
194 }
195
196 let mut result;
197 loop {
198 unsafe {
199 result = libc::recvmsg(socket.as_raw_fd(), &mut message, 0);
200 }
201 if result != -1 {
202 break;
203 }
204 let err = Error::last_os_error();
205 if err.kind() != ErrorKind::Interrupted {
206 return Err(err);
207 }
208 }
209 if result == 0 {
210 return Err(Error::new(
211 ErrorKind::UnexpectedEof,
212 "Unexpected EOF reading from fusermount",
213 ));
214 }
215
216 unsafe {
217 let control_msg = libc::CMSG_FIRSTHDR(&message);
218 if (*control_msg).cmsg_type != libc::SCM_RIGHTS {
219 return Err(Error::new(
220 ErrorKind::InvalidData,
221 format!(
222 "Unknown control message from fusermount: {}",
223 (*control_msg).cmsg_type
224 ),
225 ));
226 }
227 let fd_data = libc::CMSG_DATA(control_msg);
228
229 let fd = *(fd_data as *const c_int);
230 if fd < 0 {
231 Err(ErrorKind::InvalidData.into())
232 } else {
233 Ok(File::from_raw_fd(fd))
234 }
235 }
236}
237
238fn fuse_mount_fusermount(
239 mountpoint: &OsStr,
240 options: &[MountOption],
241) -> Result<(File, Option<UnixStream>), Error> {
242 let (child_socket, receive_socket) = UnixStream::pair()?;
243
244 unsafe {
245 libc::fcntl(child_socket.as_raw_fd(), libc::F_SETFD, 0);
246 }
247
248 let mut builder = Command::new(detect_fusermount_bin());
249 builder.stdout(Stdio::piped()).stderr(Stdio::piped());
250 if !options.is_empty() {
251 builder.arg("-o");
252 let options_strs: Vec<String> = options.iter().map(option_to_string).collect();
253 builder.arg(options_strs.join(","));
254 }
255 builder
256 .arg("--")
257 .arg(mountpoint)
258 .env(FUSERMOUNT_COMM_ENV, child_socket.as_raw_fd().to_string());
259
260 let fusermount_child = builder.spawn()?;
261
262 drop(child_socket); let file = match receive_fusermount_message(&receive_socket) {
265 Ok(f) => f,
266 Err(_) => {
267 drop(receive_socket);
269 let output = fusermount_child.wait_with_output().unwrap();
270 let stderr_string = String::from_utf8_lossy(&output.stderr).to_string();
271 return if stderr_string.contains("only allowed if 'user_allow_other' is set") {
272 Err(io::Error::new(ErrorKind::PermissionDenied, stderr_string))
273 } else {
274 Err(io::Error::new(ErrorKind::Other, stderr_string))
275 };
276 }
277 };
278 let mut receive_socket = Some(receive_socket);
279
280 if !options.contains(&MountOption::AutoUnmount) {
281 drop(mem::take(&mut receive_socket));
284 let output = fusermount_child.wait_with_output()?;
285 debug!("fusermount: {}", String::from_utf8_lossy(&output.stdout));
286 debug!("fusermount: {}", String::from_utf8_lossy(&output.stderr));
287 } else {
288 if let Some(mut stdout) = fusermount_child.stdout {
289 let stdout_fd = stdout.as_raw_fd();
290 unsafe {
291 let mut flags = libc::fcntl(stdout_fd, libc::F_GETFL, 0);
292 flags |= libc::O_NONBLOCK;
293 libc::fcntl(stdout_fd, libc::F_SETFL, flags);
294 }
295 let mut buf = vec![0; 64 * 1024];
296 if let Ok(len) = stdout.read(&mut buf) {
297 debug!("fusermount: {}", String::from_utf8_lossy(&buf[..len]));
298 }
299 }
300 if let Some(mut stderr) = fusermount_child.stderr {
301 let stderr_fd = stderr.as_raw_fd();
302 unsafe {
303 let mut flags = libc::fcntl(stderr_fd, libc::F_GETFL, 0);
304 flags |= libc::O_NONBLOCK;
305 libc::fcntl(stderr_fd, libc::F_SETFL, flags);
306 }
307 let mut buf = vec![0; 64 * 1024];
308 if let Ok(len) = stderr.read(&mut buf) {
309 debug!("fusermount: {}", String::from_utf8_lossy(&buf[..len]));
310 }
311 }
312 }
313
314 unsafe {
315 libc::fcntl(file.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC);
316 }
317
318 Ok((file, receive_socket))
319}
320
321fn fuse_mount_sys(mountpoint: &OsStr, options: &[MountOption]) -> Result<Option<File>, Error> {
323 let fuse_device_name = "/dev/fuse";
324
325 let mountpoint_mode = File::open(mountpoint)?.metadata()?.permissions().mode();
326
327 assert!(!options.contains(&MountOption::AutoUnmount));
329
330 let file = match OpenOptions::new()
331 .read(true)
332 .write(true)
333 .open(fuse_device_name)
334 {
335 Ok(file) => file,
336 Err(error) => {
337 if error.kind() == ErrorKind::NotFound {
338 error!("{} not found. Try 'modprobe fuse'", fuse_device_name);
339 }
340 return Err(error);
341 }
342 };
343 assert!(
344 file.as_raw_fd() > 2,
345 "Conflict with stdin/stdout/stderr. fd={}",
346 file.as_raw_fd()
347 );
348
349 let mut mount_options = format!(
350 "fd={},rootmode={:o},user_id={},group_id={}",
351 file.as_raw_fd(),
352 mountpoint_mode,
353 nix::unistd::getuid(),
354 nix::unistd::getgid()
355 );
356
357 for option in options
358 .iter()
359 .filter(|x| option_group(x) == MountOptionGroup::KernelOption)
360 {
361 mount_options.push(',');
362 mount_options.push_str(&option_to_string(option));
363 }
364
365 let mut flags = 0;
366 if !options.contains(&MountOption::Dev) {
367 #[cfg(target_os = "linux")]
369 {
370 flags |= libc::MS_NODEV;
371 }
372 #[cfg(target_os = "macos")]
373 {
374 flags |= libc::MNT_NODEV;
375 }
376 }
377 if !options.contains(&MountOption::Suid) {
378 #[cfg(target_os = "linux")]
380 {
381 flags |= libc::MS_NOSUID;
382 }
383 #[cfg(target_os = "macos")]
384 {
385 flags |= libc::MNT_NOSUID;
386 }
387 }
388 for flag in options
389 .iter()
390 .filter(|x| option_group(x) == MountOptionGroup::KernelFlag)
391 {
392 flags |= option_to_flag(flag);
393 }
394
395 let mut source = fuse_device_name;
397 if let Some(MountOption::Subtype(subtype)) = options
398 .iter()
399 .find(|x| matches!(**x, MountOption::Subtype(_)))
400 {
401 source = subtype;
402 }
403 if let Some(MountOption::FSName(name)) = options
404 .iter()
405 .find(|x| matches!(**x, MountOption::FSName(_)))
406 {
407 source = name;
408 }
409
410 let c_source = CString::new(source).unwrap();
411 let c_mountpoint = CString::new(mountpoint.as_bytes()).unwrap();
412
413 let result = unsafe {
414 #[cfg(target_os = "linux")]
415 {
416 let c_options = CString::new(mount_options).unwrap();
417 let c_type = CString::new("fuse").unwrap();
418 libc::mount(
419 c_source.as_ptr(),
420 c_mountpoint.as_ptr(),
421 c_type.as_ptr(),
422 flags,
423 c_options.as_ptr() as *const libc::c_void,
424 )
425 }
426 #[cfg(target_os = "macos")]
427 {
428 let mut c_options = CString::new(mount_options).unwrap();
429 libc::mount(
430 c_source.as_ptr(),
431 c_mountpoint.as_ptr(),
432 flags,
433 c_options.as_ptr() as *mut libc::c_void,
434 )
435 }
436 };
437 if result == -1 {
438 let err = Error::last_os_error();
439 if err.kind() == ErrorKind::PermissionDenied {
440 return Ok(None); } else {
442 return Err(Error::new(
443 err.kind(),
444 format!("Error calling mount() at {mountpoint:?}: {err}"),
445 ));
446 }
447 }
448
449 Ok(Some(file))
450}
451
452#[derive(PartialEq)]
453pub enum MountOptionGroup {
454 KernelOption,
455 KernelFlag,
456 Fusermount,
457}
458
459pub fn option_group(option: &MountOption) -> MountOptionGroup {
460 match option {
461 MountOption::FSName(_) => MountOptionGroup::Fusermount,
462 MountOption::Subtype(_) => MountOptionGroup::Fusermount,
463 MountOption::CUSTOM(_) => MountOptionGroup::KernelOption,
464 MountOption::AutoUnmount => MountOptionGroup::Fusermount,
465 MountOption::AllowOther => MountOptionGroup::KernelOption,
466 MountOption::Dev => MountOptionGroup::KernelFlag,
467 MountOption::NoDev => MountOptionGroup::KernelFlag,
468 MountOption::Suid => MountOptionGroup::KernelFlag,
469 MountOption::NoSuid => MountOptionGroup::KernelFlag,
470 MountOption::RO => MountOptionGroup::KernelFlag,
471 MountOption::RW => MountOptionGroup::KernelFlag,
472 MountOption::Exec => MountOptionGroup::KernelFlag,
473 MountOption::NoExec => MountOptionGroup::KernelFlag,
474 MountOption::Atime => MountOptionGroup::KernelFlag,
475 MountOption::NoAtime => MountOptionGroup::KernelFlag,
476 MountOption::DirSync => MountOptionGroup::KernelFlag,
477 MountOption::Sync => MountOptionGroup::KernelFlag,
478 MountOption::Async => MountOptionGroup::KernelFlag,
479 MountOption::AllowRoot => MountOptionGroup::KernelOption,
480 MountOption::DefaultPermissions => MountOptionGroup::KernelOption,
481 }
482}
483
484#[cfg(target_os = "linux")]
485pub fn option_to_flag(option: &MountOption) -> libc::c_ulong {
486 match option {
487 MountOption::Dev => 0, MountOption::NoDev => libc::MS_NODEV,
489 MountOption::Suid => 0,
490 MountOption::NoSuid => libc::MS_NOSUID,
491 MountOption::RW => 0,
492 MountOption::RO => libc::MS_RDONLY,
493 MountOption::Exec => 0,
494 MountOption::NoExec => libc::MS_NOEXEC,
495 MountOption::Atime => 0,
496 MountOption::NoAtime => libc::MS_NOATIME,
497 MountOption::Async => 0,
498 MountOption::Sync => libc::MS_SYNCHRONOUS,
499 MountOption::DirSync => libc::MS_DIRSYNC,
500 _ => unreachable!(),
501 }
502}
503
504#[cfg(target_os = "macos")]
505pub fn option_to_flag(option: &MountOption) -> libc::c_int {
506 match option {
507 MountOption::Dev => 0, MountOption::NoDev => libc::MNT_NODEV,
509 MountOption::Suid => 0,
510 MountOption::NoSuid => libc::MNT_NOSUID,
511 MountOption::RW => 0,
512 MountOption::RO => libc::MNT_RDONLY,
513 MountOption::Exec => 0,
514 MountOption::NoExec => libc::MNT_NOEXEC,
515 MountOption::Atime => 0,
516 MountOption::NoAtime => libc::MNT_NOATIME,
517 MountOption::Async => 0,
518 MountOption::Sync => libc::MNT_SYNCHRONOUS,
519 _ => unreachable!(),
520 }
521}