1use std::{
2 ffi::{OsStr, OsString},
3 io,
4 os::unix::{
5 ffi::OsStrExt,
6 io::{AsRawFd, RawFd},
7 net::UnixStream,
8 },
9 path::{Path, PathBuf},
10 process::Command,
11};
12
13use nix::{
14 self, cmsg_space,
15 fcntl::{fcntl, FcntlArg, FdFlag},
16 sys::socket::{recvmsg, ControlMessageOwned, MsgFlags},
17};
18
19use crate::{error::MountError, session::Start, util::DumbFd};
20
21#[derive(Default)]
22pub struct Options(OsString);
23
24impl Options {
25 pub fn fs_name<O: AsRef<OsStr>>(&mut self, fs_name: O) -> &mut Self {
26 self.push_key_value("fsname", fs_name)
27 }
28
29 pub fn read_only(&mut self) -> &mut Self {
30 self.push("ro")
31 }
32
33 pub fn push<O: AsRef<OsStr>>(&mut self, option: O) -> &mut Self {
34 self.push_parts(&[option.as_ref()])
35 }
36
37 pub fn push_key_value<K, V>(&mut self, key: K, value: V) -> &mut Self
38 where
39 K: AsRef<OsStr>,
40 V: AsRef<OsStr>,
41 {
42 let (key, value) = (key.as_ref(), value.as_ref());
43
44 let assert_valid = |part: &OsStr| {
45 let bytes = part.as_bytes();
46 assert!(
47 !bytes.is_empty() && bytes.iter().all(|b| !matches!(*b, b',' | b'=')),
48 "invalid key or value: {}",
49 part.to_string_lossy()
50 );
51 };
52
53 assert_valid(key);
54 assert_valid(value);
55
56 self.push_parts(&[key, OsStr::new("="), value])
57 }
58
59 fn push_parts(&mut self, segment: &[&OsStr]) -> &mut Self {
60 if !self.0.is_empty() {
61 self.0.push(",");
62 }
63
64 let start = self.0.as_bytes().len();
65 segment.iter().for_each(|part| self.0.push(part));
66
67 let bytes = self.0.as_bytes();
68 let last = bytes.len() - 1;
69
70 assert!(
71 last >= start && bytes[start] != b',' && bytes[last] != b',',
72 "invalid option string: {}",
73 OsStr::from_bytes(&bytes[start..]).to_string_lossy()
74 );
75
76 self
77 }
78}
79
80impl<O: AsRef<OsStr>> Extend<O> for Options {
81 fn extend<I: IntoIterator<Item = O>>(&mut self, iter: I) {
82 iter.into_iter().for_each(|option| {
83 self.push(option);
84 });
85 }
86}
87
88pub fn mount_sync<M>(mountpoint: M, options: &Options) -> Result<Start, MountError>
89where
90 M: AsRef<Path> + Into<PathBuf>,
91{
92 let (left_side, right_side) = UnixStream::pair()?;
93
94 let right_fd = right_side.as_raw_fd();
96 fcntl(
97 right_fd,
98 FcntlArg::F_SETFD(
99 FdFlag::from_bits(fcntl(right_fd, FcntlArg::F_GETFD).unwrap()).unwrap()
100 & !FdFlag::FD_CLOEXEC,
101 ),
102 )
103 .unwrap();
104
105 let mut command = Command::new(FUSERMOUNT_CMD);
106 if !options.0.is_empty() {
107 command.args(&[OsStr::new("-o"), &options.0]);
108 }
109
110 command.args(&[OsStr::new("--"), mountpoint.as_ref().as_ref()]);
111 let mut fusermount = command.env("_FUSE_COMMFD", right_fd.to_string()).spawn()?;
112
113 drop(right_side);
115
116 let session_fd = {
117 let mut buffer = cmsg_space!(RawFd);
118 let message = recvmsg(
119 left_side.as_raw_fd(),
120 &[],
121 Some(&mut buffer),
122 MsgFlags::empty(),
123 )
124 .map_err(io::Error::from)?;
125
126 let session_fd = match message.cmsgs().next() {
127 Some(ControlMessageOwned::ScmRights(fds)) => fds.into_iter().next(),
128 _ => None,
129 };
130
131 session_fd.ok_or(MountError::Fusermount)
132 };
133
134 match session_fd {
135 Ok(session_fd) => Ok(Start::new(DumbFd(session_fd), mountpoint.into())),
136
137 Err(error) => {
138 drop(left_side);
139 fusermount.wait()?;
140 Err(error)
141 }
142 }
143}
144
145pub(crate) fn unmount_sync<M: AsRef<OsStr>>(mountpoint: M) -> Result<(), MountError> {
146 let status = Command::new(FUSERMOUNT_CMD)
147 .args(&[OsStr::new("-zuq"), OsStr::new("--"), mountpoint.as_ref()])
148 .status()?;
149
150 if status.success() {
151 Ok(())
152 } else {
153 Err(MountError::Fusermount)
154 }
155}
156
157const FUSERMOUNT_CMD: &str = "fusermount3";