From 34b38c8c94bfcebd1966f48b5a6eb3fb76ad6a33 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Tue, 21 Aug 2018 19:16:21 -0700 Subject: [PATCH 01/15] mesabox: partially implement mount --- Cargo.toml | 3 +- libmesabox/Cargo.toml | 3 +- libmesabox/src/lsb/mount/mod.rs | 767 ++++++++++++++++++++++++++++++++ libmesabox/src/util_list.rs | 1 + 4 files changed, 772 insertions(+), 2 deletions(-) create mode 100644 libmesabox/src/lsb/mount/mod.rs diff --git a/Cargo.toml b/Cargo.toml index c083b2c..fbe8363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,10 @@ loginutils = [ # FIXME: this is only like this because we can't rename dependencies and we use the same macro in # the tests that libmesabox uses to build +mount = ["libmesabox/mount"] tar_util = ["libmesabox/tar_util"] lsb = [ - "tar_util" + "mount", "tar_util" ] ping = ["libmesabox/ping"] diff --git a/libmesabox/Cargo.toml b/libmesabox/Cargo.toml index eab9d34..e5bb3da 100644 --- a/libmesabox/Cargo.toml +++ b/libmesabox/Cargo.toml @@ -22,9 +22,10 @@ loginutils = [ ] # XXX: temporary until renaming dependencies is supported +mount = ["libc"] tar_util = ["tar", "globset"] lsb = [ - "tar" + "mount", "tar" ] ping = ["chrono", "crossbeam", "libc", "pnet", "byteorder", "trust-dns-resolver", "mio", "socket2"] diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs new file mode 100644 index 0000000..c78c1fa --- /dev/null +++ b/libmesabox/src/lsb/mount/mod.rs @@ -0,0 +1,767 @@ +// +// Copyright (c) 2018, The MesaLock Linux Project Contributors +// All rights reserved. +// +// This work is licensed under the terms of the BSD 3-Clause License. +// For a copy, see the LICENSE file. +// + +extern crate clap; +extern crate libc; +extern crate nix; +use clap::Arg; +use nix::mount::MsFlags; +use std::collections::HashMap; +use std::ffi::OsString; +use std::fs; +use std::io::{self, BufRead, BufReader, Write}; +use std::path::PathBuf; +use std::thread; +use {ArgsIter, LockError, Result, UtilSetup, UtilWrite}; + +pub(crate) const NAME: &str = "mount"; +pub(crate) const DESCRIPTION: &str = "Mount file systems"; + +#[derive(Debug, Fail)] +enum MountError { + #[fail(display = "{}", _0)] + Output(#[cause] io::Error), + #[fail(display = "Cannot open file: {}", _0)] + OpenFileError(String), + #[fail(display = "Bad format while reading {}", _0)] + FSDescFileFormatError(String), + #[fail(display = "Unsupported filesystem type")] + UnsupportedFSType, + #[fail(display = "unknown filesystem type '{}'.", _0)] + UnknownFSType(String), + #[fail(display = "Unsupported option")] + UnsupportedOption, + #[fail(display = "Only root can do that")] + PermissionDenied, + #[fail(display = "Invalid argument")] + InvalidArgument, + #[fail(display = "Cannot support UUID on your system")] + UuidSupportError, + #[fail(display = "Cannot support Label on your system")] + LabelSupportError, + #[fail(display = "Cannot find UUID=\"{}\"", _0)] + UuidNotFoundError(String), + #[fail(display = "Cannot find Label=\"{}\"", _0)] + LabelNotFoundError(String), + #[fail(display = "{}: mount point does not exist.", _0)] + MountPointNotExist(String), + #[fail(display = "{}: special device {} does not exist.", _0, _1)] + DeviceNotExist(String, String), + #[fail(display = "Invalid UTF-8 characters")] + InvalidCharError, + // Denotes an error caused by one of stdin, stdout, or stderr failing to lock + #[fail(display = "{}", _0)] + Lock(#[cause] LockError), +} + +impl From for MountError { + fn from(err: LockError) -> Self { + MountError::Lock(err) + } +} + +impl From for MountError { + fn from(err: io::Error) -> Self { + MountError::Output(err) + } +} + +// There are several types of mount, all of them implement Mount trait +trait Mount { + fn run(&mut self) -> MountResult<()>; +} + +struct MountCommand { + multi_thread: bool, + mount_list: Vec>, +} + +struct Uuid { + // map UUID to actual path + path_map: HashMap, +} + +impl Uuid { + fn new() -> MountResult { + let mut path_map: HashMap = HashMap::new(); + let dir = fs::read_dir("/dev/disk/by-uuid").or(Err(MountError::UuidSupportError))?; + for symlink in dir { + let link = symlink.or(Err(MountError::UuidSupportError))?; + path_map.insert( + link.file_name(), + link.path() + .canonicalize() + .or(Err(MountError::UuidSupportError))?, + ); + } + Ok(Self { path_map: path_map }) + } + + fn to_dev(&self, input: OsString) -> MountResult { + // OsString and OsStr lacks "starts_with" function + // FIXME Maybe find a better way to test if input starts with "UUID=" + let dir; + let s = input + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?; + if s.starts_with("UUID=") { + dir = OsString::from(&s[5..]); + } else { + dir = input; + } + Ok(self + .path_map + .get(&dir) + .ok_or(MountError::UuidNotFoundError(String::from(&s[5..])))? + .clone()) + } +} + +struct Label { + // map Label to actual path + path_map: HashMap, +} + +impl Label { + fn new() -> MountResult { + let mut path_map: HashMap = HashMap::new(); + let dir = fs::read_dir("/dev/disk/by-label").or(Err(MountError::LabelSupportError))?; + for symlink in dir { + let link = symlink.or(Err(MountError::LabelSupportError))?; + path_map.insert( + link.file_name(), + link.path() + .canonicalize() + .or(Err(MountError::LabelSupportError))?, + ); + } + Ok(Self { path_map: path_map }) + } + + fn to_dev(&self, input: OsString) -> MountResult { + // OsString and OsStr lacks "starts_with" function + // FIXME Maybe find a better way to test if input starts with "Label=" + let dir; + let s = input + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?; + if s.starts_with("Label=") { + dir = OsString::from(&s[6..]); + } else { + dir = input; + } + Ok(self + .path_map + .get(&dir) + .ok_or(MountError::LabelNotFoundError(String::from(&s[5..])))? + .clone()) + } +} + +struct Flag { + option_map: HashMap<&'static str, MsFlags>, + flag: MsFlags, +} + +impl Default for Flag { + fn default() -> Self { + Self { + option_map: Self::get_option_map(), + flag: MsFlags::MS_SILENT, + } + } +} + +impl Flag { + fn get_option_map() -> HashMap<&'static str, MsFlags> { + let mut option_map = HashMap::new(); + //option_map.insert("auto",MsFlags{bits: 0,}); // ignored + //option_map.insert("noauto", MsFlags{bits: 0,}); // ignored + //option_map.insert("defaults", MsFlags{bits: 0,}); // ignored + //option_map.insert("nouser", MsFlags{bits: 0,}); // ignored + //option_map.insert("user", MsFlags{bits: 0,}); // ignored + option_map.insert("async", MsFlags::MS_SYNCHRONOUS); + option_map.insert("atime", MsFlags::MS_NOATIME); + option_map.insert("dev", MsFlags::MS_NODEV); + option_map.insert("exec", MsFlags::MS_NOEXEC); + option_map.insert("noatime", MsFlags::MS_NOATIME); + option_map.insert("nodev", MsFlags::MS_NODEV); + option_map.insert("noexec", MsFlags::MS_NOEXEC); + option_map.insert("nosuid", MsFlags::MS_NOSUID); + option_map.insert("remount", MsFlags::MS_REMOUNT); + option_map.insert("ro", MsFlags::MS_RDONLY); + option_map.insert("rw", MsFlags::MS_RDONLY); + option_map.insert("suid", MsFlags::MS_NOSUID); + option_map.insert("sync", MsFlags::MS_SYNCHRONOUS); + option_map + } + + fn add_flag(&mut self, f: &str) -> MountResult<()> { + let flg = self.option_map.get(f).ok_or(MountError::UnsupportedOption)?; + self.flag.insert(*flg); + Ok(()) + } + + fn get_flag(options: &OsString) -> MountResult { + let s = options + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?; + let mut options: Vec<&str> = s.split(",").collect(); + let mut flag = Flag::default(); + if options.contains(&"default") { + options.extend_from_slice(&["rw", "suid", "dev", "exec", "auto", "nouser", "async"]); + } + for opt in options { + if opt == "" { + continue; + } + flag.add_flag(opt)?; + } + Ok(flag.flag) + } +} + +struct MntEnt { + mnt_fsname: OsString, + mnt_dir: OsString, + mnt_type: OsString, + mnt_opts: OsString, +} + +// This is used to read Filesystem Description File +// e.g. /etc/fstab and /etc/mtab +struct FSDescFile { + list: Vec, +} + +impl FSDescFile { + fn new(path: &str) -> MountResult { + // all of these files should exist and can be read, but just in case + let file = fs::File::open(path).or(Err(MountError::OpenFileError(String::from(path))))?; + let mut list = Vec::new(); + for line in BufReader::new(file).lines() { + let line = line.or(Err(MountError::FSDescFileFormatError(String::from(path))))?; + match line.chars().next() { + None | Some('#') => { + continue; + } + Some(_) => {} + } + let mut iter = line.split_whitespace(); + let mnt_fsname = iter + .next() + .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; + let mnt_dir = iter + .next() + .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; + let mnt_type = iter + .next() + .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; + let mnt_opts = iter + .next() + .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; + let mnt = MntEnt { + mnt_fsname: OsString::from(mnt_fsname), + mnt_dir: OsString::from(mnt_dir), + mnt_type: OsString::from(mnt_type), + mnt_opts: OsString::from(mnt_opts), + }; + list.push(mnt) + } + Ok(Self { list: list }) + } + fn print(&self, fs_type: OsString, output: &mut O) -> MountResult<()> { + for item in &self.list { + if fs_type != "" { + if fs_type != item.mnt_type { + continue; + } + } + writeln!( + output, + "{} on {} type {} ({})", + item.mnt_fsname + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?, + item.mnt_dir + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?, + item.mnt_type + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?, + item.mnt_opts + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))? + )?; + } + Ok(()) + } +} + +// Define some special requirements +struct Property { + fake: bool, + // TODO user mountable devices +} + +impl Default for Property { + fn default() -> Self { + Self { fake: false } + } +} + +// OsString has limited methods, implement them +trait OsStringExtend { + fn starts_with(&self, pat: &str) -> MountResult; + fn contains(&self, pat: &str) -> MountResult; +} + +impl OsStringExtend for OsString { + fn starts_with(&self, pat: &str) -> MountResult { + Ok(self + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))? + .starts_with(pat)) + } + fn contains(&self, pat: &str) -> MountResult { + Ok(self + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))? + .contains(pat)) + } +} + +// -a and -F are parsed at the very beginning +impl<'a> MountCommand { + fn new(setup: &'a mut S, matches: &clap::ArgMatches) -> MountResult { + let mut mount_list: Vec> = Vec::new(); + let multi_thread = if matches.is_present("F") { true } else { false }; + // If -a exists, mount all the entries in /etc/fstab, except for those who contain "noauto" + if matches.is_present("a") { + let fstab = FSDescFile::new("/etc/fstab")?; + for item in fstab.list { + if item.mnt_opts.contains("noauto")? { + continue; + } + // In this case, all the mounts are of type "CreateMountPoint" + let m = CreateMountPoint::new( + // TODO detect user mountable devices + Property::default(), + item.mnt_fsname, + PathBuf::from(item.mnt_dir), + item.mnt_type, + item.mnt_opts, + OsString::from(""), + )?; + mount_list.push(Box::new(m)); + } + } + // If -a dosn't exist, read arguments from command line, and find out the mount type + else { + let (_, output, _) = setup.stdio(); + let mut stdout = output.lock()?; + let mut arg1 = match matches.value_of("arg1") { + Some(arg1) => OsString::from(arg1), + None => OsString::from(""), + }; + let mut arg2 = match matches.value_of("arg2") { + Some(arg2) => OsString::from(arg2), + None => OsString::from(""), + }; + let fs_type = match matches.value_of("t") { + Some(t) => OsString::from(t), + None => OsString::from(""), + }; + let options: Vec<&str> = match matches.values_of("o") { + Some(t) => t.collect(), + None => Vec::new(), + }; + let fake = if matches.is_present("f") { true } else { false }; + let property = Property { fake: fake }; + // We can use UUID as source + if let Some(uuid) = matches.value_of("U") { + arg2 = arg1; + arg1 = OsString::from(String::from("UUID=") + uuid); + } + // We can use Label as source + if let Some(uuid) = matches.value_of("L") { + arg2 = arg1; + arg1 = OsString::from(String::from("Label=") + uuid); + } + // no argument + if arg1 == "" { + let mut m = ShowMountPoints::new(fs_type, &mut stdout); + m.run()?; + } + // one argument + else if arg1 != "" && arg2 == "" { + if options.contains(&"remount") { + let m = Remount::new( + property, + arg1, + OsString::from(options.join(",")), + OsString::from(""), + ); + mount_list.push(Box::new(m)); + } else { + let fstab = FSDescFile::new("/etc/fstab")?; + for item in fstab.list { + if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { + let m = CreateMountPoint::new( + property, + item.mnt_fsname, + PathBuf::from(item.mnt_dir), + item.mnt_type, + item.mnt_opts, + OsString::from(""), + )?; + mount_list.push(Box::new(m)); + break; + } + } + if mount_list.len() == 0 { + return Err(MountError::InvalidArgument); + } + } + } + // two argument + else { + let m = CreateMountPoint::new( + property, + arg1, + PathBuf::from(arg2), + fs_type, + OsString::from(options.join(",")), + OsString::from(""), + )?; + mount_list.push(Box::new(m)); + } + } + Ok(Self { + multi_thread: multi_thread, + mount_list: mount_list, + }) + } + fn run(mut self) -> MountResult<()> { + if self.multi_thread { + let mut handle = vec![]; + for mut m in self.mount_list { + handle.push(thread::spawn(move || m.run())); + } + for h in handle { + if let Err(_) = h.join() { + return Err(MountError::InvalidArgument); + } + } + } else { + for m in &mut self.mount_list { + m.run()?; + } + } + Ok(()) + } +} + +/* + * Show mount points from /proc/mounts + * If -t is specified, only output mount points of this file system type + * Usage examples: "mount", "mount -t ext4" + */ +struct ShowMountPoints<'a, O: Write + 'a> { + stdout: &'a mut O, + filesystem_type: OsString, +} + +impl<'a, O: Write> ShowMountPoints<'a, O> { + fn new(filesystem_type: OsString, stdout: &'a mut O) -> Self { + Self { + stdout: stdout, + filesystem_type: filesystem_type, + } + } +} + +impl<'a, O: Write> Mount for ShowMountPoints<'a, O> { + fn run(&mut self) -> MountResult<()> { + FSDescFile::new("/proc/mounts")?.print(self.filesystem_type.clone(), self.stdout)?; + Ok(()) + } +} + +/* + * Create a new mount point + * Usage examples: "mount /dev/sda1 /home/username/mnt" + */ +struct CreateMountPoint { + property: Property, + source: PathBuf, + target: PathBuf, + filesystem_type: OsString, + mountflags: OsString, + data: OsString, +} + +impl CreateMountPoint { + fn new( + property: Property, + source: OsString, + target: PathBuf, + filesystem_type: OsString, + mountflags: OsString, + data: OsString, + ) -> MountResult { + Ok(Self { + property, + source: Self::parse_source(source)?, + target, + filesystem_type, + mountflags, + data, + }) + } + fn parse_source(source: OsString) -> MountResult { + if source.starts_with("UUID=")? { + let uuid = Uuid::new()?; + Ok(PathBuf::from(uuid.to_dev(source)?)) + } else if source.starts_with("Label=")? { + let label = Label::new()?; + Ok(PathBuf::from(label.to_dev(source)?)) + } else { + Ok(PathBuf::from(source)) + } + } +} + +impl Mount for CreateMountPoint { + fn run(&mut self) -> MountResult<()> { + // check privilege + // getuid() is always successful, so it's ok to use it + if unsafe { libc::getuid() } != 0 { + return Err(MountError::PermissionDenied); + } + // check if mount point exists + if !self.target.exists() { + return Err(MountError::MountPointNotExist(String::from( + self.target.to_str().ok_or(MountError::InvalidCharError)?, + ))); + } + // check if device exists + if !self.source.exists() { + let s = String::from(self.source.to_str().ok_or(MountError::InvalidCharError)?); + let t = String::from(self.target.to_str().ok_or(MountError::InvalidCharError)?); + return Err(MountError::DeviceNotExist(t, s)); + } + // if type is not specified, auto detect filesystem type + if self.filesystem_type == "" || self.filesystem_type == "auto" { + let file_name = "/proc/filesystems"; + let file = match fs::File::open(file_name) { + Ok(f) => f, + Err(_) => { + // this file should always exist, but just in case + return Err(MountError::OpenFileError(String::from(file_name))); + } + }; + for line in BufReader::new(file).lines() { + let line = line?; + match line.chars().next() { + Some(line) => { + if !line.is_whitespace() { + continue; // skip nodev devices + } + } + None => { + continue; // skip empty lines + } + } + let try_fs_type = &line[1..]; + let mountflags = Flag::get_flag(&self.mountflags)?; + match nix::mount::mount( + Some(self.source.as_os_str()), + self.target.as_os_str(), + Some(try_fs_type), + mountflags, + Some(self.data.as_os_str()), + ).or(Err(MountError::from(io::Error::last_os_error()))) + { + Ok(_) => return Ok(()), + Err(_) => { /*println!("get error number: {}", nix::errno::errno());*/ } + } + } + return Err(MountError::UnsupportedFSType); + } else { + // if type is specified + let mountflags = Flag::get_flag(&self.mountflags)?; + if !self.property.fake { + match nix::mount::mount( + Some(self.source.as_os_str()), + self.target.as_os_str(), + Some(self.filesystem_type.as_os_str()), + mountflags, + Some(self.data.as_os_str()), + ).or(Err(MountError::from(io::Error::last_os_error()))) + { + Ok(_) => return Ok(()), + Err(_) => { + // errno == 19 means "No such device" + // this happens if you provide a wrong filesystem type + if nix::errno::errno() == 19 { + return Err(MountError::UnknownFSType( + self.filesystem_type + .clone() + .into_string() + .or(Err(MountError::InvalidCharError))?, + )); + } + return Err(MountError::from(io::Error::last_os_error())); + } + } + } + Ok(()) + } + } +} + +/* + * Remount an existing mount + * Usage examples: "mount -o remount /home/username/mnt" + */ +struct Remount { + property: Property, + target: PathBuf, + mountflags: OsString, + data: OsString, +} + +impl Remount { + fn new(property: Property, target: OsString, mountflags: OsString, data: OsString) -> Self { + Self { + property, + target: PathBuf::from(target), + mountflags, + data, + } + } +} + +impl Mount for Remount { + fn run(&mut self) -> MountResult<()> { + // check privilege + // getuid() is always successful, so it's ok to use it + if unsafe { libc::getuid() } != 0 { + return Err(MountError::PermissionDenied); + } + let mounts = FSDescFile::new("/proc/mounts")?; + let mut source = OsString::new(); + let mut target = OsString::new(); + let mut filesystem_type = OsString::new(); + for item in mounts.list.iter().rev() { + if self.target == item.mnt_fsname || self.target == item.mnt_dir { + source = item.mnt_fsname.clone(); + target = item.mnt_dir.clone(); + filesystem_type = item.mnt_type.clone(); + break; + } + } + let mountflags = Flag::get_flag(&self.mountflags)?; + if !self.property.fake { + nix::mount::mount( + Some(source.as_os_str()), + target.as_os_str(), + Some(filesystem_type.as_os_str()), + mountflags, + Some(self.data.as_os_str()), + ).or(Err(MountError::from(io::Error::last_os_error())))? + } + Ok(()) + } +} + +type MountResult = ::std::result::Result; + +pub fn execute(setup: &mut S, args: T) -> Result<()> +where + S: UtilSetup, + T: ArgsIter, +{ + let matches = { + let app = util_app!(NAME) + .author("Zhuohua Li ") + .arg(Arg::with_name("arg1") + .index(1)) + .arg(Arg::with_name("arg2") + .index(2)) + .arg(Arg::with_name("v") + .short("v") + .help("invoke verbose mode. The mount command shall provide diagnostic messages on stdout.")) + .arg(Arg::with_name("a") + .short("a") + .help("mount all file systems (of the given types) mentioned in /etc/fstab.")) + .arg(Arg::with_name("F") + .short("F") + .requires("a") + .help("If the -a option is also present, fork a new incarnation of mount for each device to be mounted. This will do the mounts on different devices or different NFS servers in parallel.")) + .arg(Arg::with_name("f") + .short("f") + .help("cause everything to be done except for the actual system call; if it's not obvious, this `fakes' mounting the file system.")) + // FIXME not supported yet + .arg(Arg::with_name("n") + .short("n") + .help("mount without writing in /etc/mtab. This is necessary for example when /etc is on a read-only file system.")) + // FIXME not supported yet + .arg(Arg::with_name("s") + .short("s") + .help("ignore mount options not supported by a file system type. Not all file systems support this option.")) + .arg(Arg::with_name("r") + .short("r") + .conflicts_with("w") + .help("mount the file system read-only. A synonym is -o ro.")) + .arg(Arg::with_name("w") + .short("w") + .conflicts_with("r") + .help("mount the file system read/write. (default) A synonym is -o rw.")) + .arg(Arg::with_name("L") + .short("L") + .help("If the file /proc/partitions is supported, mount the partition that has the specified label.") + .takes_value(true) + .conflicts_with("U") + .value_name("label")) + .arg(Arg::with_name("U") + .short("U") + .help("If the file /proc/partitions is supported, mount the partition that has the specified uuid.") + .takes_value(true) + .conflicts_with("L") + .value_name("uuid")) + .arg(Arg::with_name("t") + .short("t") + .help("indicate a file system type of vfstype.{n}{n}More than one type may be specified in a comma separated list. The list of file system types can be prefixed with no to specify the file system types on which no action should be taken.") + .takes_value(true) + .value_name("vfstype")) + .arg(Arg::with_name("o") + .short("o") + .help("options are specified with a -o flag followed by a comma-separated string of options. Some of these options are only useful when they appear in the /etc/fstab file. The following options apply to any file system that is being mounted:{n}{n}async{n}\tperform all I/O to the file system asynchronously.{n}{n}atime{n}\tupdate inode access time for each access. (default){n}{n}auto{n}\tin /etc/fstab, indicate the device is mountable with -a.{n}{n}defaults{n}\tuse default options: rw, suid, dev, exec, auto, nouser, async.{n}{n}dev{n}\tinterpret character or block special devices on the file system.{n}{n}exec{n}\tpermit execution of binaries.{n}{n}noatime{n}\tdo not update file access times on this file system.{n}{n}noauto{n}\tin /etc/fstab, indicates the device is only explicitly mountable.{n}{n}nodev{n}\tdo not interpret character or block special devices on the file system.{n}{n}noexec{n}\tdo not allow execution of any binaries on the mounted file system.{n}{n}nosuid{n}\tdo not allow set-user-identifier or set-group-identifier bits to take effect.{n}{n}nouser{n}\tforbid an unprivileged user to mount the file system. (default){n}{n}remount{n}\tremount an already-mounted file system. This is commonly used to change the mount options for a file system, especially to make a read-only file system writable.{n}{n}ro{n}\tmount the file system read-only.{n}{n}rw{n}\tmount the file system read-write.{n}{n}suid{n}\tallow set-user-identifier or set-group-identifier bits to take effect.{n}{n}sync{n}\tdo all I/O to the file system synchronously.{n}{n}user{n}\tallow an unprivilieged user to mount the file system. This option implies the options noexec, nosuid, nodev unless overridden by subsequent options.") + .takes_value(true) + .value_name("options") + .use_delimiter(true) + .possible_values(&["async", "atime", "defaults", "dev", "exec", "noatime", "nodev", "noexec", "nosuid", "nouser", "remount", "ro", "rw", "suid", "sync", "user"]) + .hide_possible_values(true)); + + app.get_matches_from_safe(args)? + }; + + let mount_command = MountCommand::new(setup, &matches)?; + Ok(mount_command.run()?) +} diff --git a/libmesabox/src/util_list.rs b/libmesabox/src/util_list.rs index fab9b1f..77cfe85 100644 --- a/libmesabox/src/util_list.rs +++ b/libmesabox/src/util_list.rs @@ -9,6 +9,7 @@ generate_fns! { (getty, "getty") }, lsb { + (mount, "mount"), (tar, "tar_util") }, networking { From 37611f4869be32df11554142b99ffa4a4c791f16 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Tue, 21 Aug 2018 19:17:28 -0700 Subject: [PATCH 02/15] lsb/mount: add tests --- tests/fixtures/mount/dev.img | Bin 0 -> 131072 bytes tests/lsb/mount.rs | 235 +++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 tests/fixtures/mount/dev.img create mode 100644 tests/lsb/mount.rs diff --git a/tests/fixtures/mount/dev.img b/tests/fixtures/mount/dev.img new file mode 100644 index 0000000000000000000000000000000000000000..72356d526398f3f27ee96c457f5cf6a8574948d0 GIT binary patch literal 131072 zcmeI*&ud&o902gy%~ndRv|1?^0!FkK1KHYx2rXz)p}Dk&(jS<^lA5=4+x-E%n~*|~ z*uTJopuGr+UfP46B6yJKNiQOT;7Lz{DB>aFP1bpvY}~F*=@vGrywAYwzWMQH-hAGC zACEwi5F`QwvMO*OE_@~Chhn}Gb0Owj%v)(P9>tvYTUcG5U5xpe(CM80BiHRWd2V?& zHg!|)yoY0HCFU1m{xToJ)i~&rpZv9c`n^+gpTF_V^C!>WJQ162OU28LT2ic5iWeHq ztHpAqK36R*Ce2o*Q7_h#*4(AWQn8)1+O62O)Lv*NtzxaQP;VD&_4aK8-KB8w)9Ytn zd-j#DZ_G^mx;pX2axT0S_HFj>U&mdB7`mxv(tlzhR$l6_a7e!`ro~CqUr;A<*gU2^0Tb>PklMA`DC&3 zdNSQ!y5|@1IF3Ys!0rjeSpM<+-yhGUao!*Qzl?3f|L$FxnF$aWwLlvG-(P(7vvi(6 z(;osz1PEkCAdUZ9mA6i(3l{&Gx$PE8perE$Z3F_@6%hZ~z4um5K>XVS1hOk2{G#GExy}7I#DD``9~~$U7Wcg~ShKU`pMTrg`37e@ za%iw-XUlJwLs(0nKSx9O@BPn*C%TujxBEHx{E2@*E$r#G-2cb5{ULl9_unINh?^G& zpMSmmxBc_)$UROy)qL#TJ9V91zf-NcED4-Ss@2Big+{YF|I~Pv7H+Ks2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly mK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB=E}6!-@Y1C|Z| literal 0 HcmV?d00001 diff --git a/tests/lsb/mount.rs b/tests/lsb/mount.rs new file mode 100644 index 0000000..1e41774 --- /dev/null +++ b/tests/lsb/mount.rs @@ -0,0 +1,235 @@ +// +// Copyright (c) 2018, The MesaLock Linux Project Contributors +// All rights reserved. +// +// This work is licensed under the terms of the BSD 3-Clause License. +// For a copy, see the LICENSE file. +// + +// NOTE: some ignored tests need root privilege, and you must run these tests in serial +// use command `sudo cargo test root_test_mount -- --ignored --test-threads 1` + +use assert_cmd::prelude::*; +use predicates::prelude::*; +use std::fs; +use std::io::{BufRead, BufReader, Read}; +use std::process::Command; + +const NAME: &str = "mount"; + +struct MntEnt { + mnt_fsname: String, + mnt_dir: String, + mnt_type: String, + mnt_opts: String, +} + +#[test] +fn test_mount_no_arg() { + let file = fs::File::open("/proc/mounts").unwrap(); + let mut list = Vec::new(); + for line in BufReader::new(file).lines() { + let line = line.unwrap(); + match line.chars().next() { + None | Some('#') => continue, + Some(_) => {} + } + let mut iter = line.split_whitespace(); + let mnt_fsname = iter.next().unwrap(); + let mnt_dir = iter.next().unwrap(); + let mnt_type = iter.next().unwrap(); + let mnt_opts = iter.next().unwrap(); + let mnt = MntEnt { + mnt_fsname: String::from(mnt_fsname), + mnt_dir: String::from(mnt_dir), + mnt_type: String::from(mnt_type), + mnt_opts: String::from(mnt_opts), + }; + list.push(mnt) + } + + let mut output = String::new(); + for item in &list { + if item.mnt_type != "ext4" { + continue; + } + let s = format!( + "{} on {} type {} ({})", + item.mnt_fsname, item.mnt_dir, item.mnt_type, item.mnt_opts + ); + output.push_str((s + "\n").as_str()); + } + new_cmd!() + .args(&["-t", "ext4"]) + .assert() + .success() + .stdout(predicate::str::contains(output).from_utf8()) + .stderr(""); +} + +#[test] +fn test_mount_without_root() { + new_cmd!() + .current_dir(fixtures_dir!()) + .args(&["/dev/loop0", "mnt"]) + .assert() + .failure() + .stdout("") + .stderr("mount: Only root can do that\n"); +} + +#[test] +#[ignore] +fn root_test_mount_nonexistent_dev() { + new_cmd!() + .current_dir(fixtures_dir!()) + .args(&["/dev/this_device_should_not_exist", "mnt"]) + .assert() + .failure() + .stdout("") + .stderr( + predicate::str::contains( + "special device /dev/this_device_should_not_exist does not exist.", + ).from_utf8(), + ); +} + +#[test] +#[ignore] +fn root_test_mount_nonexistent_mount_point() { + new_cmd!() + .current_dir(fixtures_dir!()) + .args(&["/dev/loop0", "this_target_should_not_exist"]) + .assert() + .failure() + .stdout("") + .stderr(predicate::str::contains("mount point does not exist.").from_utf8()); +} + +#[test] +#[ignore] +fn root_test_mount_unknown_filesystem_type() { + new_cmd!() + .current_dir(fixtures_dir!()) + .args(&[ + "-t", + "this_filesystem_type_should_not_exist", + "/dev/loop0", + "mnt", + ]) + .assert() + .failure() + .stdout("") + .stderr( + predicate::str::contains( + "unknown filesystem type 'this_filesystem_type_should_not_exist'.", + ).from_utf8(), + ); +} + +#[test] +#[ignore] +fn root_test_mount_unknown_uuid() { + new_cmd!() + .current_dir(fixtures_dir!()) + .args(&["-U", "this_uuid_should_not_exist", "mnt"]) + .assert() + .failure() + .stdout("") + .stderr( + predicate::str::contains("Cannot find UUID=\"this_uuid_should_not_exist\"").from_utf8(), + ); +} + +#[test] +#[ignore] +fn root_test_mount_create_mount_point() { + Command::new("losetup") + .current_dir(fixtures_dir!()) + .args(&["/dev/loop0", "dev.img"]) + .assert() + .success() + .stdout("") + .stderr(""); + + new_cmd!() + .current_dir(fixtures_dir!()) + .args(&["/dev/loop0", "mnt"]) + .assert() + .success(); + + let mut path = fixtures_dir!(); + path.push("mnt/read_file_from_device.txt"); + let mut file = fs::File::open(path).unwrap(); + let mut contents = String::new(); + file.read_to_string(&mut contents).unwrap(); + assert_eq!(contents, "Hello World!\n"); + drop(file); + + Command::new("umount") + .current_dir(fixtures_dir!()) + .args(&["mnt"]) + .assert() + .success() + .stdout("") + .stderr(""); + + Command::new("losetup") + .current_dir(fixtures_dir!()) + .args(&["-d", "/dev/loop0"]) + .assert() + .success() + .stdout("") + .stderr(""); +} + +#[test] +#[ignore] +fn root_test_mount_remount() { + Command::new("losetup") + .current_dir(fixtures_dir!()) + .args(&["/dev/loop0", "dev.img"]) + .assert() + .success() + .stdout("") + .stderr(""); + + new_cmd!() + .current_dir(fixtures_dir!()) + // mount it as read-write + .args(&["-o", "rw", "/dev/loop0", "mnt"]) + .assert() + .success(); + + new_cmd!() + .current_dir(fixtures_dir!()) + // then remount it as read-only + .args(&["-o", "remount,ro", "/dev/loop0", "mnt"]) + .assert() + .success(); + + let mut path = fixtures_dir!(); + path.push("mnt/create_new_file.txt"); + match fs::File::create(path) { + // This operation should not succeed because it's read-only + Ok(_) => assert!(false), + // OS error 30 means "Read-only file system" + Err(e) => assert_eq!(e.raw_os_error(), Some(30)), + } + + Command::new("umount") + .current_dir(fixtures_dir!()) + .args(&["mnt"]) + .assert() + .success() + .stdout("") + .stderr(""); + + Command::new("losetup") + .current_dir(fixtures_dir!()) + .args(&["-d", "/dev/loop0"]) + .assert() + .success() + .stdout("") + .stderr(""); +} From b6de5b3b4e05f6653f2cfc85eb71a7d7c99b5b17 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Tue, 21 Aug 2018 22:22:56 -0700 Subject: [PATCH 03/15] lsb/mount: fix compile error on stable --- libmesabox/src/lsb/mount/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index c78c1fa..0f4343e 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -280,7 +280,7 @@ impl FSDescFile { } fn print(&self, fs_type: OsString, output: &mut O) -> MountResult<()> { for item in &self.list { - if fs_type != "" { + if fs_type != *"" { if fs_type != item.mnt_type { continue; } @@ -403,12 +403,12 @@ impl<'a> MountCommand { arg1 = OsString::from(String::from("Label=") + uuid); } // no argument - if arg1 == "" { + if arg1 == *"" { let mut m = ShowMountPoints::new(fs_type, &mut stdout); m.run()?; } // one argument - else if arg1 != "" && arg2 == "" { + else if arg1 != *"" && arg2 == *"" { if options.contains(&"remount") { let m = Remount::new( property, @@ -566,7 +566,7 @@ impl Mount for CreateMountPoint { return Err(MountError::DeviceNotExist(t, s)); } // if type is not specified, auto detect filesystem type - if self.filesystem_type == "" || self.filesystem_type == "auto" { + if self.filesystem_type == *"" || self.filesystem_type == *"auto" { let file_name = "/proc/filesystems"; let file = match fs::File::open(file_name) { Ok(f) => f, From 1a47d9f652a5bf59a3fed746afe4ad52a6a562ca Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Wed, 22 Aug 2018 15:02:28 -0700 Subject: [PATCH 04/15] lsb/mount: use to_string_lossy method to eliminate invalid Unicode error --- libmesabox/src/lsb/mount/mod.rs | 81 ++++++++++----------------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index 0f4343e..3233182 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -52,8 +52,6 @@ enum MountError { MountPointNotExist(String), #[fail(display = "{}: special device {} does not exist.", _0, _1)] DeviceNotExist(String, String), - #[fail(display = "Invalid UTF-8 characters")] - InvalidCharError, // Denotes an error caused by one of stdin, stdout, or stderr failing to lock #[fail(display = "{}", _0)] Lock(#[cause] LockError), @@ -106,10 +104,8 @@ impl Uuid { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "UUID=" let dir; - let s = input - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?; + let s = input.clone(); + let s = s.to_string_lossy(); if s.starts_with("UUID=") { dir = OsString::from(&s[5..]); } else { @@ -148,10 +144,8 @@ impl Label { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "Label=" let dir; - let s = input - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?; + let s = input.clone(); + let s = s.to_string_lossy(); if s.starts_with("Label=") { dir = OsString::from(&s[6..]); } else { @@ -210,10 +204,8 @@ impl Flag { } fn get_flag(options: &OsString) -> MountResult { - let s = options - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?; + let s = options.clone(); + let s = s.to_string_lossy(); let mut options: Vec<&str> = s.split(",").collect(); let mut flag = Flag::default(); if options.contains(&"default") { @@ -288,22 +280,10 @@ impl FSDescFile { writeln!( output, "{} on {} type {} ({})", - item.mnt_fsname - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?, - item.mnt_dir - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?, - item.mnt_type - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?, - item.mnt_opts - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))? + item.mnt_fsname.to_string_lossy(), + item.mnt_dir.to_string_lossy(), + item.mnt_type.to_string_lossy(), + item.mnt_opts.to_string_lossy(), )?; } Ok(()) @@ -324,24 +304,16 @@ impl Default for Property { // OsString has limited methods, implement them trait OsStringExtend { - fn starts_with(&self, pat: &str) -> MountResult; - fn contains(&self, pat: &str) -> MountResult; + fn starts_with(&self, pat: &str) -> bool; + fn contains(&self, pat: &str) -> bool; } impl OsStringExtend for OsString { - fn starts_with(&self, pat: &str) -> MountResult { - Ok(self - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))? - .starts_with(pat)) + fn starts_with(&self, pat: &str) -> bool { + self.to_string_lossy().starts_with(pat) } - fn contains(&self, pat: &str) -> MountResult { - Ok(self - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))? - .contains(pat)) + fn contains(&self, pat: &str) -> bool { + self.to_string_lossy().contains(pat) } } @@ -354,7 +326,7 @@ impl<'a> MountCommand { if matches.is_present("a") { let fstab = FSDescFile::new("/etc/fstab")?; for item in fstab.list { - if item.mnt_opts.contains("noauto")? { + if item.mnt_opts.contains("noauto") { continue; } // In this case, all the mounts are of type "CreateMountPoint" @@ -534,10 +506,10 @@ impl CreateMountPoint { }) } fn parse_source(source: OsString) -> MountResult { - if source.starts_with("UUID=")? { + if source.starts_with("UUID=") { let uuid = Uuid::new()?; Ok(PathBuf::from(uuid.to_dev(source)?)) - } else if source.starts_with("Label=")? { + } else if source.starts_with("Label=") { let label = Label::new()?; Ok(PathBuf::from(label.to_dev(source)?)) } else { @@ -556,13 +528,13 @@ impl Mount for CreateMountPoint { // check if mount point exists if !self.target.exists() { return Err(MountError::MountPointNotExist(String::from( - self.target.to_str().ok_or(MountError::InvalidCharError)?, + self.target.to_string_lossy(), ))); } // check if device exists if !self.source.exists() { - let s = String::from(self.source.to_str().ok_or(MountError::InvalidCharError)?); - let t = String::from(self.target.to_str().ok_or(MountError::InvalidCharError)?); + let s = String::from(self.source.to_string_lossy()); + let t = String::from(self.target.to_string_lossy()); return Err(MountError::DeviceNotExist(t, s)); } // if type is not specified, auto detect filesystem type @@ -619,12 +591,9 @@ impl Mount for CreateMountPoint { // errno == 19 means "No such device" // this happens if you provide a wrong filesystem type if nix::errno::errno() == 19 { - return Err(MountError::UnknownFSType( - self.filesystem_type - .clone() - .into_string() - .or(Err(MountError::InvalidCharError))?, - )); + return Err(MountError::UnknownFSType(String::from( + self.filesystem_type.to_string_lossy(), + ))); } return Err(MountError::from(io::Error::last_os_error())); } From 9ac55c2e041dc514e742d19fe9c00a3e3e6e76f9 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Wed, 22 Aug 2018 16:56:13 -0700 Subject: [PATCH 05/15] tests/lsb/mount: remove hardcode device image, now it is generated during tests --- tests/fixtures/mount/dev.img | Bin 131072 -> 0 bytes tests/lsb/mount.rs | 118 ++++++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 31 deletions(-) delete mode 100644 tests/fixtures/mount/dev.img diff --git a/tests/fixtures/mount/dev.img b/tests/fixtures/mount/dev.img deleted file mode 100644 index 72356d526398f3f27ee96c457f5cf6a8574948d0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131072 zcmeI*&ud&o902gy%~ndRv|1?^0!FkK1KHYx2rXz)p}Dk&(jS<^lA5=4+x-E%n~*|~ z*uTJopuGr+UfP46B6yJKNiQOT;7Lz{DB>aFP1bpvY}~F*=@vGrywAYwzWMQH-hAGC zACEwi5F`QwvMO*OE_@~Chhn}Gb0Owj%v)(P9>tvYTUcG5U5xpe(CM80BiHRWd2V?& zHg!|)yoY0HCFU1m{xToJ)i~&rpZv9c`n^+gpTF_V^C!>WJQ162OU28LT2ic5iWeHq ztHpAqK36R*Ce2o*Q7_h#*4(AWQn8)1+O62O)Lv*NtzxaQP;VD&_4aK8-KB8w)9Ytn zd-j#DZ_G^mx;pX2axT0S_HFj>U&mdB7`mxv(tlzhR$l6_a7e!`ro~CqUr;A<*gU2^0Tb>PklMA`DC&3 zdNSQ!y5|@1IF3Ys!0rjeSpM<+-yhGUao!*Qzl?3f|L$FxnF$aWwLlvG-(P(7vvi(6 z(;osz1PEkCAdUZ9mA6i(3l{&Gx$PE8perE$Z3F_@6%hZ~z4um5K>XVS1hOk2{G#GExy}7I#DD``9~~$U7Wcg~ShKU`pMTrg`37e@ za%iw-XUlJwLs(0nKSx9O@BPn*C%TujxBEHx{E2@*E$r#G-2cb5{ULl9_unINh?^G& zpMSmmxBc_)$UROy)qL#TJ9V91zf-NcED4-Ss@2Big+{YF|I~Pv7H+Ks2oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly mK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB=E}6!-@Y1C|Z| diff --git a/tests/lsb/mount.rs b/tests/lsb/mount.rs index 1e41774..a056fe2 100644 --- a/tests/lsb/mount.rs +++ b/tests/lsb/mount.rs @@ -10,8 +10,11 @@ // use command `sudo cargo test root_test_mount -- --ignored --test-threads 1` use assert_cmd::prelude::*; +use assert_fs; +use assert_fs::prelude::*; use predicates::prelude::*; use std::fs; +use std::io::prelude::*; use std::io::{BufRead, BufReader, Read}; use std::process::Command; @@ -69,9 +72,10 @@ fn test_mount_no_arg() { #[test] fn test_mount_without_root() { + let temp_dir = assert_fs::TempDir::new().unwrap(); + let mnt = temp_dir.path().to_str().unwrap(); new_cmd!() - .current_dir(fixtures_dir!()) - .args(&["/dev/loop0", "mnt"]) + .args(&["/dev/loop0", mnt]) .assert() .failure() .stdout("") @@ -81,9 +85,10 @@ fn test_mount_without_root() { #[test] #[ignore] fn root_test_mount_nonexistent_dev() { + let temp_dir = assert_fs::TempDir::new().unwrap(); + let mnt = temp_dir.path().to_str().unwrap(); new_cmd!() - .current_dir(fixtures_dir!()) - .args(&["/dev/this_device_should_not_exist", "mnt"]) + .args(&["/dev/this_device_should_not_exist", mnt]) .assert() .failure() .stdout("") @@ -98,7 +103,6 @@ fn root_test_mount_nonexistent_dev() { #[ignore] fn root_test_mount_nonexistent_mount_point() { new_cmd!() - .current_dir(fixtures_dir!()) .args(&["/dev/loop0", "this_target_should_not_exist"]) .assert() .failure() @@ -109,13 +113,14 @@ fn root_test_mount_nonexistent_mount_point() { #[test] #[ignore] fn root_test_mount_unknown_filesystem_type() { + let temp_dir = assert_fs::TempDir::new().unwrap(); + let mnt = temp_dir.path().to_str().unwrap(); new_cmd!() - .current_dir(fixtures_dir!()) .args(&[ "-t", "this_filesystem_type_should_not_exist", "/dev/loop0", - "mnt", + mnt, ]) .assert() .failure() @@ -130,9 +135,10 @@ fn root_test_mount_unknown_filesystem_type() { #[test] #[ignore] fn root_test_mount_unknown_uuid() { + let temp_dir = assert_fs::TempDir::new().unwrap(); + let mnt = temp_dir.path().to_str().unwrap(); new_cmd!() - .current_dir(fixtures_dir!()) - .args(&["-U", "this_uuid_should_not_exist", "mnt"]) + .args(&["-U", "this_uuid_should_not_exist", mnt]) .assert() .failure() .stdout("") @@ -144,38 +150,71 @@ fn root_test_mount_unknown_uuid() { #[test] #[ignore] fn root_test_mount_create_mount_point() { + let temp_dir = assert_fs::TempDir::new().unwrap(); + // let device_image = temp_dir.child("device.img"); + let mount_point = temp_dir.child("mnt"); + let mount_point_path = mount_point.path(); + //let device_image_path = device_image.path(); + // create mount point directory + fs::create_dir(mount_point_path).unwrap(); + // create device image + Command::new("dd") + .current_dir(&temp_dir) + .args(&["if=/dev/zero", "of=device.img", "bs=128", "count=1024"]) + .assert() + .success(); + + Command::new("mkfs.ext4") + .current_dir(&temp_dir) + .args(&["device.img"]) + .assert() + .success(); + + Command::new("mount") + .current_dir(&temp_dir) + .args(&["device.img", "mnt"]) + .assert() + .success(); + let new_file_path = mount_point_path.join("new_file.txt"); + let mut file = fs::File::create(new_file_path).unwrap(); + file.write_all(b"Hello World!").unwrap(); + drop(file); + + Command::new("umount") + .current_dir(&temp_dir) + .args(&["mnt"]) + .assert() + .success(); + Command::new("losetup") - .current_dir(fixtures_dir!()) - .args(&["/dev/loop0", "dev.img"]) + .current_dir(&temp_dir) + .args(&["/dev/loop0", "device.img"]) .assert() .success() .stdout("") .stderr(""); new_cmd!() - .current_dir(fixtures_dir!()) + .current_dir(&temp_dir) .args(&["/dev/loop0", "mnt"]) .assert() .success(); - let mut path = fixtures_dir!(); - path.push("mnt/read_file_from_device.txt"); + let path = temp_dir.path().join("mnt/new_file.txt"); let mut file = fs::File::open(path).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); - assert_eq!(contents, "Hello World!\n"); + assert_eq!(contents, "Hello World!"); drop(file); Command::new("umount") - .current_dir(fixtures_dir!()) + .current_dir(&temp_dir) .args(&["mnt"]) .assert() - .success() - .stdout("") - .stderr(""); + .success(); Command::new("losetup") - .current_dir(fixtures_dir!()) + .current_dir(&temp_dir) .args(&["-d", "/dev/loop0"]) .assert() .success() @@ -186,30 +225,47 @@ fn root_test_mount_create_mount_point() { #[test] #[ignore] fn root_test_mount_remount() { + let temp_dir = assert_fs::TempDir::new().unwrap(); + let mount_point = temp_dir.child("mnt"); + let mount_point_path = mount_point.path(); + // create mount point directory + fs::create_dir(mount_point_path).unwrap(); + // create device image + Command::new("dd") + .current_dir(&temp_dir) + .args(&["if=/dev/zero", "of=device.img", "bs=128", "count=1024"]) + .assert() + .success(); + + Command::new("mkfs.ext4") + .current_dir(&temp_dir) + .args(&["device.img"]) + .assert() + .success(); + Command::new("losetup") - .current_dir(fixtures_dir!()) - .args(&["/dev/loop0", "dev.img"]) + .current_dir(&temp_dir) + .args(&["/dev/loop1", "device.img"]) .assert() .success() .stdout("") .stderr(""); new_cmd!() - .current_dir(fixtures_dir!()) + .current_dir(&temp_dir) // mount it as read-write - .args(&["-o", "rw", "/dev/loop0", "mnt"]) + .args(&["-o", "rw", "/dev/loop1", "mnt"]) .assert() .success(); new_cmd!() - .current_dir(fixtures_dir!()) + .current_dir(&temp_dir) // then remount it as read-only - .args(&["-o", "remount,ro", "/dev/loop0", "mnt"]) + .args(&["-o", "remount,ro", "/dev/loop1", "mnt"]) .assert() .success(); - let mut path = fixtures_dir!(); - path.push("mnt/create_new_file.txt"); + let path = temp_dir.path().join("mnt/create_new_file.txt"); match fs::File::create(path) { // This operation should not succeed because it's read-only Ok(_) => assert!(false), @@ -218,7 +274,7 @@ fn root_test_mount_remount() { } Command::new("umount") - .current_dir(fixtures_dir!()) + .current_dir(&temp_dir) .args(&["mnt"]) .assert() .success() @@ -226,8 +282,8 @@ fn root_test_mount_remount() { .stderr(""); Command::new("losetup") - .current_dir(fixtures_dir!()) - .args(&["-d", "/dev/loop0"]) + .current_dir(&temp_dir) + .args(&["-d", "/dev/loop1"]) .assert() .success() .stdout("") From eecb068fba6326b8bb3290b2a64806d5aca940cb Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Thu, 23 Aug 2018 14:23:10 -0700 Subject: [PATCH 06/15] lsb/mount: use lazy_static to generate option map --- Cargo.lock | 1 + libmesabox/Cargo.toml | 3 ++- libmesabox/src/lib.rs | 3 +++ libmesabox/src/lsb/mount/mod.rs | 40 +++++++++++++++------------------ 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c03ee4c..7b2ed23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,6 +475,7 @@ dependencies = [ "glob 0.2.11 (git+https://github.com/mesalock-linux/glob)", "globset 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.15 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/libmesabox/Cargo.toml b/libmesabox/Cargo.toml index e5bb3da..806e80e 100644 --- a/libmesabox/Cargo.toml +++ b/libmesabox/Cargo.toml @@ -22,7 +22,7 @@ loginutils = [ ] # XXX: temporary until renaming dependencies is supported -mount = ["libc"] +mount = ["libc", "lazy_static"] tar_util = ["tar", "globset"] lsb = [ "mount", "tar" @@ -101,6 +101,7 @@ kernel32-sys = "0.2.2" winapi = { version = "0.3.5", features = ["namedpipeapi"] } nix = "0.10.0" +lazy_static = { version = "1.0.1", optional = true } libc = { version = "0.2.40", optional = true } platform-info = { version = "0.0.1", optional = true } uucore = { git = "https://github.com/uutils/coreutils", features = ["encoding", "fs", "mode", "parse_time"], optional = true } diff --git a/libmesabox/src/lib.rs b/libmesabox/src/lib.rs index 4ddef16..ca61f9a 100644 --- a/libmesabox/src/lib.rs +++ b/libmesabox/src/lib.rs @@ -31,6 +31,9 @@ extern crate fnv; extern crate globset; #[cfg(feature = "libc")] extern crate libc; +#[cfg(feature = "lazy_static")] +#[macro_use] +extern crate lazy_static; #[cfg(feature = "mio")] extern crate mio; #[cfg(feature = "pnet")] diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index 3233182..6575939 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -7,6 +7,7 @@ // extern crate clap; +extern crate lazy_static; extern crate libc; extern crate nix; use clap::Arg; @@ -159,28 +160,9 @@ impl Label { } } -struct Flag { - option_map: HashMap<&'static str, MsFlags>, - flag: MsFlags, -} - -impl Default for Flag { - fn default() -> Self { - Self { - option_map: Self::get_option_map(), - flag: MsFlags::MS_SILENT, - } - } -} - -impl Flag { - fn get_option_map() -> HashMap<&'static str, MsFlags> { +lazy_static! { + static ref OPTION_MAP: HashMap<&'static str, MsFlags> = { let mut option_map = HashMap::new(); - //option_map.insert("auto",MsFlags{bits: 0,}); // ignored - //option_map.insert("noauto", MsFlags{bits: 0,}); // ignored - //option_map.insert("defaults", MsFlags{bits: 0,}); // ignored - //option_map.insert("nouser", MsFlags{bits: 0,}); // ignored - //option_map.insert("user", MsFlags{bits: 0,}); // ignored option_map.insert("async", MsFlags::MS_SYNCHRONOUS); option_map.insert("atime", MsFlags::MS_NOATIME); option_map.insert("dev", MsFlags::MS_NODEV); @@ -195,10 +177,24 @@ impl Flag { option_map.insert("suid", MsFlags::MS_NOSUID); option_map.insert("sync", MsFlags::MS_SYNCHRONOUS); option_map + }; +} + +struct Flag { + flag: MsFlags, +} + +impl Default for Flag { + fn default() -> Self { + Self { + flag: MsFlags::MS_SILENT, + } } +} +impl Flag { fn add_flag(&mut self, f: &str) -> MountResult<()> { - let flg = self.option_map.get(f).ok_or(MountError::UnsupportedOption)?; + let flg = OPTION_MAP.get(f).ok_or(MountError::UnsupportedOption)?; self.flag.insert(*flg); Ok(()) } From b69888173177df680118b2ca9a076b20b683e669 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Tue, 4 Sep 2018 16:27:12 -0700 Subject: [PATCH 07/15] lsb/mount: fix the order of entries in Cargo.toml --- Cargo.toml | 2 +- libmesabox/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index fbe8363..e7a83c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ loginutils = [ "getty" ] +mount = ["libmesabox/mount"] # FIXME: this is only like this because we can't rename dependencies and we use the same macro in # the tests that libmesabox uses to build -mount = ["libmesabox/mount"] tar_util = ["libmesabox/tar_util"] lsb = [ "mount", "tar_util" diff --git a/libmesabox/Cargo.toml b/libmesabox/Cargo.toml index 806e80e..6662257 100644 --- a/libmesabox/Cargo.toml +++ b/libmesabox/Cargo.toml @@ -21,8 +21,8 @@ loginutils = [ "getty" ] -# XXX: temporary until renaming dependencies is supported mount = ["libc", "lazy_static"] +# XXX: temporary until renaming dependencies is supported tar_util = ["tar", "globset"] lsb = [ "mount", "tar" From 9751f15762e46125d762fe01a08b2b58cd992545 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Tue, 4 Sep 2018 16:28:17 -0700 Subject: [PATCH 08/15] lsb/mount: refactor code according to the dummy utility --- libmesabox/src/lsb/mount/mod.rs | 182 ++++++++++++++++++-------------- 1 file changed, 102 insertions(+), 80 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index 6575939..1001dd9 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -10,7 +10,7 @@ extern crate clap; extern crate lazy_static; extern crate libc; extern crate nix; -use clap::Arg; +use clap::{App, Arg}; use nix::mount::MsFlags; use std::collections::HashMap; use std::ffi::OsString; @@ -23,6 +23,8 @@ use {ArgsIter, LockError, Result, UtilSetup, UtilWrite}; pub(crate) const NAME: &str = "mount"; pub(crate) const DESCRIPTION: &str = "Mount file systems"; +type MountResult = ::std::result::Result; + #[derive(Debug, Fail)] enum MountError { #[fail(display = "{}", _0)] @@ -70,16 +72,19 @@ impl From for MountError { } } -// There are several types of mount, all of them implement Mount trait -trait Mount { - fn run(&mut self) -> MountResult<()>; +/// There are several types of mount task, all of them implement Mountable trait +trait Mountable { + fn run(&mut self) -> MountResult; } -struct MountCommand { +/// Store information that we need to execute a mount command +struct MountOptions { multi_thread: bool, - mount_list: Vec>, + // Mount command might mount a lot of devices at the same time, so we cache them in a list + mount_list: Vec>, } +/// Used to translate UUID into corresponding device path struct Uuid { // map UUID to actual path path_map: HashMap, @@ -120,6 +125,7 @@ impl Uuid { } } +/// Used to translate Label into corresponding device path struct Label { // map Label to actual path path_map: HashMap, @@ -224,8 +230,8 @@ struct MntEnt { mnt_opts: OsString, } -// This is used to read Filesystem Description File -// e.g. /etc/fstab and /etc/mtab +/// This is used to read Filesystem Description File +/// e.g. /etc/fstab and /etc/mtab struct FSDescFile { list: Vec, } @@ -266,27 +272,30 @@ impl FSDescFile { } Ok(Self { list: list }) } - fn print(&self, fs_type: OsString, output: &mut O) -> MountResult<()> { + + fn to_string(&self, fs_type: &OsString) -> MountResult { + let mut ret = String::new(); for item in &self.list { - if fs_type != *"" { - if fs_type != item.mnt_type { + if *fs_type != *"" { + if *fs_type != item.mnt_type { continue; } } - writeln!( - output, - "{} on {} type {} ({})", - item.mnt_fsname.to_string_lossy(), - item.mnt_dir.to_string_lossy(), - item.mnt_type.to_string_lossy(), - item.mnt_opts.to_string_lossy(), - )?; + ret.push_str( + format!( + "{} on {} type {} ({})\n", + item.mnt_fsname.to_string_lossy(), + item.mnt_dir.to_string_lossy(), + item.mnt_type.to_string_lossy(), + item.mnt_opts.to_string_lossy(), + ).as_str(), + ); } - Ok(()) + Ok(ret) } } -// Define some special requirements +/// Define some special requirements struct Property { fake: bool, // TODO user mountable devices @@ -298,7 +307,7 @@ impl Default for Property { } } -// OsString has limited methods, implement them +/// OsString has limited methods, implement them trait OsStringExtend { fn starts_with(&self, pat: &str) -> bool; fn contains(&self, pat: &str) -> bool; @@ -308,16 +317,17 @@ impl OsStringExtend for OsString { fn starts_with(&self, pat: &str) -> bool { self.to_string_lossy().starts_with(pat) } + fn contains(&self, pat: &str) -> bool { self.to_string_lossy().contains(pat) } } -// -a and -F are parsed at the very beginning -impl<'a> MountCommand { - fn new(setup: &'a mut S, matches: &clap::ArgMatches) -> MountResult { - let mut mount_list: Vec> = Vec::new(); +impl MountOptions { + fn from_matches(matches: &clap::ArgMatches) -> MountResult { + let mut mount_list: Vec> = Vec::new(); let multi_thread = if matches.is_present("F") { true } else { false }; + // -a and -F are parsed at the very beginning // If -a exists, mount all the entries in /etc/fstab, except for those who contain "noauto" if matches.is_present("a") { let fstab = FSDescFile::new("/etc/fstab")?; @@ -338,10 +348,8 @@ impl<'a> MountCommand { mount_list.push(Box::new(m)); } } - // If -a dosn't exist, read arguments from command line, and find out the mount type + // If -a doesn't exist, read arguments from command line, and find out the mount type else { - let (_, output, _) = setup.stdio(); - let mut stdout = output.lock()?; let mut arg1 = match matches.value_of("arg1") { Some(arg1) => OsString::from(arg1), None => OsString::from(""), @@ -372,8 +380,9 @@ impl<'a> MountCommand { } // no argument if arg1 == *"" { - let mut m = ShowMountPoints::new(fs_type, &mut stdout); - m.run()?; + let m = ShowMountPoints::new(fs_type); + // m.run()?; + mount_list.push(Box::new(m)); } // one argument else if arg1 != *"" && arg2 == *"" { @@ -424,10 +433,27 @@ impl<'a> MountCommand { mount_list: mount_list, }) } - fn run(mut self) -> MountResult<()> { - if self.multi_thread { +} + +struct Mounter +where + O: Write, +{ + output: O, +} + +impl Mounter +where + O: Write, +{ + fn new(output: O) -> Self { + Mounter { output } + } + + fn mount(&mut self, mut options: MountOptions) -> MountResult<()> { + if options.multi_thread { let mut handle = vec![]; - for mut m in self.mount_list { + for mut m in options.mount_list { handle.push(thread::spawn(move || m.run())); } for h in handle { @@ -436,44 +462,37 @@ impl<'a> MountCommand { } } } else { - for m in &mut self.mount_list { - m.run()?; + for m in &mut options.mount_list { + write!(self.output, "{}", m.run()?); } } Ok(()) } } -/* - * Show mount points from /proc/mounts - * If -t is specified, only output mount points of this file system type - * Usage examples: "mount", "mount -t ext4" - */ -struct ShowMountPoints<'a, O: Write + 'a> { - stdout: &'a mut O, +/// Show mount points from /proc/mounts +/// If -t is specified, only output mount points of this file system type +/// Usage examples: "mount", "mount -t ext4" +struct ShowMountPoints { filesystem_type: OsString, } -impl<'a, O: Write> ShowMountPoints<'a, O> { - fn new(filesystem_type: OsString, stdout: &'a mut O) -> Self { +impl ShowMountPoints { + fn new(filesystem_type: OsString) -> Self { Self { - stdout: stdout, filesystem_type: filesystem_type, } } } -impl<'a, O: Write> Mount for ShowMountPoints<'a, O> { - fn run(&mut self) -> MountResult<()> { - FSDescFile::new("/proc/mounts")?.print(self.filesystem_type.clone(), self.stdout)?; - Ok(()) +impl Mountable for ShowMountPoints { + fn run(&mut self) -> MountResult { + FSDescFile::new("/proc/mounts")?.to_string(&self.filesystem_type) } } -/* - * Create a new mount point - * Usage examples: "mount /dev/sda1 /home/username/mnt" - */ +/// Create a new mount point +/// Usage examples: "mount /dev/sda1 /home/username/mnt" struct CreateMountPoint { property: Property, source: PathBuf, @@ -501,6 +520,7 @@ impl CreateMountPoint { data, }) } + fn parse_source(source: OsString) -> MountResult { if source.starts_with("UUID=") { let uuid = Uuid::new()?; @@ -514,8 +534,8 @@ impl CreateMountPoint { } } -impl Mount for CreateMountPoint { - fn run(&mut self) -> MountResult<()> { +impl Mountable for CreateMountPoint { + fn run(&mut self) -> MountResult { // check privilege // getuid() is always successful, so it's ok to use it if unsafe { libc::getuid() } != 0 { @@ -565,7 +585,7 @@ impl Mount for CreateMountPoint { Some(self.data.as_os_str()), ).or(Err(MountError::from(io::Error::last_os_error()))) { - Ok(_) => return Ok(()), + Ok(_) => return Ok(String::new()), Err(_) => { /*println!("get error number: {}", nix::errno::errno());*/ } } } @@ -582,7 +602,7 @@ impl Mount for CreateMountPoint { Some(self.data.as_os_str()), ).or(Err(MountError::from(io::Error::last_os_error()))) { - Ok(_) => return Ok(()), + Ok(_) => return Ok(String::new()), Err(_) => { // errno == 19 means "No such device" // this happens if you provide a wrong filesystem type @@ -595,15 +615,13 @@ impl Mount for CreateMountPoint { } } } - Ok(()) + Ok(String::new()) } } } -/* - * Remount an existing mount - * Usage examples: "mount -o remount /home/username/mnt" - */ +/// Remount an existing mount point +/// Usage examples: "mount -o remount /home/username/mnt" struct Remount { property: Property, target: PathBuf, @@ -622,8 +640,8 @@ impl Remount { } } -impl Mount for Remount { - fn run(&mut self) -> MountResult<()> { +impl Mountable for Remount { + fn run(&mut self) -> MountResult { // check privilege // getuid() is always successful, so it's ok to use it if unsafe { libc::getuid() } != 0 { @@ -651,19 +669,12 @@ impl Mount for Remount { Some(self.data.as_os_str()), ).or(Err(MountError::from(io::Error::last_os_error())))? } - Ok(()) + Ok(String::new()) } } -type MountResult = ::std::result::Result; - -pub fn execute(setup: &mut S, args: T) -> Result<()> -where - S: UtilSetup, - T: ArgsIter, -{ - let matches = { - let app = util_app!(NAME) +fn create_app() -> App<'static, 'static> { + util_app!(NAME) .author("Zhuohua Li ") .arg(Arg::with_name("arg1") .index(1)) @@ -722,11 +733,22 @@ where .value_name("options") .use_delimiter(true) .possible_values(&["async", "atime", "defaults", "dev", "exec", "noatime", "nodev", "noexec", "nosuid", "nouser", "remount", "ro", "rw", "suid", "sync", "user"]) - .hide_possible_values(true)); + .hide_possible_values(true)) +} - app.get_matches_from_safe(args)? - }; +pub fn execute(setup: &mut S, args: T) -> Result<()> +where + S: UtilSetup, + T: ArgsIter, +{ + let app = create_app(); + let matches = app.get_matches_from_safe(args)?; + let options = MountOptions::from_matches(&matches)?; + + let output = setup.output(); + let output = output.lock()?; - let mount_command = MountCommand::new(setup, &matches)?; - Ok(mount_command.run()?) + let mut mounter = Mounter::new(output); + mounter.mount(options)?; + Ok(()) } From 7db42e1e4f740adc9a6820ad00de6d62650318c9 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Wed, 5 Sep 2018 16:26:53 -0700 Subject: [PATCH 09/15] lsb/mount: polish code --- libmesabox/src/lsb/mount/mod.rs | 116 ++++++++++++++------------------ 1 file changed, 49 insertions(+), 67 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index 1001dd9..b59db0f 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -74,6 +74,7 @@ impl From for MountError { /// There are several types of mount task, all of them implement Mountable trait trait Mountable { + // Sometimes mount prints messages, so returns a string fn run(&mut self) -> MountResult; } @@ -84,7 +85,7 @@ struct MountOptions { mount_list: Vec>, } -/// Used to translate UUID into corresponding device path +/// Translate UUID into corresponding device path struct Uuid { // map UUID to actual path path_map: HashMap, @@ -106,7 +107,7 @@ impl Uuid { Ok(Self { path_map: path_map }) } - fn to_dev(&self, input: OsString) -> MountResult { + fn get_dev(&self, input: OsString) -> MountResult { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "UUID=" let dir; @@ -125,7 +126,7 @@ impl Uuid { } } -/// Used to translate Label into corresponding device path +/// Translate Label into corresponding device path struct Label { // map Label to actual path path_map: HashMap, @@ -147,7 +148,7 @@ impl Label { Ok(Self { path_map: path_map }) } - fn to_dev(&self, input: OsString) -> MountResult { + fn get_dev(&self, input: OsString) -> MountResult { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "Label=" let dir; @@ -199,13 +200,10 @@ impl Default for Flag { } impl Flag { - fn add_flag(&mut self, f: &str) -> MountResult<()> { - let flg = OPTION_MAP.get(f).ok_or(MountError::UnsupportedOption)?; - self.flag.insert(*flg); - Ok(()) - } - - fn get_flag(options: &OsString) -> MountResult { + fn from_os_string(options: &OsString) -> MountResult { + if options == "" { + return Ok(Flag::default().flag); + } let s = options.clone(); let s = s.to_string_lossy(); let mut options: Vec<&str> = s.split(",").collect(); @@ -214,10 +212,8 @@ impl Flag { options.extend_from_slice(&["rw", "suid", "dev", "exec", "auto", "nouser", "async"]); } for opt in options { - if opt == "" { - continue; - } - flag.add_flag(opt)?; + flag.flag + .insert(*OPTION_MAP.get(opt).ok_or(MountError::UnsupportedOption)?); } Ok(flag.flag) } @@ -233,14 +229,14 @@ struct MntEnt { /// This is used to read Filesystem Description File /// e.g. /etc/fstab and /etc/mtab struct FSDescFile { - list: Vec, + entries: Vec, } impl FSDescFile { fn new(path: &str) -> MountResult { // all of these files should exist and can be read, but just in case let file = fs::File::open(path).or(Err(MountError::OpenFileError(String::from(path))))?; - let mut list = Vec::new(); + let mut entries = Vec::new(); for line in BufReader::new(file).lines() { let line = line.or(Err(MountError::FSDescFileFormatError(String::from(path))))?; match line.chars().next() { @@ -249,33 +245,26 @@ impl FSDescFile { } Some(_) => {} } - let mut iter = line.split_whitespace(); - let mnt_fsname = iter - .next() - .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; - let mnt_dir = iter - .next() - .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; - let mnt_type = iter - .next() - .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; - let mnt_opts = iter - .next() - .ok_or(MountError::FSDescFileFormatError(String::from(path)))?; + let a: Vec<&str> = line.split_whitespace().collect(); + // There should be 6 columns in FileSystem Description Files + if a.len() != 6 { + return Err(MountError::FSDescFileFormatError(String::from(path))); + } + // We only need the first 4 columns let mnt = MntEnt { - mnt_fsname: OsString::from(mnt_fsname), - mnt_dir: OsString::from(mnt_dir), - mnt_type: OsString::from(mnt_type), - mnt_opts: OsString::from(mnt_opts), + mnt_fsname: OsString::from(a[0]), + mnt_dir: OsString::from(a[1]), + mnt_type: OsString::from(a[2]), + mnt_opts: OsString::from(a[3]), }; - list.push(mnt) + entries.push(mnt) } - Ok(Self { list: list }) + Ok(Self { entries: entries }) } - fn to_string(&self, fs_type: &OsString) -> MountResult { + fn get_output(&self, fs_type: &OsString) -> String { let mut ret = String::new(); - for item in &self.list { + for item in &self.entries { if *fs_type != *"" { if *fs_type != item.mnt_type { continue; @@ -291,7 +280,7 @@ impl FSDescFile { ).as_str(), ); } - Ok(ret) + ret } } @@ -326,12 +315,10 @@ impl OsStringExtend for OsString { impl MountOptions { fn from_matches(matches: &clap::ArgMatches) -> MountResult { let mut mount_list: Vec> = Vec::new(); - let multi_thread = if matches.is_present("F") { true } else { false }; - // -a and -F are parsed at the very beginning // If -a exists, mount all the entries in /etc/fstab, except for those who contain "noauto" if matches.is_present("a") { let fstab = FSDescFile::new("/etc/fstab")?; - for item in fstab.list { + for item in fstab.entries { if item.mnt_opts.contains("noauto") { continue; } @@ -366,22 +353,22 @@ impl MountOptions { Some(t) => t.collect(), None => Vec::new(), }; - let fake = if matches.is_present("f") { true } else { false }; - let property = Property { fake: fake }; + let property = Property { + fake: matches.is_present("f"), + }; // We can use UUID as source if let Some(uuid) = matches.value_of("U") { arg2 = arg1; arg1 = OsString::from(String::from("UUID=") + uuid); } // We can use Label as source - if let Some(uuid) = matches.value_of("L") { + if let Some(label) = matches.value_of("L") { arg2 = arg1; - arg1 = OsString::from(String::from("Label=") + uuid); + arg1 = OsString::from(String::from("Label=") + label); } // no argument if arg1 == *"" { let m = ShowMountPoints::new(fs_type); - // m.run()?; mount_list.push(Box::new(m)); } // one argument @@ -396,7 +383,7 @@ impl MountOptions { mount_list.push(Box::new(m)); } else { let fstab = FSDescFile::new("/etc/fstab")?; - for item in fstab.list { + for item in fstab.entries { if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { let m = CreateMountPoint::new( property, @@ -429,7 +416,7 @@ impl MountOptions { } } Ok(Self { - multi_thread: multi_thread, + multi_thread: matches.is_present("F"), mount_list: mount_list, }) } @@ -487,7 +474,7 @@ impl ShowMountPoints { impl Mountable for ShowMountPoints { fn run(&mut self) -> MountResult { - FSDescFile::new("/proc/mounts")?.to_string(&self.filesystem_type) + Ok(FSDescFile::new("/proc/mounts")?.get_output(&self.filesystem_type)) } } @@ -524,10 +511,10 @@ impl CreateMountPoint { fn parse_source(source: OsString) -> MountResult { if source.starts_with("UUID=") { let uuid = Uuid::new()?; - Ok(PathBuf::from(uuid.to_dev(source)?)) + Ok(PathBuf::from(uuid.get_dev(source)?)) } else if source.starts_with("Label=") { let label = Label::new()?; - Ok(PathBuf::from(label.to_dev(source)?)) + Ok(PathBuf::from(label.get_dev(source)?)) } else { Ok(PathBuf::from(source)) } @@ -556,13 +543,8 @@ impl Mountable for CreateMountPoint { // if type is not specified, auto detect filesystem type if self.filesystem_type == *"" || self.filesystem_type == *"auto" { let file_name = "/proc/filesystems"; - let file = match fs::File::open(file_name) { - Ok(f) => f, - Err(_) => { - // this file should always exist, but just in case - return Err(MountError::OpenFileError(String::from(file_name))); - } - }; + let file = fs::File::open(file_name) + .or(Err(MountError::OpenFileError(String::from(file_name))))?; for line in BufReader::new(file).lines() { let line = line?; match line.chars().next() { @@ -576,8 +558,8 @@ impl Mountable for CreateMountPoint { } } let try_fs_type = &line[1..]; - let mountflags = Flag::get_flag(&self.mountflags)?; - match nix::mount::mount( + let mountflags = Flag::from_os_string(&self.mountflags)?; + if let Ok(_) = nix::mount::mount( Some(self.source.as_os_str()), self.target.as_os_str(), Some(try_fs_type), @@ -585,14 +567,13 @@ impl Mountable for CreateMountPoint { Some(self.data.as_os_str()), ).or(Err(MountError::from(io::Error::last_os_error()))) { - Ok(_) => return Ok(String::new()), - Err(_) => { /*println!("get error number: {}", nix::errno::errno());*/ } + return Ok(String::new()); } } return Err(MountError::UnsupportedFSType); } else { // if type is specified - let mountflags = Flag::get_flag(&self.mountflags)?; + let mountflags = Flag::from_os_string(&self.mountflags)?; if !self.property.fake { match nix::mount::mount( Some(self.source.as_os_str()), @@ -647,11 +628,12 @@ impl Mountable for Remount { if unsafe { libc::getuid() } != 0 { return Err(MountError::PermissionDenied); } - let mounts = FSDescFile::new("/proc/mounts")?; + let existing_mounts = FSDescFile::new("/proc/mounts")?; let mut source = OsString::new(); let mut target = OsString::new(); let mut filesystem_type = OsString::new(); - for item in mounts.list.iter().rev() { + // Go through all the existing mount points in reverse order + for item in existing_mounts.entries.iter().rev() { if self.target == item.mnt_fsname || self.target == item.mnt_dir { source = item.mnt_fsname.clone(); target = item.mnt_dir.clone(); @@ -659,7 +641,7 @@ impl Mountable for Remount { break; } } - let mountflags = Flag::get_flag(&self.mountflags)?; + let mountflags = Flag::from_os_string(&self.mountflags)?; if !self.property.fake { nix::mount::mount( Some(source.as_os_str()), From d970eb48c0434e18d75ff8503c13d6c075ded46b Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Wed, 5 Sep 2018 17:49:04 -0700 Subject: [PATCH 10/15] lsb/mount: use match expression in from_matches() --- libmesabox/src/lsb/mount/mod.rs | 165 +++++++++++++++++--------------- 1 file changed, 90 insertions(+), 75 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index b59db0f..e6b2f87 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -107,7 +107,7 @@ impl Uuid { Ok(Self { path_map: path_map }) } - fn get_dev(&self, input: OsString) -> MountResult { + fn get_device_path(&self, input: OsString) -> MountResult { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "UUID=" let dir; @@ -121,8 +121,9 @@ impl Uuid { Ok(self .path_map .get(&dir) - .ok_or(MountError::UuidNotFoundError(String::from(&s[5..])))? - .clone()) + .ok_or(MountError::UuidNotFoundError( + dir.to_string_lossy().to_string(), + ))?.clone()) } } @@ -148,7 +149,7 @@ impl Label { Ok(Self { path_map: path_map }) } - fn get_dev(&self, input: OsString) -> MountResult { + fn get_device_path(&self, input: OsString) -> MountResult { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "Label=" let dir; @@ -287,12 +288,18 @@ impl FSDescFile { /// Define some special requirements struct Property { fake: bool, + use_uuid: bool, + use_label: bool, // TODO user mountable devices } impl Default for Property { fn default() -> Self { - Self { fake: false } + Self { + fake: false, + use_uuid: false, + use_label: false, + } } } @@ -337,82 +344,87 @@ impl MountOptions { } // If -a doesn't exist, read arguments from command line, and find out the mount type else { - let mut arg1 = match matches.value_of("arg1") { - Some(arg1) => OsString::from(arg1), - None => OsString::from(""), - }; - let mut arg2 = match matches.value_of("arg2") { - Some(arg2) => OsString::from(arg2), - None => OsString::from(""), - }; - let fs_type = match matches.value_of("t") { - Some(t) => OsString::from(t), - None => OsString::from(""), - }; + let mut arg1 = matches.value_of("arg1"); + let mut arg2 = matches.value_of("arg2"); + let fs_type = matches.value_of("t"); let options: Vec<&str> = match matches.values_of("o") { Some(t) => t.collect(), None => Vec::new(), }; let property = Property { fake: matches.is_present("f"), + use_uuid: matches.is_present("U"), + use_label: matches.is_present("L"), }; // We can use UUID as source if let Some(uuid) = matches.value_of("U") { arg2 = arg1; - arg1 = OsString::from(String::from("UUID=") + uuid); + arg1 = Some(uuid); } // We can use Label as source if let Some(label) = matches.value_of("L") { arg2 = arg1; - arg1 = OsString::from(String::from("Label=") + label); - } - // no argument - if arg1 == *"" { - let m = ShowMountPoints::new(fs_type); - mount_list.push(Box::new(m)); + // arg1 = Some((String::from("Label==")+label).as_str()); + arg1 = Some(label); } - // one argument - else if arg1 != *"" && arg2 == *"" { - if options.contains(&"remount") { - let m = Remount::new( - property, - arg1, - OsString::from(options.join(",")), - OsString::from(""), - ); - mount_list.push(Box::new(m)); - } else { - let fstab = FSDescFile::new("/etc/fstab")?; - for item in fstab.entries { - if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { + + match arg1 { + Some(arg1) => { + match arg2 { + // two arguments + Some(arg2) => { + let t = match fs_type { + Some(t) => OsString::from(t), + None => OsString::new(), + }; let m = CreateMountPoint::new( property, - item.mnt_fsname, - PathBuf::from(item.mnt_dir), - item.mnt_type, - item.mnt_opts, + OsString::from(arg1), + PathBuf::from(arg2), + t, + OsString::from(options.join(",")), OsString::from(""), )?; mount_list.push(Box::new(m)); - break; } - } - if mount_list.len() == 0 { - return Err(MountError::InvalidArgument); + // one argument + None => { + if options.contains(&"remount") { + let m = Remount::new( + property, + OsString::from(arg1), + OsString::from(options.join(",")), + OsString::from(""), + ); + mount_list.push(Box::new(m)); + } else { + let fstab = FSDescFile::new("/etc/fstab")?; + for item in fstab.entries { + if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { + let m = CreateMountPoint::new( + property, + item.mnt_fsname, + PathBuf::from(item.mnt_dir), + item.mnt_type, + item.mnt_opts, + OsString::from(""), + )?; + mount_list.push(Box::new(m)); + break; + } + } + if mount_list.len() == 0 { + return Err(MountError::InvalidArgument); + } + } + } } } - } - // two argument - else { - let m = CreateMountPoint::new( - property, - arg1, - PathBuf::from(arg2), - fs_type, - OsString::from(options.join(",")), - OsString::from(""), - )?; - mount_list.push(Box::new(m)); + // no argument + None => { + let m = ShowMountPoints::new(fs_type); + mount_list.push(Box::new(m)); + } } } Ok(Self { @@ -465,10 +477,12 @@ struct ShowMountPoints { } impl ShowMountPoints { - fn new(filesystem_type: OsString) -> Self { - Self { - filesystem_type: filesystem_type, - } + fn new(filesystem_type: Option<&str>) -> Self { + let t = match filesystem_type { + Some(t) => OsString::from(t), + None => OsString::new(), + }; + Self { filesystem_type: t } } } @@ -498,27 +512,28 @@ impl CreateMountPoint { mountflags: OsString, data: OsString, ) -> MountResult { + // If source is an UUID or LABEL, get the corresponding device path + // If source is read from /etc/fstab, check its prefix + // If source is read from command line, check its property + let device_path; + if source.starts_with("UUID=") || property.use_uuid { + let uuid = Uuid::new()?; + device_path = PathBuf::from(uuid.get_device_path(source)?); + } else if source.starts_with("Label=") || property.use_label { + let label = Label::new()?; + device_path = PathBuf::from(label.get_device_path(source)?); + } else { + device_path = PathBuf::from(source); + } Ok(Self { property, - source: Self::parse_source(source)?, + source: device_path, target, filesystem_type, mountflags, data, }) } - - fn parse_source(source: OsString) -> MountResult { - if source.starts_with("UUID=") { - let uuid = Uuid::new()?; - Ok(PathBuf::from(uuid.get_dev(source)?)) - } else if source.starts_with("Label=") { - let label = Label::new()?; - Ok(PathBuf::from(label.get_dev(source)?)) - } else { - Ok(PathBuf::from(source)) - } - } } impl Mountable for CreateMountPoint { From df7afcbab0671f50f70a571e2f577e32bb69de56 Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Mon, 10 Sep 2018 11:11:21 -0700 Subject: [PATCH 11/15] lsb/mount: turn to use match expressions --- libmesabox/src/lsb/mount/mod.rs | 328 +++++++++++++++++++------------- 1 file changed, 199 insertions(+), 129 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index e6b2f87..d8c3c89 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -107,16 +107,16 @@ impl Uuid { Ok(Self { path_map: path_map }) } - fn get_device_path(&self, input: OsString) -> MountResult { + fn get_device_path(&self, input: &OsString) -> MountResult { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "UUID=" let dir; - let s = input.clone(); - let s = s.to_string_lossy(); + //let s = input.clone(); + let s = input.to_string_lossy(); if s.starts_with("UUID=") { dir = OsString::from(&s[5..]); } else { - dir = input; + dir = input.clone(); } Ok(self .path_map @@ -149,16 +149,16 @@ impl Label { Ok(Self { path_map: path_map }) } - fn get_device_path(&self, input: OsString) -> MountResult { + fn get_device_path(&self, input: &OsString) -> MountResult { // OsString and OsStr lacks "starts_with" function // FIXME Maybe find a better way to test if input starts with "Label=" let dir; - let s = input.clone(); - let s = s.to_string_lossy(); + //let s = input.clone(); + let s = input.to_string_lossy(); if s.starts_with("Label=") { dir = OsString::from(&s[6..]); } else { - dir = input; + dir = input.clone(); } Ok(self .path_map @@ -169,21 +169,22 @@ impl Label { } lazy_static! { - static ref OPTION_MAP: HashMap<&'static str, MsFlags> = { + //static ref OPTION_MAP: HashMap<&'static str, MsFlags> = { + static ref OPTION_MAP: HashMap = { let mut option_map = HashMap::new(); - option_map.insert("async", MsFlags::MS_SYNCHRONOUS); - option_map.insert("atime", MsFlags::MS_NOATIME); - option_map.insert("dev", MsFlags::MS_NODEV); - option_map.insert("exec", MsFlags::MS_NOEXEC); - option_map.insert("noatime", MsFlags::MS_NOATIME); - option_map.insert("nodev", MsFlags::MS_NODEV); - option_map.insert("noexec", MsFlags::MS_NOEXEC); - option_map.insert("nosuid", MsFlags::MS_NOSUID); - option_map.insert("remount", MsFlags::MS_REMOUNT); - option_map.insert("ro", MsFlags::MS_RDONLY); - option_map.insert("rw", MsFlags::MS_RDONLY); - option_map.insert("suid", MsFlags::MS_NOSUID); - option_map.insert("sync", MsFlags::MS_SYNCHRONOUS); + option_map.insert(OsString::from("async"), MsFlags::MS_SYNCHRONOUS); + option_map.insert(OsString::from("atime"), MsFlags::MS_NOATIME); + option_map.insert(OsString::from("dev"), MsFlags::MS_NODEV); + option_map.insert(OsString::from("exec"), MsFlags::MS_NOEXEC); + option_map.insert(OsString::from("noatime"), MsFlags::MS_NOATIME); + option_map.insert(OsString::from("nodev"), MsFlags::MS_NODEV); + option_map.insert(OsString::from("noexec"), MsFlags::MS_NOEXEC); + option_map.insert(OsString::from("nosuid"), MsFlags::MS_NOSUID); + option_map.insert(OsString::from("remount"), MsFlags::MS_REMOUNT); + option_map.insert(OsString::from("ro"), MsFlags::MS_RDONLY); + option_map.insert(OsString::from("rw"), MsFlags::MS_RDONLY); + option_map.insert(OsString::from("suid"), MsFlags::MS_NOSUID); + option_map.insert(OsString::from("sync"), MsFlags::MS_SYNCHRONOUS); option_map }; } @@ -201,16 +202,18 @@ impl Default for Flag { } impl Flag { - fn from_os_string(options: &OsString) -> MountResult { - if options == "" { - return Ok(Flag::default().flag); - } - let s = options.clone(); - let s = s.to_string_lossy(); - let mut options: Vec<&str> = s.split(",").collect(); + fn from(options: &mut Vec) -> MountResult { let mut flag = Flag::default(); - if options.contains(&"default") { - options.extend_from_slice(&["rw", "suid", "dev", "exec", "auto", "nouser", "async"]); + if options.contains(&OsString::from("default")) { + options.extend_from_slice(&[ + OsString::from("rw"), + OsString::from("suid"), + OsString::from("dev"), + OsString::from("exec"), + OsString::from("auto"), + OsString::from("nouser"), + OsString::from("async"), + ]); } for opt in options { flag.flag @@ -329,15 +332,24 @@ impl MountOptions { if item.mnt_opts.contains("noauto") { continue; } + // Split the comma separated option string into a vector, also convert &str into OsString + let opts: Vec = item + .mnt_opts + .to_string_lossy() + .split(",") + .collect::>() + .into_iter() + .map(|i| OsString::from(i)) + .collect(); // In this case, all the mounts are of type "CreateMountPoint" let m = CreateMountPoint::new( // TODO detect user mountable devices Property::default(), item.mnt_fsname, PathBuf::from(item.mnt_dir), - item.mnt_type, - item.mnt_opts, - OsString::from(""), + Some(item.mnt_type), + Some(opts), + OsString::new(), )?; mount_list.push(Box::new(m)); } @@ -347,21 +359,27 @@ impl MountOptions { let mut arg1 = matches.value_of("arg1"); let mut arg2 = matches.value_of("arg2"); let fs_type = matches.value_of("t"); - let options: Vec<&str> = match matches.values_of("o") { - Some(t) => t.collect(), - None => Vec::new(), - }; + let options: Option> = matches + .values_of("o") + .map(|i| i.collect()) + .map(|i: Vec<&str>| i.into_iter().map(|s| OsString::from(s)).collect()); + //let options: Vec<&str> = match matches.values_of("o") { + //Some(t) => t.collect(), + //None => Vec::new(), + //}; + let property = Property { fake: matches.is_present("f"), use_uuid: matches.is_present("U"), use_label: matches.is_present("L"), }; - // We can use UUID as source + + // If -U exists, then use UUID as source if let Some(uuid) = matches.value_of("U") { arg2 = arg1; arg1 = Some(uuid); } - // We can use Label as source + // If -L exists, then use Label as source if let Some(label) = matches.value_of("L") { arg2 = arg1; // arg1 = Some((String::from("Label==")+label).as_str()); @@ -373,50 +391,81 @@ impl MountOptions { match arg2 { // two arguments Some(arg2) => { - let t = match fs_type { - Some(t) => OsString::from(t), - None => OsString::new(), - }; + //let t = match fs_type { + //Some(t) => OsString::from(t), + //None => OsString::new(), + //}; let m = CreateMountPoint::new( property, OsString::from(arg1), PathBuf::from(arg2), - t, - OsString::from(options.join(",")), - OsString::from(""), + fs_type.map(|t| OsString::from(t)), + options, + OsString::new(), )?; mount_list.push(Box::new(m)); } // one argument None => { - if options.contains(&"remount") { - let m = Remount::new( - property, - OsString::from(arg1), - OsString::from(options.join(",")), - OsString::from(""), - ); - mount_list.push(Box::new(m)); - } else { - let fstab = FSDescFile::new("/etc/fstab")?; - for item in fstab.entries { - if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { - let m = CreateMountPoint::new( - property, - item.mnt_fsname, - PathBuf::from(item.mnt_dir), - item.mnt_type, - item.mnt_opts, - OsString::from(""), - )?; - mount_list.push(Box::new(m)); - break; - } + match options { + Some(ref opts) if opts.contains(&OsString::from("remount")) => { + let m = Remount::new( + property, + OsString::from(arg1), + opts.to_vec(), + OsString::new(), + ); + mount_list.push(Box::new(m)); } - if mount_list.len() == 0 { - return Err(MountError::InvalidArgument); + _ => { + let fstab = FSDescFile::new("/etc/fstab")?; + for item in fstab.entries { + if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { + let m = CreateMountPoint::new( + property, + item.mnt_fsname, + PathBuf::from(item.mnt_dir), + Some(item.mnt_type), + options, + OsString::new(), + )?; + mount_list.push(Box::new(m)); + break; + } + } + if mount_list.len() == 0 { + return Err(MountError::InvalidArgument); + } } } + //if options.contains(&"remount") { + //let m = Remount::new( + //property, + //OsString::from(arg1), + //OsString::from(options.join(",")), + //OsString::new(), + //); + //mount_list.push(Box::new(m)); + //} else { + //let fstab = FSDescFile::new("/etc/fstab")?; + //for item in fstab.entries { + //if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { + //let m = CreateMountPoint::new( + //property, + //item.mnt_fsname, + //PathBuf::from(item.mnt_dir), + //Some(item.mnt_type), + //item.mnt_opts, + //OsString::new(), + //)?; + //mount_list.push(Box::new(m)); + //break; + //} + //} + //if mount_list.len() == 0 { + //return Err(MountError::InvalidArgument); + //} + //} } } } @@ -457,7 +506,7 @@ where } for h in handle { if let Err(_) = h.join() { - return Err(MountError::InvalidArgument); + return Err(MountError::from(io::Error::last_os_error())); } } } else { @@ -498,8 +547,8 @@ struct CreateMountPoint { property: Property, source: PathBuf, target: PathBuf, - filesystem_type: OsString, - mountflags: OsString, + filesystem_type: Option, + mountflags: Option>, data: OsString, } @@ -508,8 +557,8 @@ impl CreateMountPoint { property: Property, source: OsString, target: PathBuf, - filesystem_type: OsString, - mountflags: OsString, + filesystem_type: Option, + mountflags: Option>, data: OsString, ) -> MountResult { // If source is an UUID or LABEL, get the corresponding device path @@ -518,10 +567,10 @@ impl CreateMountPoint { let device_path; if source.starts_with("UUID=") || property.use_uuid { let uuid = Uuid::new()?; - device_path = PathBuf::from(uuid.get_device_path(source)?); + device_path = PathBuf::from(uuid.get_device_path(&source)?); } else if source.starts_with("Label=") || property.use_label { let label = Label::new()?; - device_path = PathBuf::from(label.get_device_path(source)?); + device_path = PathBuf::from(label.get_device_path(&source)?); } else { device_path = PathBuf::from(source); } @@ -543,75 +592,91 @@ impl Mountable for CreateMountPoint { if unsafe { libc::getuid() } != 0 { return Err(MountError::PermissionDenied); } + // check if mount point exists if !self.target.exists() { return Err(MountError::MountPointNotExist(String::from( self.target.to_string_lossy(), ))); } + // check if device exists if !self.source.exists() { let s = String::from(self.source.to_string_lossy()); let t = String::from(self.target.to_string_lossy()); return Err(MountError::DeviceNotExist(t, s)); } - // if type is not specified, auto detect filesystem type - if self.filesystem_type == *"" || self.filesystem_type == *"auto" { - let file_name = "/proc/filesystems"; - let file = fs::File::open(file_name) - .or(Err(MountError::OpenFileError(String::from(file_name))))?; - for line in BufReader::new(file).lines() { - let line = line?; - match line.chars().next() { - Some(line) => { - if !line.is_whitespace() { - continue; // skip nodev devices + + // Get mountflags + let mountflags = match self.mountflags { + Some(ref mut mountflags) => Flag::from(mountflags)?, + None => Flag::default().flag, + }; + + // if type is not specified or "auto", automatically detect filesystem type + if Some(OsString::from("auto")) == self.filesystem_type { + self.filesystem_type = None; + } + + match self.filesystem_type { + None => { + let file_name = "/proc/filesystems"; + let file = fs::File::open(file_name) + .or(Err(MountError::OpenFileError(String::from(file_name))))?; + for line in BufReader::new(file).lines() { + let line = line?; + match line.chars().next() { + Some(line) => { + if !line.is_whitespace() { + continue; // skip nodev devices + } + } + None => { + continue; // skip empty lines } } - None => { - continue; // skip empty lines + let try_fs_type = &line[1..]; + //let mountflags = Flag::from_os_string(&self.mountflags)?; + if let Ok(_) = nix::mount::mount( + Some(self.source.as_os_str()), + self.target.as_os_str(), + Some(try_fs_type), + mountflags, + Some(self.data.as_os_str()), + ).or(Err(MountError::from(io::Error::last_os_error()))) + { + return Ok(String::new()); } } - let try_fs_type = &line[1..]; - let mountflags = Flag::from_os_string(&self.mountflags)?; - if let Ok(_) = nix::mount::mount( - Some(self.source.as_os_str()), - self.target.as_os_str(), - Some(try_fs_type), - mountflags, - Some(self.data.as_os_str()), - ).or(Err(MountError::from(io::Error::last_os_error()))) - { - return Ok(String::new()); - } + return Err(MountError::UnsupportedFSType); } - return Err(MountError::UnsupportedFSType); - } else { // if type is specified - let mountflags = Flag::from_os_string(&self.mountflags)?; - if !self.property.fake { - match nix::mount::mount( - Some(self.source.as_os_str()), - self.target.as_os_str(), - Some(self.filesystem_type.as_os_str()), - mountflags, - Some(self.data.as_os_str()), - ).or(Err(MountError::from(io::Error::last_os_error()))) - { - Ok(_) => return Ok(String::new()), - Err(_) => { - // errno == 19 means "No such device" - // this happens if you provide a wrong filesystem type - if nix::errno::errno() == 19 { - return Err(MountError::UnknownFSType(String::from( - self.filesystem_type.to_string_lossy(), - ))); + Some(ref fs_type) => { + //let mountflags = Flag::from(&self.mountflags)?; + if !self.property.fake { + match nix::mount::mount( + Some(self.source.as_os_str()), + self.target.as_os_str(), + Some(fs_type.as_os_str()), + mountflags, + Some(self.data.as_os_str()), + ).or(Err(MountError::from(io::Error::last_os_error()))) + { + Ok(_) => return Ok(String::new()), + Err(_) => { + // errno == 19 means "No such device" + // this happens if you provide a wrong filesystem type + if nix::errno::errno() == 19 { + return Err(MountError::UnknownFSType(String::from( + fs_type.to_string_lossy(), + ))); + } + return Err(MountError::from(io::Error::last_os_error())); } - return Err(MountError::from(io::Error::last_os_error())); } } + return Ok(String::new()); } - Ok(String::new()) } } } @@ -621,12 +686,17 @@ impl Mountable for CreateMountPoint { struct Remount { property: Property, target: PathBuf, - mountflags: OsString, + mountflags: Vec, data: OsString, } impl Remount { - fn new(property: Property, target: OsString, mountflags: OsString, data: OsString) -> Self { + fn new( + property: Property, + target: OsString, + mountflags: Vec, + data: OsString, + ) -> Self { Self { property, target: PathBuf::from(target), @@ -656,7 +726,7 @@ impl Mountable for Remount { break; } } - let mountflags = Flag::from_os_string(&self.mountflags)?; + let mountflags = Flag::from(&mut self.mountflags)?; if !self.property.fake { nix::mount::mount( Some(source.as_os_str()), From 715a4bb264cf73dad3dda764c9cd5831768ddbaf Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Mon, 10 Sep 2018 16:52:26 -0700 Subject: [PATCH 12/15] lsb/mount: reduce the use of clone --- libmesabox/src/lsb/mount/mod.rs | 225 ++++++++++++++------------------ 1 file changed, 98 insertions(+), 127 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index d8c3c89..f81d0a1 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -13,10 +13,10 @@ extern crate nix; use clap::{App, Arg}; use nix::mount::MsFlags; use std::collections::HashMap; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::fs; use std::io::{self, BufRead, BufReader, Write}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::thread; use {ArgsIter, LockError, Result, UtilSetup, UtilWrite}; @@ -53,6 +53,8 @@ enum MountError { LabelNotFoundError(String), #[fail(display = "{}: mount point does not exist.", _0)] MountPointNotExist(String), + #[fail(display = "{}: mount point not mounted or bad option.", _0)] + MountPointNotMounted(String), #[fail(display = "{}: special device {} does not exist.", _0, _1)] DeviceNotExist(String, String), // Denotes an error caused by one of stdin, stdout, or stderr failing to lock @@ -74,7 +76,7 @@ impl From for MountError { /// There are several types of mount task, all of them implement Mountable trait trait Mountable { - // Sometimes mount prints messages, so returns a string + // Sometimes mount prints messages, so it returns a string fn run(&mut self) -> MountResult; } @@ -107,23 +109,20 @@ impl Uuid { Ok(Self { path_map: path_map }) } - fn get_device_path(&self, input: &OsString) -> MountResult { - // OsString and OsStr lacks "starts_with" function - // FIXME Maybe find a better way to test if input starts with "UUID=" + fn get_device_path(&self, input: &OsString) -> MountResult<&Path> { + let input_string = input.to_string_lossy(); let dir; - //let s = input.clone(); - let s = input.to_string_lossy(); - if s.starts_with("UUID=") { - dir = OsString::from(&s[5..]); + if input_string.starts_with("UUID=") { + dir = OsStr::new(&input_string[5..]); } else { - dir = input.clone(); + dir = input; } Ok(self .path_map - .get(&dir) + .get(dir) .ok_or(MountError::UuidNotFoundError( dir.to_string_lossy().to_string(), - ))?.clone()) + ))?.as_path()) } } @@ -149,22 +148,19 @@ impl Label { Ok(Self { path_map: path_map }) } - fn get_device_path(&self, input: &OsString) -> MountResult { - // OsString and OsStr lacks "starts_with" function - // FIXME Maybe find a better way to test if input starts with "Label=" + fn get_device_path(&self, input: &OsString) -> MountResult<&Path> { + let input_string = input.to_string_lossy(); let dir; - //let s = input.clone(); - let s = input.to_string_lossy(); - if s.starts_with("Label=") { - dir = OsString::from(&s[6..]); + if input_string.starts_with("Label=") { + dir = OsStr::new(&input_string[6..]); } else { - dir = input.clone(); + dir = input; } Ok(self .path_map - .get(&dir) - .ok_or(MountError::LabelNotFoundError(String::from(&s[5..])))? - .clone()) + .get(dir) + .ok_or(MountError::LabelNotFoundError(dir.to_string_lossy().to_string()))? + .as_path()) } } @@ -202,7 +198,7 @@ impl Default for Flag { } impl Flag { - fn from(options: &mut Vec) -> MountResult { + fn from(options: &mut Vec) -> MountResult { let mut flag = Flag::default(); if options.contains(&OsString::from("default")) { options.extend_from_slice(&[ @@ -219,7 +215,7 @@ impl Flag { flag.flag .insert(*OPTION_MAP.get(opt).ok_or(MountError::UnsupportedOption)?); } - Ok(flag.flag) + Ok(flag) } } @@ -325,6 +321,7 @@ impl OsStringExtend for OsString { impl MountOptions { fn from_matches(matches: &clap::ArgMatches) -> MountResult { let mut mount_list: Vec> = Vec::new(); + // If -a exists, mount all the entries in /etc/fstab, except for those who contain "noauto" if matches.is_present("a") { let fstab = FSDescFile::new("/etc/fstab")?; @@ -343,7 +340,6 @@ impl MountOptions { .collect(); // In this case, all the mounts are of type "CreateMountPoint" let m = CreateMountPoint::new( - // TODO detect user mountable devices Property::default(), item.mnt_fsname, PathBuf::from(item.mnt_dir), @@ -359,14 +355,11 @@ impl MountOptions { let mut arg1 = matches.value_of("arg1"); let mut arg2 = matches.value_of("arg2"); let fs_type = matches.value_of("t"); + let options: Option> = matches .values_of("o") .map(|i| i.collect()) .map(|i: Vec<&str>| i.into_iter().map(|s| OsString::from(s)).collect()); - //let options: Vec<&str> = match matches.values_of("o") { - //Some(t) => t.collect(), - //None => Vec::new(), - //}; let property = Property { fake: matches.is_present("f"), @@ -379,22 +372,18 @@ impl MountOptions { arg2 = arg1; arg1 = Some(uuid); } + // If -L exists, then use Label as source if let Some(label) = matches.value_of("L") { arg2 = arg1; - // arg1 = Some((String::from("Label==")+label).as_str()); arg1 = Some(label); } match arg1 { Some(arg1) => { match arg2 { - // two arguments + // Two arguments Some(arg2) => { - //let t = match fs_type { - //Some(t) => OsString::from(t), - //None => OsString::new(), - //}; let m = CreateMountPoint::new( property, OsString::from(arg1), @@ -405,68 +394,38 @@ impl MountOptions { )?; mount_list.push(Box::new(m)); } - // one argument - None => { - match options { - Some(ref opts) if opts.contains(&OsString::from("remount")) => { - let m = Remount::new( - property, - OsString::from(arg1), - opts.to_vec(), - OsString::new(), - ); - mount_list.push(Box::new(m)); - } - _ => { - let fstab = FSDescFile::new("/etc/fstab")?; - for item in fstab.entries { - if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { - let m = CreateMountPoint::new( - property, - item.mnt_fsname, - PathBuf::from(item.mnt_dir), - Some(item.mnt_type), - options, - OsString::new(), - )?; - mount_list.push(Box::new(m)); - break; - } - } - if mount_list.len() == 0 { - return Err(MountError::InvalidArgument); + // One argument + None => match options { + Some(ref opts) if opts.contains(&OsString::from("remount")) => { + let m = Remount::new( + property, + OsString::from(arg1), + opts.to_vec(), + OsString::new(), + ); + mount_list.push(Box::new(m)); + } + _ => { + let fstab = FSDescFile::new("/etc/fstab")?; + for item in fstab.entries { + if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { + let m = CreateMountPoint::new( + property, + item.mnt_fsname, + PathBuf::from(item.mnt_dir), + Some(item.mnt_type), + options, + OsString::new(), + )?; + mount_list.push(Box::new(m)); + break; } } + if mount_list.len() == 0 { + return Err(MountError::InvalidArgument); + } } - //if options.contains(&"remount") { - //let m = Remount::new( - //property, - //OsString::from(arg1), - //OsString::from(options.join(",")), - //OsString::new(), - //); - //mount_list.push(Box::new(m)); - //} else { - //let fstab = FSDescFile::new("/etc/fstab")?; - //for item in fstab.entries { - //if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { - //let m = CreateMountPoint::new( - //property, - //item.mnt_fsname, - //PathBuf::from(item.mnt_dir), - //Some(item.mnt_type), - //item.mnt_opts, - //OsString::new(), - //)?; - //mount_list.push(Box::new(m)); - //break; - //} - //} - //if mount_list.len() == 0 { - //return Err(MountError::InvalidArgument); - //} - //} - } + }, } } // no argument @@ -595,31 +554,31 @@ impl Mountable for CreateMountPoint { // check if mount point exists if !self.target.exists() { - return Err(MountError::MountPointNotExist(String::from( - self.target.to_string_lossy(), - ))); + return Err(MountError::MountPointNotExist( + self.target.to_string_lossy().to_string(), + )); } // check if device exists if !self.source.exists() { - let s = String::from(self.source.to_string_lossy()); - let t = String::from(self.target.to_string_lossy()); + let s = self.source.to_string_lossy().to_string(); + let t = self.target.to_string_lossy().to_string(); return Err(MountError::DeviceNotExist(t, s)); } // Get mountflags let mountflags = match self.mountflags { - Some(ref mut mountflags) => Flag::from(mountflags)?, + Some(ref mut mountflags) => Flag::from(mountflags)?.flag, None => Flag::default().flag, }; - // if type is not specified or "auto", automatically detect filesystem type + // If type is not specified or "auto", automatically detect filesystem type if Some(OsString::from("auto")) == self.filesystem_type { self.filesystem_type = None; } - match self.filesystem_type { None => { + // Read all the filesystem types that we support let file_name = "/proc/filesystems"; let file = fs::File::open(file_name) .or(Err(MountError::OpenFileError(String::from(file_name))))?; @@ -635,8 +594,8 @@ impl Mountable for CreateMountPoint { continue; // skip empty lines } } + // Empty lines are already skipped, so it is ok to read array from index 1 let try_fs_type = &line[1..]; - //let mountflags = Flag::from_os_string(&self.mountflags)?; if let Ok(_) = nix::mount::mount( Some(self.source.as_os_str()), self.target.as_os_str(), @@ -648,11 +607,12 @@ impl Mountable for CreateMountPoint { return Ok(String::new()); } } + // Now we tried all the types that we support and none of them succeed return Err(MountError::UnsupportedFSType); } - // if type is specified + + // If type is specified Some(ref fs_type) => { - //let mountflags = Flag::from(&self.mountflags)?; if !self.property.fake { match nix::mount::mount( Some(self.source.as_os_str()), @@ -663,15 +623,16 @@ impl Mountable for CreateMountPoint { ).or(Err(MountError::from(io::Error::last_os_error()))) { Ok(_) => return Ok(String::new()), - Err(_) => { - // errno == 19 means "No such device" - // this happens if you provide a wrong filesystem type + Err(e) => { + // Error number 19 means "No such device" + // This happens if you provide a wrong filesystem type if nix::errno::errno() == 19 { return Err(MountError::UnknownFSType(String::from( fs_type.to_string_lossy(), ))); } - return Err(MountError::from(io::Error::last_os_error())); + // TODO: It's not known if there are other possible error numbers + return Err(e); } } } @@ -713,29 +674,41 @@ impl Mountable for Remount { if unsafe { libc::getuid() } != 0 { return Err(MountError::PermissionDenied); } + + // Go through all the existing mount points, find the appropriate source & target let existing_mounts = FSDescFile::new("/proc/mounts")?; - let mut source = OsString::new(); - let mut target = OsString::new(); - let mut filesystem_type = OsString::new(); - // Go through all the existing mount points in reverse order + let mut source = OsStr::new(""); + let mut target = OsStr::new(""); + let mut filesystem_type = OsStr::new(""); + // We need to do this in reverse order for item in existing_mounts.entries.iter().rev() { if self.target == item.mnt_fsname || self.target == item.mnt_dir { - source = item.mnt_fsname.clone(); - target = item.mnt_dir.clone(); - filesystem_type = item.mnt_type.clone(); + source = &item.mnt_fsname; + target = &item.mnt_dir; + filesystem_type = &item.mnt_type; break; } } - let mountflags = Flag::from(&mut self.mountflags)?; + + // If not found, the mount point is not mounted + if source == "" || target == "" { + return Err(MountError::MountPointNotMounted( + self.target.to_string_lossy().to_string(), + )); + } + + let mountflags = Flag::from(&mut self.mountflags)?.flag; + if !self.property.fake { nix::mount::mount( - Some(source.as_os_str()), - target.as_os_str(), - Some(filesystem_type.as_os_str()), + Some(source), + target, + Some(filesystem_type), mountflags, Some(self.data.as_os_str()), ).or(Err(MountError::from(io::Error::last_os_error())))? } + Ok(String::new()) } } @@ -747,7 +720,7 @@ fn create_app() -> App<'static, 'static> { .index(1)) .arg(Arg::with_name("arg2") .index(2)) - .arg(Arg::with_name("v") + .arg(Arg::with_name("v") // TODO: not supported yet .short("v") .help("invoke verbose mode. The mount command shall provide diagnostic messages on stdout.")) .arg(Arg::with_name("a") @@ -760,12 +733,10 @@ fn create_app() -> App<'static, 'static> { .arg(Arg::with_name("f") .short("f") .help("cause everything to be done except for the actual system call; if it's not obvious, this `fakes' mounting the file system.")) - // FIXME not supported yet - .arg(Arg::with_name("n") + .arg(Arg::with_name("n") // TODO: not supported yet .short("n") .help("mount without writing in /etc/mtab. This is necessary for example when /etc is on a read-only file system.")) - // FIXME not supported yet - .arg(Arg::with_name("s") + .arg(Arg::with_name("s") // TODO: not supported yet .short("s") .help("ignore mount options not supported by a file system type. Not all file systems support this option.")) .arg(Arg::with_name("r") From 41c54333a80d7857ac636780f0fec825ef9e5d5a Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Mon, 10 Sep 2018 18:05:46 -0700 Subject: [PATCH 13/15] lsb/mount: fix bug and polish code --- libmesabox/src/lsb/mount/mod.rs | 67 +++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index f81d0a1..0fa5487 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -11,6 +11,7 @@ extern crate lazy_static; extern crate libc; extern crate nix; use clap::{App, Arg}; +use libc::{c_long, c_ulong}; use nix::mount::MsFlags; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; @@ -159,28 +160,28 @@ impl Label { Ok(self .path_map .get(dir) - .ok_or(MountError::LabelNotFoundError(dir.to_string_lossy().to_string()))? - .as_path()) + .ok_or(MountError::LabelNotFoundError( + dir.to_string_lossy().to_string(), + ))?.as_path()) } } lazy_static! { - //static ref OPTION_MAP: HashMap<&'static str, MsFlags> = { - static ref OPTION_MAP: HashMap = { + static ref OPTION_MAP: HashMap = { let mut option_map = HashMap::new(); - option_map.insert(OsString::from("async"), MsFlags::MS_SYNCHRONOUS); - option_map.insert(OsString::from("atime"), MsFlags::MS_NOATIME); - option_map.insert(OsString::from("dev"), MsFlags::MS_NODEV); - option_map.insert(OsString::from("exec"), MsFlags::MS_NOEXEC); - option_map.insert(OsString::from("noatime"), MsFlags::MS_NOATIME); - option_map.insert(OsString::from("nodev"), MsFlags::MS_NODEV); - option_map.insert(OsString::from("noexec"), MsFlags::MS_NOEXEC); - option_map.insert(OsString::from("nosuid"), MsFlags::MS_NOSUID); - option_map.insert(OsString::from("remount"), MsFlags::MS_REMOUNT); - option_map.insert(OsString::from("ro"), MsFlags::MS_RDONLY); - option_map.insert(OsString::from("rw"), MsFlags::MS_RDONLY); - option_map.insert(OsString::from("suid"), MsFlags::MS_NOSUID); - option_map.insert(OsString::from("sync"), MsFlags::MS_SYNCHRONOUS); + option_map.insert(OsString::from("async"), !libc::MS_SYNCHRONOUS); + option_map.insert(OsString::from("atime"), !libc::MS_NOATIME); + option_map.insert(OsString::from("dev"), !libc::MS_NODEV); + option_map.insert(OsString::from("exec"), !libc::MS_NOEXEC); + option_map.insert(OsString::from("noatime"), libc::MS_NOATIME); + option_map.insert(OsString::from("nodev"), libc::MS_NODEV); + option_map.insert(OsString::from("noexec"), libc::MS_NOEXEC); + option_map.insert(OsString::from("nosuid"), libc::MS_NOSUID); + option_map.insert(OsString::from("remount"), libc::MS_REMOUNT); + option_map.insert(OsString::from("ro"), libc::MS_RDONLY); + option_map.insert(OsString::from("rw"), !libc::MS_RDONLY); + option_map.insert(OsString::from("suid"), !libc::MS_NOSUID); + option_map.insert(OsString::from("sync"), libc::MS_SYNCHRONOUS); option_map }; } @@ -198,8 +199,9 @@ impl Default for Flag { } impl Flag { - fn from(options: &mut Vec) -> MountResult { - let mut flag = Flag::default(); + fn from(options: &mut Vec) -> MountResult { + //let mut flag = Flag::default(); + let mut flag = Flag::default().flag.bits(); if options.contains(&OsString::from("default")) { options.extend_from_slice(&[ OsString::from("rw"), @@ -212,10 +214,14 @@ impl Flag { ]); } for opt in options { - flag.flag - .insert(*OPTION_MAP.get(opt).ok_or(MountError::UnsupportedOption)?); + let f = *OPTION_MAP.get(opt).ok_or(MountError::UnsupportedOption)?; + if (f as c_long) < 0 { + flag &= f; + } else { + flag |= f; + } } - Ok(flag) + Ok(MsFlags::from_bits(flag).ok_or(MountError::UnsupportedOption)?) } } @@ -329,15 +335,15 @@ impl MountOptions { if item.mnt_opts.contains("noauto") { continue; } + // Split the comma separated option string into a vector, also convert &str into OsString let opts: Vec = item .mnt_opts .to_string_lossy() .split(",") - .collect::>() - .into_iter() .map(|i| OsString::from(i)) .collect(); + // In this case, all the mounts are of type "CreateMountPoint" let m = CreateMountPoint::new( Property::default(), @@ -347,6 +353,7 @@ impl MountOptions { Some(opts), OsString::new(), )?; + mount_list.push(Box::new(m)); } } @@ -379,10 +386,11 @@ impl MountOptions { arg1 = Some(label); } + // Find out the exact mount type match arg1 { Some(arg1) => { match arg2 { - // Two arguments + // Two arguments, the type must be "CreateMountPoint" Some(arg2) => { let m = CreateMountPoint::new( property, @@ -396,6 +404,7 @@ impl MountOptions { } // One argument None => match options { + // If there is a "remount" option, the type is "Remount" Some(ref opts) if opts.contains(&OsString::from("remount")) => { let m = Remount::new( property, @@ -405,6 +414,7 @@ impl MountOptions { ); mount_list.push(Box::new(m)); } + // Otherwise, this device should be written in /etc/fstab _ => { let fstab = FSDescFile::new("/etc/fstab")?; for item in fstab.entries { @@ -421,6 +431,7 @@ impl MountOptions { break; } } + // If we cannot find anything about this device, return an error if mount_list.len() == 0 { return Err(MountError::InvalidArgument); } @@ -428,7 +439,7 @@ impl MountOptions { }, } } - // no argument + // no argument, the type must be "ShowMountPoints" None => { let m = ShowMountPoints::new(fs_type); mount_list.push(Box::new(m)); @@ -568,7 +579,7 @@ impl Mountable for CreateMountPoint { // Get mountflags let mountflags = match self.mountflags { - Some(ref mut mountflags) => Flag::from(mountflags)?.flag, + Some(ref mut mountflags) => Flag::from(mountflags)?, None => Flag::default().flag, }; @@ -697,7 +708,7 @@ impl Mountable for Remount { )); } - let mountflags = Flag::from(&mut self.mountflags)?.flag; + let mountflags = Flag::from(&mut self.mountflags)?; if !self.property.fake { nix::mount::mount( From 2c6a8845055f919ce322b091805186ff5cb979ac Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Mon, 10 Sep 2018 19:12:10 -0700 Subject: [PATCH 14/15] tests/lsb/mount: add tests for remount --- tests/lsb/mount.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/lsb/mount.rs b/tests/lsb/mount.rs index a056fe2..dbc7f62 100644 --- a/tests/lsb/mount.rs +++ b/tests/lsb/mount.rs @@ -261,7 +261,7 @@ fn root_test_mount_remount() { new_cmd!() .current_dir(&temp_dir) // then remount it as read-only - .args(&["-o", "remount,ro", "/dev/loop1", "mnt"]) + .args(&["-o", "remount,ro", "/dev/loop1"]) .assert() .success(); @@ -289,3 +289,18 @@ fn root_test_mount_remount() { .stdout("") .stderr(""); } + +#[test] +#[ignore] +fn root_test_mount_remount_nonexistent_mount_point() { + new_cmd!() + .args(&["-o", "remount", "/dev/this_device_should_not_be_mounted"]) + .assert() + .failure() + .stdout("") + .stderr( + predicate::str::contains( + "mount point not mounted or bad option.", + ).from_utf8(), + ); +} From 2f51827a4b5e87610b61cba9afe2a8d8a752a65f Mon Sep 17 00:00:00 2001 From: Li Zhuohua Date: Fri, 21 Sep 2018 17:14:12 -0700 Subject: [PATCH 15/15] lsb/mount: implement change requests --- Cargo.toml | 3 +- libmesabox/Cargo.toml | 3 +- libmesabox/src/lsb/mount/mod.rs | 359 +++++++++++++++++++------------- 3 files changed, 213 insertions(+), 152 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e7a83c7..2a72c1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,8 @@ mount = ["libmesabox/mount"] # the tests that libmesabox uses to build tar_util = ["libmesabox/tar_util"] lsb = [ - "mount", "tar_util" + "mount", + "tar_util" ] ping = ["libmesabox/ping"] diff --git a/libmesabox/Cargo.toml b/libmesabox/Cargo.toml index 6662257..89c3e0a 100644 --- a/libmesabox/Cargo.toml +++ b/libmesabox/Cargo.toml @@ -25,7 +25,8 @@ mount = ["libc", "lazy_static"] # XXX: temporary until renaming dependencies is supported tar_util = ["tar", "globset"] lsb = [ - "mount", "tar" + "mount", + "tar" ] ping = ["chrono", "crossbeam", "libc", "pnet", "byteorder", "trust-dns-resolver", "mio", "socket2"] diff --git a/libmesabox/src/lsb/mount/mod.rs b/libmesabox/src/lsb/mount/mod.rs index 0fa5487..efa8309 100644 --- a/libmesabox/src/lsb/mount/mod.rs +++ b/libmesabox/src/lsb/mount/mod.rs @@ -10,13 +10,16 @@ extern crate clap; extern crate lazy_static; extern crate libc; extern crate nix; + use clap::{App, Arg}; use libc::{c_long, c_ulong}; use nix::mount::MsFlags; +use std::borrow::Cow; use std::collections::HashMap; use std::ffi::{OsStr, OsString}; -use std::fs; +use std::fs::{self, File}; use std::io::{self, BufRead, BufReader, Write}; +use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::thread; use {ArgsIter, LockError, Result, UtilSetup, UtilWrite}; @@ -44,14 +47,10 @@ enum MountError { PermissionDenied, #[fail(display = "Invalid argument")] InvalidArgument, - #[fail(display = "Cannot support UUID on your system")] - UuidSupportError, - #[fail(display = "Cannot support Label on your system")] - LabelSupportError, - #[fail(display = "Cannot find UUID=\"{}\"", _0)] - UuidNotFoundError(String), - #[fail(display = "Cannot find Label=\"{}\"", _0)] - LabelNotFoundError(String), + #[fail(display = "Cannot support {} on your system", _0)] + UuidLabelNotSupportedError(String), + #[fail(display = "Cannot find {}=\"{}\"", _0, _1)] + UuidLabelNotFoundError(String, String), #[fail(display = "{}: mount point does not exist.", _0)] MountPointNotExist(String), #[fail(display = "{}: mount point not mounted or bad option.", _0)] @@ -75,113 +74,139 @@ impl From for MountError { } } +enum MountCore { + ShowMountPoints(ShowMountPoints), + CreateMountPoint(CreateMountPoint), + Remount(Remount), +} + /// There are several types of mount task, all of them implement Mountable trait trait Mountable { // Sometimes mount prints messages, so it returns a string fn run(&mut self) -> MountResult; } +impl Mountable for MountCore { + fn run(&mut self) -> MountResult { + match *self { + MountCore::ShowMountPoints(ref mut mount) => mount.run(), + MountCore::CreateMountPoint(ref mut mount) => mount.run(), + MountCore::Remount(ref mut mount) => mount.run(), + } + } +} + /// Store information that we need to execute a mount command struct MountOptions { multi_thread: bool, // Mount command might mount a lot of devices at the same time, so we cache them in a list - mount_list: Vec>, + mount_list: Vec, +} + +/// Source devices may be represented as an UUID or Label +enum SourceType { + Uuid, + Label, } -/// Translate UUID into corresponding device path -struct Uuid { - // map UUID to actual path +/// Help to convert an UUID or Label to the corresponding device path +struct SourceHelper { + source_type: SourceType, path_map: HashMap, } -impl Uuid { - fn new() -> MountResult { - let mut path_map: HashMap = HashMap::new(); - let dir = fs::read_dir("/dev/disk/by-uuid").or(Err(MountError::UuidSupportError))?; - for symlink in dir { - let link = symlink.or(Err(MountError::UuidSupportError))?; - path_map.insert( - link.file_name(), - link.path() - .canonicalize() - .or(Err(MountError::UuidSupportError))?, - ); - } - Ok(Self { path_map: path_map }) +impl SourceHelper { + fn new_uuid() -> MountResult { + Ok(Self { + source_type: SourceType::Uuid, + path_map: Self::get_path_map("/dev/disk/by-uuid", SourceType::Uuid)?, + }) + } + + fn new_label() -> MountResult { + Ok(Self { + source_type: SourceType::Label, + path_map: Self::get_path_map("/dev/disk/by-label", SourceType::Label)?, + }) } fn get_device_path(&self, input: &OsString) -> MountResult<&Path> { - let input_string = input.to_string_lossy(); - let dir; - if input_string.starts_with("UUID=") { - dir = OsStr::new(&input_string[5..]); - } else { - dir = input; + let input_bytes = input.as_bytes(); + let clipped_input; + let err_msg; + match self.source_type { + SourceType::Uuid => { + err_msg = "UUID".to_string(); + if input_bytes.starts_with(b"UUID=") { + clipped_input = OsStr::from_bytes(&input_bytes[5..]); + } else { + clipped_input = input; + } + } + SourceType::Label => { + err_msg = "Label".to_string(); + if input_bytes.starts_with(b"Label=") { + clipped_input = OsStr::from_bytes(&input_bytes[6..]); + } else { + clipped_input = input; + } + } } Ok(self .path_map - .get(dir) - .ok_or(MountError::UuidNotFoundError( - dir.to_string_lossy().to_string(), + .get(clipped_input) + .ok_or(MountError::UuidLabelNotFoundError( + err_msg, + clipped_input.to_string_lossy().to_string(), ))?.as_path()) } -} - -/// Translate Label into corresponding device path -struct Label { - // map Label to actual path - path_map: HashMap, -} -impl Label { - fn new() -> MountResult { + fn get_path_map( + read_path: &str, + source_type: SourceType, + ) -> MountResult> { let mut path_map: HashMap = HashMap::new(); - let dir = fs::read_dir("/dev/disk/by-label").or(Err(MountError::LabelSupportError))?; + let err_msg = match source_type { + SourceType::Uuid => "UUID", + SourceType::Label => "Label", + }; + let dir = fs::read_dir(read_path) + .or_else(|_| Err(MountError::UuidLabelNotSupportedError(err_msg.to_string())))?; for symlink in dir { - let link = symlink.or(Err(MountError::LabelSupportError))?; + let link = symlink + .or_else(|_| Err(MountError::UuidLabelNotSupportedError(err_msg.to_string())))?; path_map.insert( link.file_name(), - link.path() - .canonicalize() - .or(Err(MountError::LabelSupportError))?, + link.path().canonicalize().or_else(|_| { + Err(MountError::UuidLabelNotSupportedError(err_msg.to_string())) + })?, ); } - Ok(Self { path_map: path_map }) - } - - fn get_device_path(&self, input: &OsString) -> MountResult<&Path> { - let input_string = input.to_string_lossy(); - let dir; - if input_string.starts_with("Label=") { - dir = OsStr::new(&input_string[6..]); - } else { - dir = input; - } - Ok(self - .path_map - .get(dir) - .ok_or(MountError::LabelNotFoundError( - dir.to_string_lossy().to_string(), - ))?.as_path()) + Ok(path_map) } } lazy_static! { - static ref OPTION_MAP: HashMap = { + static ref OPTION_MAP: HashMap, c_ulong> = { let mut option_map = HashMap::new(); - option_map.insert(OsString::from("async"), !libc::MS_SYNCHRONOUS); - option_map.insert(OsString::from("atime"), !libc::MS_NOATIME); - option_map.insert(OsString::from("dev"), !libc::MS_NODEV); - option_map.insert(OsString::from("exec"), !libc::MS_NOEXEC); - option_map.insert(OsString::from("noatime"), libc::MS_NOATIME); - option_map.insert(OsString::from("nodev"), libc::MS_NODEV); - option_map.insert(OsString::from("noexec"), libc::MS_NOEXEC); - option_map.insert(OsString::from("nosuid"), libc::MS_NOSUID); - option_map.insert(OsString::from("remount"), libc::MS_REMOUNT); - option_map.insert(OsString::from("ro"), libc::MS_RDONLY); - option_map.insert(OsString::from("rw"), !libc::MS_RDONLY); - option_map.insert(OsString::from("suid"), !libc::MS_NOSUID); - option_map.insert(OsString::from("sync"), libc::MS_SYNCHRONOUS); + option_map.insert(Cow::Borrowed(OsStr::new("auto")), 0); // ignored + option_map.insert(Cow::Borrowed(OsStr::new("noauto")), 0); // ignored + option_map.insert(Cow::Borrowed(OsStr::new("defaults")), 0); // ignored + option_map.insert(Cow::Borrowed(OsStr::new("nouser")), 0); // ignored + option_map.insert(Cow::Borrowed(OsStr::new("user")), 0); // ignored + option_map.insert(Cow::Borrowed(OsStr::new("async")), !libc::MS_SYNCHRONOUS); + option_map.insert(Cow::Borrowed(OsStr::new("atime")), !libc::MS_NOATIME); + option_map.insert(Cow::Borrowed(OsStr::new("dev")), !libc::MS_NODEV); + option_map.insert(Cow::Borrowed(OsStr::new("exec")), !libc::MS_NOEXEC); + option_map.insert(Cow::Borrowed(OsStr::new("noatime")), libc::MS_NOATIME); + option_map.insert(Cow::Borrowed(OsStr::new("nodev")), libc::MS_NODEV); + option_map.insert(Cow::Borrowed(OsStr::new("noexec")), libc::MS_NOEXEC); + option_map.insert(Cow::Borrowed(OsStr::new("nosuid")), libc::MS_NOSUID); + option_map.insert(Cow::Borrowed(OsStr::new("remount")), libc::MS_REMOUNT); + option_map.insert(Cow::Borrowed(OsStr::new("ro")), libc::MS_RDONLY); + option_map.insert(Cow::Borrowed(OsStr::new("rw")), !libc::MS_RDONLY); + option_map.insert(Cow::Borrowed(OsStr::new("suid")), !libc::MS_NOSUID); + option_map.insert(Cow::Borrowed(OsStr::new("sync")), libc::MS_SYNCHRONOUS); option_map }; } @@ -199,18 +224,17 @@ impl Default for Flag { } impl Flag { - fn from(options: &mut Vec) -> MountResult { - //let mut flag = Flag::default(); - let mut flag = Flag::default().flag.bits(); - if options.contains(&OsString::from("default")) { + fn from<'a>(options: &mut Vec>) -> MountResult { + let mut flag = Self::default().flag.bits(); + if options.contains(&Cow::Borrowed(OsStr::new("default"))) { options.extend_from_slice(&[ - OsString::from("rw"), - OsString::from("suid"), - OsString::from("dev"), - OsString::from("exec"), - OsString::from("auto"), - OsString::from("nouser"), - OsString::from("async"), + Cow::Borrowed(OsStr::new("rw")), + Cow::Borrowed(OsStr::new("suid")), + Cow::Borrowed(OsStr::new("dev")), + Cow::Borrowed(OsStr::new("exec")), + Cow::Borrowed(OsStr::new("auto")), + Cow::Borrowed(OsStr::new("nouser")), + Cow::Borrowed(OsStr::new("async")), ]); } for opt in options { @@ -239,42 +263,59 @@ struct FSDescFile { } impl FSDescFile { - fn new(path: &str) -> MountResult { - // all of these files should exist and can be read, but just in case - let file = fs::File::open(path).or(Err(MountError::OpenFileError(String::from(path))))?; - let mut entries = Vec::new(); - for line in BufReader::new(file).lines() { - let line = line.or(Err(MountError::FSDescFileFormatError(String::from(path))))?; - match line.chars().next() { - None | Some('#') => { - continue; - } - Some(_) => {} + fn new(path: &Path) -> MountResult { + // All of these files should exist and can be read, but just in case + let file = File::open(path) + .or_else(|_| Err(MountError::OpenFileError(path.display().to_string())))?; + let mut entries = vec![]; + let mut reader = BufReader::new(file); + let mut line_bytes = vec![]; + while let Ok(n) = reader.read_until(b'\n', &mut line_bytes) { + // Break if EOF + if n == 0 { + break; + } + // Skip empty lines and commends + if line_bytes.is_empty() || line_bytes[0] == b'#' { + continue; } - let a: Vec<&str> = line.split_whitespace().collect(); - // There should be 6 columns in FileSystem Description Files - if a.len() != 6 { - return Err(MountError::FSDescFileFormatError(String::from(path))); + let line = OsStr::from_bytes(&line_bytes).to_os_string(); + line_bytes.clear(); + + // We need the first 4 columns + let mut splitted_line = line.split_whitespace(); + let mnt_fsname = splitted_line.next(); + let mnt_dir = splitted_line.next(); + let mnt_type = splitted_line.next(); + let mnt_opts = splitted_line.next(); + // There should be 2 columns remaining + if splitted_line.count() != 2 { + return Err(MountError::FSDescFileFormatError( + path.display().to_string(), + )); } - // We only need the first 4 columns let mnt = MntEnt { - mnt_fsname: OsString::from(a[0]), - mnt_dir: OsString::from(a[1]), - mnt_type: OsString::from(a[2]), - mnt_opts: OsString::from(a[3]), + mnt_fsname: OsString::from(mnt_fsname.unwrap()), + mnt_dir: OsString::from(mnt_dir.unwrap()), + mnt_type: OsString::from(mnt_type.unwrap()), + mnt_opts: OsString::from(mnt_opts.unwrap()), }; entries.push(mnt) } + Ok(Self { entries: entries }) } - fn get_output(&self, fs_type: &OsString) -> String { + fn get_output(&self, fs_type: &Option) -> String { let mut ret = String::new(); for item in &self.entries { - if *fs_type != *"" { - if *fs_type != item.mnt_type { - continue; + match *fs_type { + Some(ref t) => { + if *t != item.mnt_type { + continue; + } } + None => {} } ret.push_str( format!( @@ -312,6 +353,7 @@ impl Default for Property { trait OsStringExtend { fn starts_with(&self, pat: &str) -> bool; fn contains(&self, pat: &str) -> bool; + fn split_whitespace<'a>(&'a self) -> Box + 'a>; } impl OsStringExtend for OsString { @@ -322,26 +364,35 @@ impl OsStringExtend for OsString { fn contains(&self, pat: &str) -> bool { self.to_string_lossy().contains(pat) } + + fn split_whitespace<'a>(&'a self) -> Box + 'a> { + Box::new( + self.as_bytes() + .split(|ch| ch.is_ascii_whitespace()) + .filter(|s| !s.is_empty()) + .map(|s| OsStr::from_bytes(s)), + ) + } } impl MountOptions { fn from_matches(matches: &clap::ArgMatches) -> MountResult { - let mut mount_list: Vec> = Vec::new(); + let mut mount_list: Vec = vec![]; // If -a exists, mount all the entries in /etc/fstab, except for those who contain "noauto" if matches.is_present("a") { - let fstab = FSDescFile::new("/etc/fstab")?; + let fstab = FSDescFile::new(&Path::new("/etc/fstab"))?; for item in fstab.entries { if item.mnt_opts.contains("noauto") { continue; } // Split the comma separated option string into a vector, also convert &str into OsString - let opts: Vec = item + let opts: Vec> = item .mnt_opts .to_string_lossy() .split(",") - .map(|i| OsString::from(i)) + .map(|i| Cow::Owned(OsString::from(i))) .collect(); // In this case, all the mounts are of type "CreateMountPoint" @@ -354,7 +405,7 @@ impl MountOptions { OsString::new(), )?; - mount_list.push(Box::new(m)); + mount_list.push(MountCore::CreateMountPoint(m)); } } // If -a doesn't exist, read arguments from command line, and find out the mount type @@ -363,10 +414,14 @@ impl MountOptions { let mut arg2 = matches.value_of("arg2"); let fs_type = matches.value_of("t"); - let options: Option> = matches + let options: Option>> = matches .values_of("o") .map(|i| i.collect()) - .map(|i: Vec<&str>| i.into_iter().map(|s| OsString::from(s)).collect()); + .map(|i: Vec<&str>| { + i.into_iter() + .map(|s| Cow::Owned(OsString::from(s))) + .collect() + }); let property = Property { fake: matches.is_present("f"), @@ -400,23 +455,25 @@ impl MountOptions { options, OsString::new(), )?; - mount_list.push(Box::new(m)); + mount_list.push(MountCore::CreateMountPoint(m)); } // One argument None => match options { // If there is a "remount" option, the type is "Remount" - Some(ref opts) if opts.contains(&OsString::from("remount")) => { + Some(ref opts) + if opts.contains(&Cow::Borrowed(OsStr::new("remount"))) => + { let m = Remount::new( property, OsString::from(arg1), opts.to_vec(), OsString::new(), ); - mount_list.push(Box::new(m)); + mount_list.push(MountCore::Remount(m)); } // Otherwise, this device should be written in /etc/fstab _ => { - let fstab = FSDescFile::new("/etc/fstab")?; + let fstab = FSDescFile::new(Path::new("/etc/fstab"))?; for item in fstab.entries { if arg1 == item.mnt_fsname || arg1 == item.mnt_dir { let m = CreateMountPoint::new( @@ -427,7 +484,7 @@ impl MountOptions { options, OsString::new(), )?; - mount_list.push(Box::new(m)); + mount_list.push(MountCore::CreateMountPoint(m)); break; } } @@ -442,7 +499,7 @@ impl MountOptions { // no argument, the type must be "ShowMountPoints" None => { let m = ShowMountPoints::new(fs_type); - mount_list.push(Box::new(m)); + mount_list.push(MountCore::ShowMountPoints(m)); } } } @@ -492,22 +549,24 @@ where /// If -t is specified, only output mount points of this file system type /// Usage examples: "mount", "mount -t ext4" struct ShowMountPoints { - filesystem_type: OsString, + filesystem_type: Option, } impl ShowMountPoints { fn new(filesystem_type: Option<&str>) -> Self { - let t = match filesystem_type { - Some(t) => OsString::from(t), - None => OsString::new(), - }; - Self { filesystem_type: t } + //let t = match filesystem_type { + //Some(t) => OsString::from(t), + //None => OsString::new(), + //}; + Self { + filesystem_type: filesystem_type.map(|s| OsString::from(s)), + } } } impl Mountable for ShowMountPoints { fn run(&mut self) -> MountResult { - Ok(FSDescFile::new("/proc/mounts")?.get_output(&self.filesystem_type)) + Ok(FSDescFile::new(Path::new("/proc/mounts"))?.get_output(&self.filesystem_type)) } } @@ -518,7 +577,7 @@ struct CreateMountPoint { source: PathBuf, target: PathBuf, filesystem_type: Option, - mountflags: Option>, + mountflags: Option>>, data: OsString, } @@ -528,7 +587,7 @@ impl CreateMountPoint { source: OsString, target: PathBuf, filesystem_type: Option, - mountflags: Option>, + mountflags: Option>>, data: OsString, ) -> MountResult { // If source is an UUID or LABEL, get the corresponding device path @@ -536,11 +595,11 @@ impl CreateMountPoint { // If source is read from command line, check its property let device_path; if source.starts_with("UUID=") || property.use_uuid { - let uuid = Uuid::new()?; - device_path = PathBuf::from(uuid.get_device_path(&source)?); + let uuid_helper = SourceHelper::new_uuid()?; + device_path = PathBuf::from(uuid_helper.get_device_path(&source)?); } else if source.starts_with("Label=") || property.use_label { - let label = Label::new()?; - device_path = PathBuf::from(label.get_device_path(&source)?); + let label_helper = SourceHelper::new_label()?; + device_path = PathBuf::from(label_helper.get_device_path(&source)?); } else { device_path = PathBuf::from(source); } @@ -591,8 +650,8 @@ impl Mountable for CreateMountPoint { None => { // Read all the filesystem types that we support let file_name = "/proc/filesystems"; - let file = fs::File::open(file_name) - .or(Err(MountError::OpenFileError(String::from(file_name))))?; + let file = File::open(file_name) + .or_else(|_| Err(MountError::OpenFileError(String::from(file_name))))?; for line in BufReader::new(file).lines() { let line = line?; match line.chars().next() { @@ -613,7 +672,7 @@ impl Mountable for CreateMountPoint { Some(try_fs_type), mountflags, Some(self.data.as_os_str()), - ).or(Err(MountError::from(io::Error::last_os_error()))) + ).or_else(|_| Err(MountError::from(io::Error::last_os_error()))) { return Ok(String::new()); } @@ -631,7 +690,7 @@ impl Mountable for CreateMountPoint { Some(fs_type.as_os_str()), mountflags, Some(self.data.as_os_str()), - ).or(Err(MountError::from(io::Error::last_os_error()))) + ).or_else(|_| Err(MountError::from(io::Error::last_os_error()))) { Ok(_) => return Ok(String::new()), Err(e) => { @@ -658,7 +717,7 @@ impl Mountable for CreateMountPoint { struct Remount { property: Property, target: PathBuf, - mountflags: Vec, + mountflags: Vec>, data: OsString, } @@ -666,7 +725,7 @@ impl Remount { fn new( property: Property, target: OsString, - mountflags: Vec, + mountflags: Vec>, data: OsString, ) -> Self { Self { @@ -687,7 +746,7 @@ impl Mountable for Remount { } // Go through all the existing mount points, find the appropriate source & target - let existing_mounts = FSDescFile::new("/proc/mounts")?; + let existing_mounts = FSDescFile::new(Path::new("/proc/mounts"))?; let mut source = OsStr::new(""); let mut target = OsStr::new(""); let mut filesystem_type = OsStr::new(""); @@ -717,7 +776,7 @@ impl Mountable for Remount { Some(filesystem_type), mountflags, Some(self.data.as_os_str()), - ).or(Err(MountError::from(io::Error::last_os_error())))? + ).or_else(|_| Err(MountError::from(io::Error::last_os_error())))? } Ok(String::new())