mirror of
https://github.com/uutils/coreutils
synced 2024-12-13 14:52:41 +00:00
uucore: Split tty
from proc_info
And renamed `TerminalType` to `Teletype` for better naming because of Terminal contains graphic terminal but the current implementation only support `Teletype`.
This commit is contained in:
parent
12eacd1cf2
commit
58d0fb6fb2
4 changed files with 168 additions and 120 deletions
|
@ -110,3 +110,4 @@ utf8 = []
|
|||
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
|
||||
version-cmp = []
|
||||
wide = []
|
||||
tty = []
|
||||
|
|
|
@ -47,6 +47,8 @@ pub mod perms;
|
|||
pub mod pipes;
|
||||
#[cfg(all(target_os = "linux", feature = "proc-info"))]
|
||||
pub mod proc_info;
|
||||
#[cfg(all(target_os = "linux", feature = "tty"))]
|
||||
pub mod tty;
|
||||
#[cfg(all(unix, feature = "process"))]
|
||||
pub mod process;
|
||||
|
||||
|
|
|
@ -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,45 +343,40 @@ 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() {
|
||||
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!(
|
||||
TerminalType::try_from("/dev/tty1").unwrap(),
|
||||
TerminalType::Tty(1)
|
||||
Teletype::try_from("/dev/pts/10").unwrap(),
|
||||
Teletype::Pts(10)
|
||||
);
|
||||
assert_eq!(Teletype::try_from("/dev/ttyS1").unwrap(), Teletype::TtyS(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)
|
||||
Teletype::try_from("/dev/ttyS10").unwrap(),
|
||||
Teletype::TtyS(10)
|
||||
);
|
||||
assert_eq!(Teletype::try_from("ttyS10").unwrap(), Teletype::TtyS(10));
|
||||
|
||||
assert!(TerminalType::try_from("value").is_err());
|
||||
assert!(TerminalType::try_from("TtyS10").is_err());
|
||||
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(), "?");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -432,8 +392,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 +415,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,16 +428,10 @@ 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());
|
||||
assert_eq!(pid_entry.ttys().unwrap(), result.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
93
src/uucore/src/lib/features/tty.rs
Normal file
93
src/uucore/src/lib/features/tty.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
// 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(())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue