blob: 807626f86b41132b746a11fd0558b7f7c9b51af9 [file] [log] [blame]
use std::io;
use std::fmt;
use std::ffi::CStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::env::current_dir;
use std::default::Default;
use nix::mount::{MsFlags, mount};
use {OSError, Error};
use util::path_to_cstring;
use explain::{Explainable, exists, user};
use mountinfo::{parse_mount_point};
/// A remount definition
///
/// Usually it is used to change mount flags for a mounted filesystem.
/// Especially to make a readonly filesystem writable or vice versa.
#[derive(Debug, Clone)]
pub struct Remount {
path: PathBuf,
flags: MountFlags,
}
#[derive(Debug, Clone, Default)]
struct MountFlags {
pub bind: Option<bool>,
pub readonly: Option<bool>,
pub nodev: Option<bool>,
pub noexec: Option<bool>,
pub nosuid: Option<bool>,
pub noatime: Option<bool>,
pub nodiratime: Option<bool>,
pub relatime: Option<bool>,
pub strictatime: Option<bool>,
pub dirsync: Option<bool>,
pub synchronous: Option<bool>,
pub mandlock: Option<bool>,
}
impl MountFlags {
fn apply_to_flags(&self, flags: MsFlags) -> MsFlags {
let mut flags = flags;
flags = apply_flag(flags, MsFlags::MS_BIND, self.bind);
flags = apply_flag(flags, MsFlags::MS_RDONLY, self.readonly);
flags = apply_flag(flags, MsFlags::MS_NODEV, self.nodev);
flags = apply_flag(flags, MsFlags::MS_NOEXEC, self.noexec);
flags = apply_flag(flags, MsFlags::MS_NOSUID, self.nosuid);
flags = apply_flag(flags, MsFlags::MS_NOATIME, self.noatime);
flags = apply_flag(flags, MsFlags::MS_NODIRATIME, self.nodiratime);
flags = apply_flag(flags, MsFlags::MS_RELATIME, self.relatime);
flags = apply_flag(flags, MsFlags::MS_STRICTATIME, self.strictatime);
flags = apply_flag(flags, MsFlags::MS_DIRSYNC, self.dirsync);
flags = apply_flag(flags, MsFlags::MS_SYNCHRONOUS, self.synchronous);
flags = apply_flag(flags, MsFlags::MS_MANDLOCK, self.mandlock);
flags
}
}
fn apply_flag(flags: MsFlags, flag: MsFlags, set: Option<bool>) -> MsFlags {
match set {
Some(true) => flags | flag,
Some(false) => flags & !flag,
None => flags,
}
}
quick_error! {
#[derive(Debug)]
pub enum RemountError {
Io(msg: String, err: io::Error) {
cause(err)
display("{}: {}", msg, err)
description(err.description())
from(err: io::Error) -> (String::new(), err)
}
ParseMountInfo(err: String) {
display("{}", err)
from()
}
UnknownMountPoint(path: PathBuf) {
display("Cannot find mount point: {:?}", path)
}
}
}
impl Remount {
/// Create a new Remount operation
///
/// By default it doesn't modify any flags. So is basically useless, you
/// should set some flags to make it effective.
pub fn new<A: AsRef<Path>>(path: A) -> Remount {
Remount {
path: path.as_ref().to_path_buf(),
flags: Default::default(),
}
}
/// Set bind flag
/// Note: remount readonly doesn't work without MS_BIND flag
/// inside unpriviledged user namespaces
pub fn bind(mut self, flag: bool) -> Remount {
self.flags.bind = Some(flag);
self
}
/// Set readonly flag
pub fn readonly(mut self, flag: bool) -> Remount {
self.flags.readonly = Some(flag);
self
}
/// Set nodev flag
pub fn nodev(mut self, flag: bool) -> Remount {
self.flags.nodev = Some(flag);
self
}
/// Set noexec flag
pub fn noexec(mut self, flag: bool) -> Remount {
self.flags.noexec = Some(flag);
self
}
/// Set nosuid flag
pub fn nosuid(mut self, flag: bool) -> Remount {
self.flags.nosuid = Some(flag);
self
}
/// Set noatime flag
pub fn noatime(mut self, flag: bool) -> Remount {
self.flags.noatime = Some(flag);
self
}
/// Set nodiratime flag
pub fn nodiratime(mut self, flag: bool) -> Remount {
self.flags.nodiratime = Some(flag);
self
}
/// Set relatime flag
pub fn relatime(mut self, flag: bool) -> Remount {
self.flags.relatime = Some(flag);
self
}
/// Set strictatime flag
pub fn strictatime(mut self, flag: bool) -> Remount {
self.flags.strictatime = Some(flag);
self
}
/// Set dirsync flag
pub fn dirsync(mut self, flag: bool) -> Remount {
self.flags.dirsync = Some(flag);
self
}
/// Set synchronous flag
pub fn synchronous(mut self, flag: bool) -> Remount {
self.flags.synchronous = Some(flag);
self
}
/// Set mandlock flag
pub fn mandlock(mut self, flag: bool) -> Remount {
self.flags.mandlock = Some(flag);
self
}
/// Execute a remount
pub fn bare_remount(self) -> Result<(), OSError> {
let mut flags = match get_mountpoint_flags(&self.path) {
Ok(flags) => flags,
Err(e) => {
return Err(OSError::from_remount(e, Box::new(self)));
},
};
flags = self.flags.apply_to_flags(flags) | MsFlags::MS_REMOUNT;
mount(
None::<&CStr>,
&*path_to_cstring(&self.path),
None::<&CStr>,
flags,
None::<&CStr>,
).map_err(|err| OSError::from_nix(err, Box::new(self)))
}
/// Execute a remount and explain the error immediately
pub fn remount(self) -> Result<(), Error> {
self.bare_remount().map_err(OSError::explain)
}
}
impl fmt::Display for MountFlags {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
let mut prefix = "";
if let Some(true) = self.bind {
try!(write!(fmt, "{}bind", prefix));
prefix = ",";
}
if let Some(true) = self.readonly {
try!(write!(fmt, "{}ro", prefix));
prefix = ",";
}
if let Some(true) = self.nodev {
try!(write!(fmt, "{}nodev", prefix));
prefix = ",";
}
if let Some(true) = self.noexec {
try!(write!(fmt, "{}noexec", prefix));
prefix = ",";
}
if let Some(true) = self.nosuid {
try!(write!(fmt, "{}nosuid", prefix));
prefix = ",";
}
if let Some(true) = self.noatime {
try!(write!(fmt, "{}noatime", prefix));
prefix = ",";
}
if let Some(true) = self.nodiratime {
try!(write!(fmt, "{}nodiratime", prefix));
prefix = ",";
}
if let Some(true) = self.relatime {
try!(write!(fmt, "{}relatime", prefix));
prefix = ",";
}
if let Some(true) = self.strictatime {
try!(write!(fmt, "{}strictatime", prefix));
prefix = ",";
}
if let Some(true) = self.dirsync {
try!(write!(fmt, "{}dirsync", prefix));
prefix = ",";
}
if let Some(true) = self.synchronous {
try!(write!(fmt, "{}sync", prefix));
prefix = ",";
}
if let Some(true) = self.mandlock {
try!(write!(fmt, "{}mand", prefix));
}
Ok(())
}
}
impl fmt::Display for Remount {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if !self.flags.apply_to_flags(MsFlags::empty()).is_empty() {
try!(write!(fmt, "{} ", self.flags));
}
write!(fmt, "remount {:?}", &self.path)
}
}
impl Explainable for Remount {
fn explain(&self) -> String {
[
format!("path: {}", exists(&self.path)),
format!("{}", user()),
].join(", ")
}
}
fn get_mountpoint_flags(path: &Path) -> Result<MsFlags, RemountError> {
let mount_path = if path.is_absolute() {
path.to_path_buf()
} else {
let mut mpath = try!(current_dir());
mpath.push(path);
mpath
};
let mut mountinfo_content = Vec::with_capacity(4 * 1024);
let mountinfo_path = Path::new("/proc/self/mountinfo");
let mut mountinfo_file = try!(File::open(mountinfo_path)
.map_err(|e| RemountError::Io(
format!("Cannot open file: {:?}", mountinfo_path), e)));
try!(mountinfo_file.read_to_end(&mut mountinfo_content)
.map_err(|e| RemountError::Io(
format!("Cannot read file: {:?}", mountinfo_path), e)));
match get_mountpoint_flags_from(&mountinfo_content, &mount_path) {
Ok(Some(flags)) => Ok(flags),
Ok(None) => Err(RemountError::UnknownMountPoint(mount_path)),
Err(e) => Err(e),
}
}
fn get_mountpoint_flags_from(content: &[u8], path: &Path)
-> Result<Option<MsFlags>, RemountError>
{
// iterate from the end of the mountinfo file
for line in content.split(|c| *c == b'\n').rev() {
let entry = parse_mount_point(line)
.map_err(|e| RemountError::ParseMountInfo(e.0))?;
if let Some(mount_point) = entry {
if mount_point.mount_point == path {
return Ok(Some(mount_point.get_mount_flags()));
}
}
}
Ok(None)
}
#[cfg(test)]
mod test {
use std::path::Path;
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
use nix::mount::MsFlags;
use Error;
use super::{Remount, RemountError, MountFlags};
use super::{get_mountpoint_flags, get_mountpoint_flags_from};
#[test]
fn test_mount_flags() {
let flags = MountFlags {
bind: Some(true),
readonly: Some(true),
nodev: Some(true),
noexec: Some(true),
nosuid: Some(true),
noatime: Some(true),
nodiratime: Some(true),
relatime: Some(true),
strictatime: Some(true),
dirsync: Some(true),
synchronous: Some(true),
mandlock: Some(true),
};
let bits = (MsFlags::MS_BIND | MsFlags::MS_RDONLY | MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID |
MsFlags::MS_NOATIME | MsFlags::MS_NODIRATIME | MsFlags::MS_RELATIME | MsFlags::MS_STRICTATIME |
MsFlags::MS_DIRSYNC | MsFlags::MS_SYNCHRONOUS | MsFlags::MS_MANDLOCK).bits();
assert_eq!(flags.apply_to_flags(MsFlags::empty()).bits(), bits);
let flags = MountFlags {
bind: Some(false),
readonly: Some(false),
nodev: Some(false),
noexec: Some(false),
nosuid: Some(false),
noatime: Some(false),
nodiratime: Some(false),
relatime: Some(false),
strictatime: Some(false),
dirsync: Some(false),
synchronous: Some(false),
mandlock: Some(false),
};
assert_eq!(flags.apply_to_flags(MsFlags::from_bits_truncate(bits)).bits(), 0);
let flags = MountFlags::default();
assert_eq!(flags.apply_to_flags(MsFlags::from_bits_truncate(0)).bits(), 0);
assert_eq!(flags.apply_to_flags(MsFlags::from_bits_truncate(bits)).bits(), bits);
}
#[test]
fn test_remount() {
let remount = Remount::new("/");
assert_eq!(format!("{}", remount), "remount \"/\"");
let remount = Remount::new("/").readonly(true).nodev(true);
assert_eq!(format!("{}", remount), "ro,nodev remount \"/\"");
}
#[test]
fn test_get_mountpoint_flags_from() {
let content = b"19 24 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:12 - proc proc rw";
let flags = get_mountpoint_flags_from(&content[..], Path::new("/proc")).unwrap().unwrap();
assert_eq!(flags, MsFlags::MS_NODEV | MsFlags::MS_NOEXEC | MsFlags::MS_NOSUID | MsFlags::MS_RELATIME);
}
#[test]
fn test_get_mountpoint_flags_from_dups() {
let content = b"11 18 0:4 / /tmp rw shared:28 - tmpfs tmpfs rw\n\
12 18 0:6 / /tmp rw,nosuid,nodev shared:29 - tmpfs tmpfs rw\n";
let flags = get_mountpoint_flags_from(&content[..], Path::new("/tmp")).unwrap().unwrap();
assert_eq!(flags, MsFlags::MS_NOSUID | MsFlags::MS_NODEV);
}
#[test]
fn test_get_mountpoint_flags() {
assert!(get_mountpoint_flags(Path::new("/")).is_ok());
}
#[test]
fn test_get_mountpoint_flags_unknown() {
let mount_point = Path::new(OsStr::from_bytes(b"/\xff"));
let error = get_mountpoint_flags(mount_point).unwrap_err();
match error {
RemountError::UnknownMountPoint(p) => assert_eq!(p, mount_point),
_ => panic!(),
}
}
#[test]
fn test_remount_unknown_mountpoint() {
let remount = Remount::new("/non-existent");
let error = remount.remount().unwrap_err();
let Error(_, e, msg) = error;
match e.get_ref() {
Some(e) => {
assert_eq!(
e.to_string(),
"Cannot find mount point: \"/non-existent\"");
},
_ => panic!(),
}
assert!(msg.starts_with(
"Cannot find mount point: \"/non-existent\", path: missing, "));
}
}