summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSylvestre Ledru <sylvestre@debian.org>2024-07-26 08:47:41 +0200
committerGitHub <noreply@github.com>2024-07-26 08:47:41 +0200
commit2c92020fd1addbbe4353f7b013586209120ed7b9 (patch)
treef0cc2fac7acedd053058fd0d30ad148b9e02ae23
parent058d94df6306367be93d44e233aea5dd013ec6ab (diff)
parent4bca4898f439445fe405d69e2a17d7b649fe3064 (diff)
Merge pull request #6593 from Krysztal112233/add_tty
uucore: Split `tty` from `proc_info`
-rw-r--r--src/uucore/Cargo.toml1
-rw-r--r--src/uucore/src/lib/features.rs2
-rw-r--r--src/uucore/src/lib/features/proc_info.rs177
-rw-r--r--src/uucore/src/lib/features/tty.rs127
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(), "?");
+ }
+}