1mod opt;
15use crate::common::fs::MountOption;
16pub use opt::*;
17
18use nix::mount::{MntFlags, MsFlags, mount, umount2};
19use std::{
20 ffi::CString,
21 io::{Error, ErrorKind, Result},
22 ops::Deref,
23 path::{Path, PathBuf},
24};
25use tracing::{debug, error};
26
27use crate::{
28 AsCString, AsPath, Filesystem, FsData, PartitionID, StackableFilesystem, StateRecovery,
29 restore_fsdata,
30};
31
32#[derive(Debug)]
33pub struct OverlayFs {
35 lower: Vec<PathBuf>,
36 upper: Option<PathBuf>,
37 work: Option<PathBuf>,
38 target: CString,
39 options: Vec<String>,
40 id: Option<PartitionID>,
41 drop: bool,
42}
43
44impl OverlayFs {
45 #[must_use = "initialised OverlayFs handle should be used"]
46 #[inline]
47 pub fn new(
48 lower: impl IntoIterator<Item = impl AsRef<Path>>,
49 upper: Option<impl Into<PathBuf>>,
50 work: Option<impl Into<PathBuf>>,
51 target: impl AsRef<Path>,
52 drop: bool,
53 ) -> Result<OverlayFs> {
54 Ok(Self {
55 lower: lower
56 .into_iter()
57 .map(|x| x.as_ref().to_path_buf())
58 .collect(),
59 upper: upper.map(|x| x.into()),
60 work: work.map(|x| x.into()),
61 target: target.as_ref().as_cstring(),
62 options: OverlayFsOption::defaults(),
63 id: None,
64 drop,
65 })
66 }
67
68 #[must_use = "initialised OverlayFs handle should be used"]
69 #[inline]
70 pub fn readonly(
71 lower: impl IntoIterator<Item = impl AsRef<Path>>,
72 target: impl AsRef<Path>,
73 ) -> Result<OverlayFs> {
74 let lower: Vec<PathBuf> = lower
75 .into_iter()
76 .map(|x| x.as_ref().to_path_buf())
77 .collect();
78 if lower.len() < 2 {
79 return Err(Error::other(
80 "overlay FileSystem need a least 2 lower directory to work",
81 ));
82 }
83 Ok(OverlayFs {
84 lower,
85 upper: None,
86 work: None,
87 target: target.as_ref().as_cstring(),
88 options: OverlayFsOption::defaults(),
89 id: None,
90 drop: true,
91 })
92 }
93
94 #[must_use = "initialised OverlayFs handle should be used"]
95 #[inline]
96 pub fn writable(
97 lower: impl IntoIterator<Item = impl AsRef<Path>>,
98 upper: impl AsRef<Path>,
99 work: impl AsRef<Path>,
100 target: impl AsRef<Path>,
101 ) -> Result<OverlayFs> {
102 if PartitionID::try_from(upper.as_ref())? != PartitionID::try_from(work.as_ref())? {
103 return Err(Error::other(
104 "overlay FileSystem need the upper dir and the work dir to be on the same FileSystem",
105 ));
106 }
107 Ok(OverlayFs {
108 lower: lower
109 .into_iter()
110 .map(|x| x.as_ref().to_path_buf())
111 .collect(),
112 upper: Some(upper.as_ref().to_path_buf()),
113 work: Some(work.as_ref().to_path_buf()),
114 target: target.as_ref().as_cstring(),
115 options: OverlayFsOption::defaults(),
116 id: None,
117 drop: true,
118 })
119 }
120
121 #[inline]
122 pub fn work(&self) -> Option<&PathBuf> {
123 self.work.as_ref()
124 }
125
126 #[inline]
127 pub fn set_work(&mut self, work: PathBuf) -> Result<()> {
128 if PartitionID::try_from(work.as_path())?
129 != PartitionID::try_from(
130 self.upper
131 .as_ref()
132 .ok_or(Error::new(ErrorKind::NotFound, "upper directory not set"))?
133 .as_path(),
134 )?
135 {
136 return Err(Error::other(
137 "overlay FileSystem need the upper dir and the work dir to be on the same FileSystem",
138 ));
139 }
140 self.work = Some(work);
141 Ok(())
142 }
143}
144
145impl Filesystem for OverlayFs {
146 #[inline]
147 fn mount(&mut self) -> Result<&mut Self> {
148 if !Self::is_available() {
149 return Err(Error::new(
150 ErrorKind::NotFound,
151 "overlayfs is not available",
152 ));
153 }
154 if self.mounted() {
155 debug!("Damascus: partition already mounted");
156 return Ok(self);
157 }
158 let flags = MsFlags::empty();
159 let mut options = String::new();
160 options.push_str("lowerdir=");
161 for (i, p) in self.lower.iter().enumerate() {
162 if i != 0 {
163 options.push(':')
164 }
165 options.push_str(p.to_string_lossy().as_ref());
166 }
167 if let (Some(u), Some(w)) = (self.upper.as_ref(), self.work.as_ref()) {
168 options.push_str(",upperdir=");
169 options.push_str(u.to_string_lossy().as_ref());
170 options.push_str(",workdir=");
171 options.push_str(w.to_string_lossy().as_ref());
172 }
173 for mo in &self.options {
174 options.push_str(&(",".to_string() + &mo.to_string()))
175 }
176 let mut args = options.as_bytes().to_vec();
177 args.push(b'\0');
178 let data = unsafe { CString::from_vec_with_nul_unchecked(args) };
179 mount(
180 Some(c"overlay"),
181 &*self.target,
182 Some(c"overlay"),
183 flags,
184 Some(data.as_bytes()),
185 )
186 .inspect_err(|_x| {
187 dbg!(&self);
188 })?;
189 self.id = Some(
190 PartitionID::try_from(self.target.as_path())
191 .map_err(|_| Error::other("unable to get PartitionID"))?,
192 );
193 Ok(self)
194 }
195
196 #[inline]
197 fn unmount(&mut self) -> Result<&mut Self> {
198 if self.mounted() {
199 umount2(self.target.as_c_str(), MntFlags::MNT_DETACH)?;
200 self.id = None;
201 }
202 Ok(self)
203 }
204
205 #[inline]
206 fn scoped(&self) -> bool {
207 self.drop
208 }
209
210 #[inline]
211 fn set_scoped(&mut self, drop: bool) -> &mut Self {
212 self.drop = drop;
213 self
214 }
215
216 #[inline]
217 fn id(&self) -> Option<&PartitionID> {
218 self.id.as_ref()
219 }
220
221 #[inline]
222 fn target(&self) -> PathBuf {
223 self.target.as_path().to_path_buf()
224 }
225
226 #[inline]
227 fn set_target(&mut self, target: impl AsRef<Path>) -> Result<&mut Self> {
228 if self.id.is_some() {
229 return Err(Error::other(
230 "mount point cannot be change when the FileSystem is mounted",
231 ));
232 }
233 self.target = target.as_ref().as_cstring();
234 Ok(self)
235 }
236
237 fn is_available() -> bool {
238 if let Ok(res) = std::fs::read_to_string("/proc/filesystems") {
239 res.contains("overlay")
240 } else {
241 false
242 }
243 }
244
245 fn add_option(&mut self, option: impl Into<String>) -> Result<()> {
246 self.options.push(option.into());
247 Ok(())
248 }
249
250 fn remove_option(&mut self, option: impl AsRef<str>) -> Result<()> {
251 self.options.retain(|x| x.deref() != option.as_ref());
252 Ok(())
253 }
254
255 fn options(&self) -> &[String] {
256 self.options.deref()
257 }
258}
259
260impl StackableFilesystem for OverlayFs {
261 #[inline]
262 fn lower(&self) -> Vec<&Path> {
263 self.lower.iter().map(|x| x.as_path()).collect()
264 }
265
266 #[inline]
267 fn set_lower(
268 &mut self,
269 lower: impl IntoIterator<Item = impl AsRef<Path>>,
270 ) -> Result<&mut Self> {
271 if self.id.is_some() {
272 return Err(Error::other(
273 "upper layer cannot be change when the FileSystem is mounted",
274 ));
275 }
276 self.lower = lower
277 .into_iter()
278 .map(|x| x.as_ref().to_path_buf())
279 .collect();
280 Ok(self)
281 }
282
283 #[inline]
284 fn upper(&self) -> Option<&Path> {
285 self.upper.as_deref()
286 }
287
288 #[inline]
289 fn set_upper(&mut self, upper: impl Into<PathBuf>) -> Result<&mut Self> {
290 let upper = upper.into();
291 if PartitionID::try_from(upper.as_path())?
292 != PartitionID::try_from(
293 self.work
294 .as_ref()
295 .ok_or(Error::new(ErrorKind::NotFound, "work directory not set"))?
296 .as_path(),
297 )?
298 {
299 return Err(Error::other(
300 "overlay FileSystem need the upper dir and the work dir to be on the same FileSystem",
301 ));
302 } else if self.id.is_some() {
303 return Err(Error::other(
304 "upper layer cannot be change when the FileSystem is mounted",
305 ));
306 }
307 self.upper = Some(upper);
308 Ok(self)
309 }
310}
311
312impl StateRecovery for OverlayFs {
313 fn recover<P: AsRef<Path>>(path: P) -> Result<Self> {
314 let path = path.as_ref();
315 let data: FsData = restore_fsdata(path)?.ok_or(Error::new(
316 ErrorKind::NotFound,
317 "OverlayFs not found at mount point : ".to_string() + &path.to_string_lossy(),
318 ))?;
319 let mut lower = vec![];
320 let mut upper = None;
321 let mut work = None;
322 let target = path.as_cstring();
323 let options = data
324 .options()
325 .iter()
326 .filter_map(|x| {
327 let (o, va) = if let Some(x) = x.split_once('=') {
328 x
329 } else {
330 return Some(x.to_owned());
331 };
332 match o {
333 "lowerdir" => {
334 for path in va.split(':') {
335 lower.push(PathBuf::from(path))
336 }
337 return None;
338 }
339 "upperdir" => {
340 upper = Some(PathBuf::from(va));
341 return None;
342 }
343 "workdir" => {
344 work = Some(PathBuf::from(va));
345 return None;
346 }
347 _ => {}
348 }
349 Some(x.to_owned())
350 })
351 .collect();
352 Ok(Self {
353 lower,
354 upper,
355 work,
356 target,
357 options,
358 id: Some(
359 PartitionID::try_from(path)
360 .map_err(|_| Error::other("unable to get PartitionID"))?,
361 ),
362 drop: false,
363 })
364 }
365}
366
367impl Drop for OverlayFs {
368 #[inline]
369 fn drop(&mut self) {
370 if self.drop
371 && let Err(err) = self.unmount()
372 {
373 error!(
374 "Damascus: unable to unmount overlay at {:?} because : {}",
375 self.target, err
376 )
377 }
378 }
379}