diff options
author | Sylvestre Ledru <sylvestre@debian.org> | 2024-07-26 08:47:41 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-26 08:47:41 +0200 |
commit | 2c92020fd1addbbe4353f7b013586209120ed7b9 (patch) | |
tree | f0cc2fac7acedd053058fd0d30ad148b9e02ae23 | |
parent | 058d94df6306367be93d44e233aea5dd013ec6ab (diff) | |
parent | 4bca4898f439445fe405d69e2a17d7b649fe3064 (diff) |
Merge pull request #6593 from Krysztal112233/add_tty
uucore: Split `tty` from `proc_info`
-rw-r--r-- | src/uucore/Cargo.toml | 1 | ||||
-rw-r--r-- | src/uucore/src/lib/features.rs | 2 | ||||
-rw-r--r-- | src/uucore/src/lib/features/proc_info.rs | 177 | ||||
-rw-r--r-- | src/uucore/src/lib/features/tty.rs | 127 |
4 files changed, 180 insertions, 127 deletions
diff --git a/src/uucore/Cargo.toml b/src/uucore/Cargo.toml index 132c6c4d49..8d463ee3dc 100644 --- a/src/uucore/Cargo.toml +++ b/src/uucore/Cargo.toml @@ -110,3 +110,4 @@ utf8 = [] utmpx = ["time", "time/macros", "libc", "dns-lookup"] version-cmp = [] wide = [] +tty = [] diff --git a/src/uucore/src/lib/features.rs b/src/uucore/src/lib/features.rs index abf401008b..cf24637f7b 100644 --- a/src/uucore/src/lib/features.rs +++ b/src/uucore/src/lib/features.rs @@ -49,6 +49,8 @@ pub mod pipes; pub mod proc_info; #[cfg(all(unix, feature = "process"))] pub mod process; +#[cfg(all(target_os = "linux", feature = "tty"))] +pub mod tty; #[cfg(all(unix, not(target_os = "macos"), feature = "fsxattr"))] pub mod fsxattr; diff --git a/src/uucore/src/lib/features/proc_info.rs b/src/uucore/src/lib/features/proc_info.rs index 41e0144be7..f6c4edf4c2 100644 --- a/src/uucore/src/lib/features/proc_info.rs +++ b/src/uucore/src/lib/features/proc_info.rs @@ -19,6 +19,8 @@ //! `snice` (TBD) //! +use crate::features::tty::Teletype; +use std::hash::Hash; use std::{ collections::{HashMap, HashSet}, fmt::{self, Display, Formatter}, @@ -28,73 +30,6 @@ use std::{ }; use walkdir::{DirEntry, WalkDir}; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum TerminalType { - Tty(u64), - TtyS(u64), - Pts(u64), -} - -impl TryFrom<String> for TerminalType { - type Error = (); - - fn try_from(value: String) -> Result<Self, Self::Error> { - Self::try_from(value.as_str()) - } -} - -impl TryFrom<&str> for TerminalType { - type Error = (); - - fn try_from(value: &str) -> Result<Self, Self::Error> { - Self::try_from(PathBuf::from(value)) - } -} - -impl TryFrom<PathBuf> for TerminalType { - type Error = (); - - fn try_from(value: PathBuf) -> Result<Self, Self::Error> { - // Three case: /dev/pts/* , /dev/ttyS**, /dev/tty** - - let mut iter = value.iter(); - // Case 1 - - // Considering this format: **/**/pts/<num> - if let (Some(_), Some(num)) = (iter.find(|it| *it == "pts"), iter.next()) { - return num - .to_str() - .ok_or(())? - .parse::<u64>() - .map_err(|_| ()) - .map(TerminalType::Pts); - }; - - // Considering this format: **/**/ttyS** then **/**/tty** - let path = value.to_str().ok_or(())?; - - let f = |prefix: &str| { - value - .iter() - .last()? - .to_str()? - .strip_prefix(prefix)? - .parse::<u64>() - .ok() - }; - - if path.contains("ttyS") { - // Case 2 - f("ttyS").ok_or(()).map(TerminalType::TtyS) - } else if path.contains("tty") { - // Case 3 - f("tty").ok_or(()).map(TerminalType::Tty) - } else { - Err(()) - } - } -} - /// State or process #[derive(Debug, PartialEq, Eq)] pub enum RunState { @@ -162,8 +97,16 @@ impl TryFrom<String> for RunState { } } +impl TryFrom<&String> for RunState { + type Error = io::Error; + + fn try_from(value: &String) -> Result<Self, Self::Error> { + Self::try_from(value.as_str()) + } +} + /// Process ID and its information -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct ProcessInformation { pub pid: usize, pub cmdline: String, @@ -177,7 +120,7 @@ pub struct ProcessInformation { cached_stat: Option<Rc<Vec<String>>>, cached_start_time: Option<u64>, - cached_tty: Option<Rc<HashSet<TerminalType>>>, + cached_tty: Option<Rc<HashSet<Teletype>>>, } impl ProcessInformation { @@ -252,7 +195,7 @@ impl ProcessInformation { } /// Collect information from `/proc/<pid>/stat` file - fn stat(&mut self) -> Rc<Vec<String>> { + pub fn stat(&mut self) -> Rc<Vec<String>> { if let Some(c) = &self.cached_stat { return Rc::clone(c); } @@ -264,7 +207,7 @@ impl ProcessInformation { Rc::clone(&result) } - /// Fetch start time + /// Fetch start time from [ProcessInformation::cached_stat] /// /// - [The /proc Filesystem: Table 1-4](https://docs.kernel.org/filesystems/proc.html#id10) pub fn start_time(&mut self) -> Result<u64, io::Error> { @@ -286,7 +229,7 @@ impl ProcessInformation { Ok(time) } - /// Fetch run state + /// Fetch run state from [ProcessInformation::cached_stat] /// /// - [The /proc Filesystem: Table 1-4](https://docs.kernel.org/filesystems/proc.html#id10) /// @@ -299,25 +242,38 @@ impl ProcessInformation { /// This function will scan the `/proc/<pid>/fd` directory /// + /// If the process does not belong to any terminal, + /// the result will contain [TerminalType::Unknown]. + /// + /// Otherwise [TerminalType::Unknown] does not appear in the result. + /// /// # Error /// /// If scanned pid had mismatched permission, /// it will caused [std::io::ErrorKind::PermissionDenied] error. - pub fn ttys(&mut self) -> Result<Rc<HashSet<TerminalType>>, io::Error> { + pub fn ttys(&mut self) -> Result<Rc<HashSet<Teletype>>, io::Error> { if let Some(tty) = &self.cached_tty { return Ok(Rc::clone(tty)); } let path = PathBuf::from(format!("/proc/{}/fd", self.pid)); - let result = Rc::new( - fs::read_dir(path)? - .flatten() - .filter(|it| it.path().is_symlink()) - .flat_map(|it| fs::read_link(it.path())) - .flat_map(TerminalType::try_from) - .collect::<HashSet<_>>(), - ); + let Ok(result) = fs::read_dir(path) else { + return Ok(Rc::new(HashSet::from_iter([Teletype::Unknown]))); + }; + + let mut result = result + .flatten() + .filter(|it| it.path().is_symlink()) + .flat_map(|it| fs::read_link(it.path())) + .flat_map(Teletype::try_from) + .collect::<HashSet<_>>(); + + if result.is_empty() { + result.insert(Teletype::Unknown); + } + + let result = Rc::new(result); self.cached_tty = Some(Rc::clone(&result)); @@ -335,6 +291,15 @@ impl TryFrom<DirEntry> for ProcessInformation { } } +impl Hash for ProcessInformation { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + // Make it faster. + self.pid.hash(state); + self.inner_status.hash(state); + self.inner_stat.hash(state); + } +} + /// Parsing `/proc/self/stat` file. /// /// In some case, the first pair (and the only one pair) will contains whitespace, @@ -378,48 +343,14 @@ pub fn walk_process() -> impl Iterator<Item = ProcessInformation> { } #[cfg(test)] -#[cfg(target_os = "linux")] mod tests { + use crate::features::tty::Teletype; + use super::*; use std::str::FromStr; #[test] - fn test_tty_convention() { - assert_eq!( - TerminalType::try_from("/dev/tty1").unwrap(), - TerminalType::Tty(1) - ); - assert_eq!( - TerminalType::try_from("/dev/tty10").unwrap(), - TerminalType::Tty(10) - ); - assert_eq!( - TerminalType::try_from("/dev/pts/1").unwrap(), - TerminalType::Pts(1) - ); - assert_eq!( - TerminalType::try_from("/dev/pts/10").unwrap(), - TerminalType::Pts(10) - ); - assert_eq!( - TerminalType::try_from("/dev/ttyS1").unwrap(), - TerminalType::TtyS(1) - ); - assert_eq!( - TerminalType::try_from("/dev/ttyS10").unwrap(), - TerminalType::TtyS(10) - ); - assert_eq!( - TerminalType::try_from("ttyS10").unwrap(), - TerminalType::TtyS(10) - ); - - assert!(TerminalType::try_from("value").is_err()); - assert!(TerminalType::try_from("TtyS10").is_err()); - } - - #[test] fn test_run_state_conversion() { assert_eq!(RunState::try_from("R").unwrap(), RunState::Running); assert_eq!(RunState::try_from("S").unwrap(), RunState::Sleeping); @@ -432,8 +363,6 @@ mod tests { assert!(RunState::try_from("G").is_err()); assert!(RunState::try_from("Rg").is_err()); - - assert!(RunState::try_from(String::from("Rg")).is_err()); } fn current_pid() -> usize { @@ -457,7 +386,7 @@ mod tests { } #[test] - fn test_process_information() { + fn test_pid_entry() { let current_pid = current_pid(); let mut pid_entry = ProcessInformation::try_new( @@ -470,19 +399,13 @@ mod tests { .flatten() .map(DirEntry::into_path) .flat_map(|it| it.read_link()) - .flat_map(TerminalType::try_from) + .flat_map(Teletype::try_from) .collect::<HashSet<_>>(); assert_eq!(pid_entry.ttys().unwrap(), result.into()); } #[test] - fn test_process_information_new() { - let result = ProcessInformation::try_new(PathBuf::from_iter(["/", "proc", "1"])); - assert!(result.is_ok()); - } - - #[test] fn test_stat_split() { let case = "32 (idle_inject/3) S 2 0 0 0 -1 69238848 0 0 0 0 0 0 0 0 -51 0 1 0 34 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 3 50 1 0 0 0 0 0 0 0 0 0 0 0"; assert!(stat_split(case)[1] == "idle_inject/3"); diff --git a/src/uucore/src/lib/features/tty.rs b/src/uucore/src/lib/features/tty.rs new file mode 100644 index 0000000000..67d34c5d0a --- /dev/null +++ b/src/uucore/src/lib/features/tty.rs @@ -0,0 +1,127 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +//! Set of functions to parsing TTY +use std::{ + fmt::{self, Display, Formatter}, + path::PathBuf, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Teletype { + Tty(u64), + TtyS(u64), + Pts(u64), + Unknown, +} + +impl Display for Teletype { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Self::Tty(id) => write!(f, "/dev/pts/{}", id), + Self::TtyS(id) => write!(f, "/dev/tty{}", id), + Self::Pts(id) => write!(f, "/dev/ttyS{}", id), + Self::Unknown => write!(f, "?"), + } + } +} + +impl TryFrom<String> for Teletype { + type Error = (); + + fn try_from(value: String) -> Result<Self, Self::Error> { + if value == "?" { + return Ok(Self::Unknown); + } + + Self::try_from(value.as_str()) + } +} + +impl TryFrom<&str> for Teletype { + type Error = (); + + fn try_from(value: &str) -> Result<Self, Self::Error> { + Self::try_from(PathBuf::from(value)) + } +} + +impl TryFrom<PathBuf> for Teletype { + type Error = (); + + fn try_from(value: PathBuf) -> Result<Self, Self::Error> { + // Three case: /dev/pts/* , /dev/ttyS**, /dev/tty** + + let mut iter = value.iter(); + // Case 1 + + // Considering this format: **/**/pts/<num> + if let (Some(_), Some(num)) = (iter.find(|it| *it == "pts"), iter.next()) { + return num + .to_str() + .ok_or(())? + .parse::<u64>() + .map_err(|_| ()) + .map(Teletype::Pts); + }; + + // Considering this format: **/**/ttyS** then **/**/tty** + let path = value.to_str().ok_or(())?; + + let f = |prefix: &str| { + value + .iter() + .last()? + .to_str()? + .strip_prefix(prefix)? + .parse::<u64>() + .ok() + }; + + if path.contains("ttyS") { + // Case 2 + f("ttyS").ok_or(()).map(Teletype::TtyS) + } else if path.contains("tty") { + // Case 3 + f("tty").ok_or(()).map(Teletype::Tty) + } else { + Err(()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tty_from() { + assert_eq!(Teletype::try_from("?").unwrap(), Teletype::Unknown); + assert_eq!(Teletype::try_from("/dev/tty1").unwrap(), Teletype::Tty(1)); + assert_eq!(Teletype::try_from("/dev/tty10").unwrap(), Teletype::Tty(10)); + assert_eq!(Teletype::try_from("/dev/pts/1").unwrap(), Teletype::Pts(1)); + assert_eq!( + Teletype::try_from("/dev/pts/10").unwrap(), + Teletype::Pts(10) + ); + assert_eq!(Teletype::try_from("/dev/ttyS1").unwrap(), Teletype::TtyS(1)); + assert_eq!( + Teletype::try_from("/dev/ttyS10").unwrap(), + Teletype::TtyS(10) + ); + assert_eq!(Teletype::try_from("ttyS10").unwrap(), Teletype::TtyS(10)); + + assert!(Teletype::try_from("value").is_err()); + assert!(Teletype::try_from("TtyS10").is_err()); + } + + #[test] + fn test_terminal_type_display() { + assert_eq!(Teletype::Pts(10).to_string(), "/dev/pts/10"); + assert_eq!(Teletype::Tty(10).to_string(), "/dev/tty10"); + assert_eq!(Teletype::TtyS(10).to_string(), "/dev/ttyS10"); + assert_eq!(Teletype::Unknown.to_string(), "?"); + } +} |