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 }
173
174 loopback = Some(create_loopback(&flags)?);
175 }
176
177 #[cfg(feature = "loop")]
178 if loopback.is_none() && explicit_loopback {
179 loopback = Some(create_loopback(&flags)?);
180 }
181
182 if c_source.is_none() {
183 c_source = Some(to_cstring(source.as_os_str().as_bytes())?);
184 }
185 };
186
187 let c_target = to_cstring(target.as_ref().as_os_str().as_bytes())?;
188 let data = match data.map(|o| to_cstring(o.as_bytes())) {
189 Some(Ok(string)) => Some(string),
190 Some(Err(why)) => return Err(why),
191 None => None,
192 };
193
194 let mut mount_data = MountData {
195 c_source,
196 c_target,
197 flags,
198 data,
199 };
200
201 let mut res = match fstype {
202 FilesystemType::Auto(supported) => mount_data.automount(supported.dev_file_systems()),
203 FilesystemType::Set(set) => mount_data.automount(set.iter().copied()),
204 FilesystemType::Manual(fstype) => mount_data.mount(fstype),
205 };
206
207 match res {
208 Ok(ref mut _mount) => {
209 #[cfg(feature = "loop")]
210 {
211 _mount.loopback = loopback;
212 _mount.loop_path = loop_path;
213 }
214 }
215 Err(_) =>
216 {
217 #[cfg(feature = "loop")]
218 if let Some(loopback) = loopback {
219 let _res = loopback.detach();
220 }
221 }
222 }
223
224 res
225 }
226
227 pub fn mount_autodrop(
233 self,
234 source: impl AsRef<Path>,
235 target: impl AsRef<Path>,
236 unmount_flags: UnmountFlags,
237 ) -> io::Result<UnmountDrop<Mount>> {
238 self.mount(source, target)
239 .map(|m| m.into_unmount_drop(unmount_flags))
240 }
241}
242
243struct MountData {
244 c_source: Option<CString>,
245 c_target: CString,
246 flags: MountFlags,
247 data: Option<CString>,
248}
249
250impl MountData {
251 fn mount(&mut self, fstype: &str) -> io::Result<Mount> {
252 let c_fstype = to_cstring(fstype.as_bytes())?;
253 match mount_(
254 self.c_source.as_ref(),
255 &self.c_target,
256 &c_fstype,
257 self.flags,
258 self.data.as_ref(),
259 ) {
260 Ok(()) => Ok(Mount::from_target_and_fstype(
261 self.c_target.clone(),
262 fstype.to_owned(),
263 )),
264 Err(why) => Err(why),
265 }
266 }
267
268 fn automount<'a, I: Iterator<Item = &'a str> + 'a>(mut self, iter: I) -> io::Result<Mount> {
269 let mut res = Ok(());
270
271 for fstype in iter {
272 match self.mount(fstype) {
273 mount @ Ok(_) => return mount,
274 Err(why) => res = Err(why),
275 }
276 }
277
278 match res {
279 Ok(()) => Err(io::Error::new(
280 io::ErrorKind::NotFound,
281 "no supported file systems found",
282 )),
283 Err(why) => Err(why),
284 }
285 }
286}
287
288fn mount_(
289 c_source: Option<&CString>,
290 c_target: &CString,
291 c_fstype: &CString,
292 flags: MountFlags,
293 c_data: Option<&CString>,
294) -> io::Result<()> {
295 let result = unsafe {
296 mount(
297 c_source.map_or_else(ptr::null, |s| s.as_ptr()),
298 c_target.as_ptr(),
299 c_fstype.as_ptr(),
300 flags.bits(),
301 c_data
302 .map_or_else(ptr::null, |s| s.as_ptr())
303 .cast::<libc::c_void>(),
304 )
305 };
306
307 match result {
308 0 => Ok(()),
309 _err => Err(io::Error::last_os_error()),
310 }
311}