blob: bccd8676c49be3ebf71e9d1755d7534e5e91f96c [file] [log] [blame]
use std::fmt;
use std::path::{Path, PathBuf};
use std::fs::metadata;
use std::ffi::{CStr, CString};
use std::os::unix::fs::MetadataExt;
use std::os::unix::ffi::OsStrExt;
use nix::mount::{MsFlags, mount};
use util::{path_to_cstring, as_path};
use {OSError, Error};
use explain::{Explainable, exists, user};
/// An overlay mount point
///
/// This requires linux kernel of at least 3.18.
///
/// To use overlayfs in user namespace you need a kernel patch (which is
/// enabled by default in ubuntu). At least this is still true for mainline
/// kernel 4.5.0.
#[derive(Debug, Clone)]
pub struct Overlay {
lowerdirs: Vec<PathBuf>,
upperdir: Option<PathBuf>,
workdir: Option<PathBuf>,
target: CString,
}
impl Overlay {
/// A constructor for read-only overlayfs mount
///
/// You must have at least two directories in the list (for single
/// dir it might be equal to bind mount, but kernel return EINVAL for
/// such options).
///
/// The top-most directory will be first in the list.
pub fn readonly<'x, I, T>(dirs: I, target: T) -> Overlay
where I: Iterator<Item=&'x Path>, T: AsRef<Path>
{
Overlay {
lowerdirs: dirs.map(|x| x.to_path_buf()).collect(),
upperdir: None,
workdir: None,
target: path_to_cstring(target.as_ref()),
}
}
/// A constructor for writable overlayfs mount
///
/// The upperdir and workdir must be on the same filesystem.
///
/// The top-most directory will be first in the list of lowerdirs.
pub fn writable<'x, I, B, C, D>(lowerdirs: I, upperdir: B,
workdir: C, target: D)
-> Overlay
where I: Iterator<Item=&'x Path>, B: AsRef<Path>,
C: AsRef<Path>, D: AsRef<Path>,
{
Overlay {
lowerdirs: lowerdirs.map(|x| x.to_path_buf()).collect(),
upperdir: Some(upperdir.as_ref().to_path_buf()),
workdir: Some(workdir.as_ref().to_path_buf()),
target: path_to_cstring(target.as_ref()),
}
}
/// Execute an overlay mount
pub fn bare_mount(self) -> Result<(), OSError> {
let mut options = Vec::new();
options.extend(b"lowerdir=");
for (i, p) in self.lowerdirs.iter().enumerate() {
if i != 0 {
options.push(b':')
}
append_escape(&mut options, p);
}
if let (Some(u), Some(w)) = (self.upperdir.as_ref(), self.workdir.as_ref()) {
options.extend(b",upperdir=");
append_escape(&mut options, u);
options.extend(b",workdir=");
append_escape(&mut options, w);
}
mount(
Some(CStr::from_bytes_with_nul(b"overlay\0").unwrap()),
&*self.target,
Some(CStr::from_bytes_with_nul(b"overlay\0").unwrap()),
MsFlags::empty(),
Some(&*options),
).map_err(|err| OSError::from_nix(err, Box::new(self)))
}
/// Execute an overlay mount and explain the error immediately
pub fn mount(self) -> Result<(), Error> {
self.bare_mount().map_err(OSError::explain)
}
}
/// Escape the path to put it into options string for overlayfs
///
/// The rules here are not documented anywhere as far as I know and was
/// derived experimentally.
fn append_escape(dest: &mut Vec<u8>, path: &Path) {
for &byte in path.as_os_str().as_bytes().iter() {
match byte {
// This is escape char
b'\\' => { dest.push(b'\\'); dest.push(b'\\'); }
// This is used as a path separator in lowerdir
b':' => { dest.push(b'\\'); dest.push(b':'); }
// This is used as a argument separator
b',' => { dest.push(b'\\'); dest.push(b','); }
x => dest.push(x),
}
}
}
impl fmt::Display for Overlay {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if let (Some(udir), Some(wdir)) =
(self.upperdir.as_ref(), self.workdir.as_ref())
{
write!(fmt, "overlayfs \
{},upperdir={:?},workdir={:?} -> {:?}",
self.lowerdirs.iter().map(|x| format!("{:?}", x))
.collect::<Vec<_>>().join(":"),
udir, wdir, as_path(&self.target))
} else {
write!(fmt, "overlayfs \
{} -> {:?}",
self.lowerdirs.iter().map(|x| format!("{:?}", x))
.collect::<Vec<_>>().join(":"),
as_path(&self.target))
}
}
}
impl Explainable for Overlay {
fn explain(&self) -> String {
let mut info = self.lowerdirs.iter()
.map(|x| format!("{:?}: {}", x, exists(x)))
.collect::<Vec<String>>();
if let (Some(udir), Some(wdir)) =
(self.upperdir.as_ref(), self.workdir.as_ref())
{
let umeta = metadata(&udir).ok();
let wmeta = metadata(&wdir).ok();
info.push(format!("upperdir: {}", exists(&udir)));
info.push(format!("workdir: {}", exists(&wdir)));
if let (Some(u), Some(w)) = (umeta, wmeta) {
info.push(format!("{}", if u.dev() == w.dev()
{ "same-fs" } else { "different-fs" }));
}
if udir.starts_with(wdir) {
info.push("upperdir-prefix-of-workdir".to_string());
} else if wdir.starts_with(udir) {
info.push("workdir-prefix-of-upperdir".to_string());
}
info.push(format!("target: {}", exists(as_path(&self.target))));
}
if self.lowerdirs.len() < 1 {
info.push("no-lowerdirs".to_string());
} else if self.upperdir.is_none() && self.lowerdirs.len() < 2 {
info.push("single-lowerdir".to_string());
}
info.push(user().to_string());
info.join(", ")
}
}