diff --git a/Cargo.lock b/Cargo.lock index 72a8529f..5f6aab7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1014,6 +1014,7 @@ dependencies = [ "uu_chcpu", "uu_ctrlaltdel", "uu_dmesg", + "uu_findmnt", "uu_fsfreeze", "uu_last", "uu_lscpu", @@ -1073,6 +1074,15 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_findmnt" +version = "0.0.1" +dependencies = [ + "clap", + "tabled", + "uucore", +] + [[package]] name = "uu_fsfreeze" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index 09330eb6..4a78242c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ feat_common_core = [ "renice", "rev", "setsid", + "findmnt", ] [workspace.dependencies] @@ -94,6 +95,7 @@ mountpoint = { optional = true, version = "0.0.1", package = "uu_mountpoint", pa renice = { optional = true, version = "0.0.1", package = "uu_renice", path = "src/uu/renice" } rev = { optional = true, version = "0.0.1", package = "uu_rev", path = "src/uu/rev" } setsid = { optional = true, version = "0.0.1", package = "uu_setsid", path ="src/uu/setsid" } +findmnt = { optional = true, version = "0.0.1", package = "uu_findmnt", path = "src/uu/findmnt" } [dev-dependencies] # dmesg test require fixed-boot-time feature turned on. diff --git a/src/uu/findmnt/Cargo.toml b/src/uu/findmnt/Cargo.toml new file mode 100644 index 00000000..c8eac8aa --- /dev/null +++ b/src/uu/findmnt/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "uu_findmnt" +version = "0.0.1" +edition = "2021" + +[lib] +path = "src/findmnt.rs" + +[[bin]] +name = "findmnt" +path = "src/main.rs" + +[dependencies] +tabled = { workspace = true } +uucore = { workspace = true } +clap = { workspace = true } diff --git a/src/uu/findmnt/findmnt.md b/src/uu/findmnt/findmnt.md new file mode 100644 index 00000000..27a1f1bc --- /dev/null +++ b/src/uu/findmnt/findmnt.md @@ -0,0 +1,7 @@ +# findmnt + +``` +findmnt [options] +``` + +findmnt will list all mounted filesytems or search for a filesystem. The findmnt command is able to search in /etc/fstab, /etc/fstab.d, /etc/mtab or /proc/self/mountinfo. If device or mountpoint is not given, all filesystems are shown. \ No newline at end of file diff --git a/src/uu/findmnt/src/findmnt.rs b/src/uu/findmnt/src/findmnt.rs new file mode 100644 index 00000000..bfa80381 --- /dev/null +++ b/src/uu/findmnt/src/findmnt.rs @@ -0,0 +1,189 @@ +use core::fmt; +use std::fs; + +use clap::{crate_version, Command}; +use tabled::{settings::Style, Table, Tabled}; +use uucore::{error::UResult, help_about}; + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + let _matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; + + // By default findmnt reads /proc/self/mountinfo + let mut res = Findmnt::new(MOUNTINFO_DIR); + res.form_nodes(); + res.print_table(); + Ok(()) +} + +pub static MOUNTINFO_DIR: &str = "/proc/self/mountinfo"; +pub static ABOUT: &str = help_about!("findmnt.md"); + +pub fn uu_app() -> Command { + Command::new(uucore::util_name()) + .version(crate_version!()) + .about(ABOUT) +} + +#[derive(Debug, Clone)] +pub struct Findmnt<'a> { + pub nodes_vec: Vec, + file_name: &'a str, +} + +impl Findmnt<'_> { + pub fn new(file_name: &str) -> Findmnt { + Findmnt { + file_name, + nodes_vec: Vec::::new(), + } + } + + pub fn form_nodes(&mut self) { + let res = fs::read_to_string(self.file_name).unwrap(); + let lines = res.lines(); + let mut unsorted_vec = Vec::::new(); + + for line in lines { + let res = Node::parse(line); + unsorted_vec.push(res); + } + + self.nodes_vec = unsorted_vec; + // /, /proc, /sys, /dev, /run, /tmp, /boot + // Sort the vec according to this + self.sort_nodes(); + } + + pub fn print_table(&self) { + let mut table = Table::new(self.nodes_vec.clone()); + table.with(Style::empty()); + print!("{}", table) + } + + fn sort_nodes(&mut self) { + let unsorted_vec = self.nodes_vec.clone(); + let mut sorted_vec = Vec::new(); + + // "/" + // This should always give one element + let res = unsorted_vec + .iter() + .find(|node| node.target == Types::ROOT.to_string()); + sorted_vec.push(res.unwrap().clone()); + + // "proc" + sorted_vec.extend(self.filter(Types::PROC)); + + // "/sys" + sorted_vec.extend(self.filter(Types::SYS)); + + // "/dev" + sorted_vec.extend(self.filter(Types::DEV)); + + // "/run" + sorted_vec.extend(self.filter(Types::RUN)); + + // "/tmp" + sorted_vec.extend(self.filter(Types::TMP)); + + // "/boot" + sorted_vec.extend(self.filter(Types::BOOT)); + + self.nodes_vec = sorted_vec; + } + + fn filter(&self, pattern: Types) -> Vec { + let mut temp_vec = Vec::::new(); + self.filter_with_pattern(pattern).iter().for_each(|node| { + temp_vec.push(node.clone()); + }); + temp_vec + } + + fn filter_with_pattern(&self, pattern: Types) -> Vec { + self.nodes_vec + .iter() + .filter(|node| node.target.starts_with(&pattern.to_string())) + .cloned() + .collect() + } +} + +// Different types for a particular node +#[derive(Debug, Clone)] +pub enum Types { + ROOT, + PROC, + SYS, + DEV, + RUN, + TMP, + BOOT, +} + +impl fmt::Display for Types { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Types::ROOT => write!(f, "/"), + Types::PROC => write!(f, "/proc"), + Types::SYS => write!(f, "/sys"), + Types::DEV => write!(f, "/dev"), + Types::RUN => write!(f, "/run"), + Types::TMP => write!(f, "/tmp"), + Types::BOOT => write!(f, "/boot"), + } + } +} + +// Represents each row for the table +#[derive(Debug, Clone, Tabled)] +pub struct Node { + target: String, + source: String, + fstype: String, + options: String, +} + +impl Node { + fn new(target: String, source: String, fstype: String, options: String) -> Node { + Node { + target, + source, + fstype, + options, + } + } + + pub fn filter_with_pattern(node_vec: &[Node], pattern: Types) -> Vec { + node_vec + .iter() + .filter(|node| node.target.starts_with(&pattern.to_string())) + .cloned() + .collect() + } + + // This is the main function that parses the default /proc/self/mountinfo + pub fn parse(line: &str) -> Self { + let (_, rest) = line.split_once("/").unwrap(); + let (target, rest) = rest.trim().split_once(" ").unwrap(); + let (options, rest) = rest.trim().split_once(" ").unwrap(); + let (_, rest) = rest.trim().split_once("-").unwrap(); + let (fstype, rest) = rest.trim().split_once(" ").unwrap(); + let (source, rest) = rest.trim().split_once(" ").unwrap(); + let options_added = if rest.split_once("rw").is_some() { + rest.split_once("rw").unwrap().1 + } else { + rest + }; + + let final_options = options.to_owned() + options_added; + + Self::new( + target.to_string(), + source.to_string(), + fstype.to_string(), + final_options, + ) + } +} diff --git a/src/uu/findmnt/src/main.rs b/src/uu/findmnt/src/main.rs new file mode 100644 index 00000000..ec0d24d6 --- /dev/null +++ b/src/uu/findmnt/src/main.rs @@ -0,0 +1,2 @@ +#[cfg(target_os = "linux")] +uucore::bin!(uu_findmnt); diff --git a/tests/by-util/test_findmnt.rs b/tests/by-util/test_findmnt.rs new file mode 100644 index 00000000..53027fba --- /dev/null +++ b/tests/by-util/test_findmnt.rs @@ -0,0 +1,7 @@ +use crate::common::util::TestScenario; + +#[cfg(target_os = "linux")] +#[test] +fn test_findmnt() { + new_ucmd!().succeeds().stdout_contains("/proc"); +} diff --git a/tests/tests.rs b/tests/tests.rs index 0fb3ed14..889d0154 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -57,6 +57,10 @@ mod test_dmesg; #[path = "by-util/test_fsfreeze.rs"] mod test_fsfreeze; +#[cfg(feature = "findmnt")] +#[path = "by-util/test_findmnt.rs"] +mod test_findmnt; + #[cfg(feature = "mcookie")] #[path = "by-util/test_mcookie.rs"] mod test_mcookie;