1use super::to_cstring;
5use crate::{
6 io, libc, CString, FilesystemType, Mount, MountFlags, OsStrExt, Path, SupportedFilesystems,
7 Unmount, UnmountDrop, UnmountFlags,
8};
9use libc::mount;
10use std::ptr;
11
12#[derive(Clone, Copy, smart_default::SmartDefault)]
26#[allow(clippy::module_name_repetitions)]
27pub struct MountBuilder<'a> {
28 #[default(MountFlags::empty())]
29 flags: MountFlags,
30 fstype: Option<FilesystemType<'a>>,
31 #[cfg(feature = "loop")]
32 loopback_offset: u64,
33 #[cfg(feature = "loop")]
34 explicit_loopback: bool,
35 data: Option<&'a str>,
36}
37
38impl<'a> MountBuilder<'a> {
39 #[must_use]
41 pub fn data(mut self, data: &'a str) -> Self {
42 self.data = Some(data);
43 self
44 }
45
46 #[must_use]
48 pub fn fstype(mut self, fs: impl Into<FilesystemType<'a>>) -> Self {
49 self.fstype = Some(fs.into());
50 self
51 }
52
53 #[must_use]
55 pub fn flags(mut self, flags: MountFlags) -> Self {
56 self.flags = flags;
57 self
58 }
59
60 #[cfg(feature = "loop")]
62 #[must_use]
63 pub fn loopback_offset(mut self, offset: u64) -> Self {
64 self.loopback_offset = offset;
65 self
66 }
67
68 #[cfg(feature = "loop")]
70 #[must_use]
71 pub fn explicit_loopback(mut self) -> Self {
72 self.explicit_loopback = true;
73 self
74 }
75
76 pub fn mount(self, source: impl AsRef<Path>, target: impl AsRef<Path>) -> io::Result<Mount> {
119 let MountBuilder {
120 data,
121 fstype,
122 flags,
123 #[cfg(feature = "loop")]
124 loopback_offset,
125 #[cfg(feature = "loop")]
126 explicit_loopback,
127 } = self;
128
129 let supported;
130
131 let fstype = if let Some(fstype) = fstype {
132 fstype
133 } else {
134 supported = SupportedFilesystems::new()?;
135 FilesystemType::Auto(&supported)
136 };
137
138 let source = source.as_ref();
139 let mut c_source = None;
140
141 #[cfg(feature = "loop")]
142 let (mut flags, mut fstype, mut loopback, mut loop_path) = (flags, fstype, None, None);
143
144 if !source.as_os_str().is_empty() {
145 #[cfg(feature = "loop")]
146 let mut create_loopback = |flags: &MountFlags| -> io::Result<loopdev::LoopDevice> {
147 let new_loopback = loopdev::LoopControl::open()?.next_free()?;
148 new_loopback
149 .with()
150 .read_only(flags.contains(MountFlags::RDONLY))
151 .offset(loopback_offset)
152 .attach(source)?;
153 let path = new_loopback.path().expect("loopback does not have path");
154 c_source = Some(to_cstring(path.as_os_str().as_bytes())?);
155 loop_path = Some(path);
156 Ok(new_loopback)
157 };
158
159 #[cfg(feature = "loop")]
161 if let Some(ext) = source.extension() {
162 let extf = i32::from(ext == "iso") | if ext == "squashfs" { 2 } else { 0 };
163
164 if extf != 0 {
165 fstype = if extf == 1 {
166 flags |= MountFlags::RDONLY;
167 FilesystemType::Manual("iso9660")
168 } else {
169 flags |= MountFlags::RDONLY;
170 FilesystemType::Manual("squashfs")
171 };
172 loopback = Some(create_loopback(&flags)?);
173 }
174 }
175
176 #[cfg(feature = "loop")]
177 if loopback.is_none() && explicit_loopback {
178 loopback = Some(create_loopback(&flags)?);
179 }
180
181 if c_source.is_none() {
182 c_source = Some(to_cstring(source.as_os_str().as_bytes())?);
183 }
184 };
185
186 let c_target = to_cstring(target.as_ref().as_os_str().as_bytes())?;
187 let data = match data.map(|o| to_cstring(o.as_bytes())) {
188 Some(Ok(string)) => Some(string),
189 Some(Err(why)) => return Err(why),
190 None => None,
191 };
192
193 let mut mount_data = MountData {
194 c_source,
195 c_target,
196 flags,
197 data,
198 };
199
200 let mut res = match fstype {
201 FilesystemType::Auto(supported) => mount_data.automount(supported.dev_file_systems()),
202 FilesystemType::Set(set) => mount_data.automount(set.iter().copied()),
203 FilesystemType::Manual(fstype) => mount_data.mount(fstype),
204 };
205
206 match res {
207 Ok(ref mut _mount) => {
208 #[cfg(feature = "loop")]
209 {
210 _mount.loopback = loopback;
211 _mount.loop_path = loop_path;
212 }
213 }
214 Err(_) =>
215 {
216 #[cfg(feature = "loop")]
217 if let Some(loopback) = loopback {
218 let _res = loopback.detach();
219 }
220 }
221 }
222
223 res
224 }
225
226 pub fn mount_autodrop(
232 self,
233 source: impl AsRef<Path>,
234 target: impl AsRef<Path>,
235 unmount_flags: UnmountFlags,
236 ) -> io::Result<UnmountDrop<Mount>> {
237 self.mount(source, target)
238 .map(|m| m.into_unmount_drop(unmount_flags))
239 }
240}
241
242struct MountData {
243 c_source: Option<CString>,
244 c_target: CString,
245 flags: MountFlags,
246 data: Option<CString>,
247}
248
249impl MountData {
250 fn mount(&mut self, fstype: &str) -> io::Result<Mount> {
251 let c_fstype = to_cstring(fstype.as_bytes())?;
252 match mount_(
253 self.c_source.as_ref(),
254 &self.c_target,
255 &c_fstype,
256 self.flags,
257 self.data.as_ref(),
258 ) {
259 Ok(()) => Ok(Mount::from_target_and_fstype(
260 self.c_target.clone(),
261 fstype.to_owned(),
262 )),
263 Err(why) => Err(why),
264 }
265 }
266
267 fn automount<'a, I: Iterator<Item = &'a str> + 'a>(mut self, iter: I) -> io::Result<Mount> {
268 let mut res = Ok(());
269
270 for fstype in iter {
271 match self.mount(fstype) {
272 mount @ Ok(_) => return mount,
273 Err(why) => res = Err(why),
274 }
275 }
276
277 match res {
278 Ok(()) => Err(io::Error::new(
279 io::ErrorKind::NotFound,
280 "no supported file systems found",
281 )),
282 Err(why) => Err(why),
283 }
284 }
285}
286
287fn mount_(
288 c_source: Option<&CString>,
289 c_target: &CString,
290 c_fstype: &CString,
291 flags: MountFlags,
292 c_data: Option<&CString>,
293) -> io::Result<()> {
294 let result = unsafe {
295 mount(
296 c_source.map_or_else(ptr::null, |s| s.as_ptr()),
297 c_target.as_ptr(),
298 c_fstype.as_ptr(),
299 flags.bits(),
300 c_data
301 .map_or_else(ptr::null, |s| s.as_ptr())
302 .cast::<libc::c_void>(),
303 )
304 };
305
306 match result {
307 0 => Ok(()),
308 _err => Err(io::Error::last_os_error()),
309 }
310}