Skip to content

Commit 2c92020

Browse files
authored
Merge pull request #6593 from Krysztal112233/add_tty
uucore: Split `tty` from `proc_info`
2 parents 058d94d + 4bca489 commit 2c92020

File tree

4 files changed

+180
-127
lines changed

4 files changed

+180
-127
lines changed

src/uucore/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,4 @@ utf8 = []
110110
utmpx = ["time", "time/macros", "libc", "dns-lookup"]
111111
version-cmp = []
112112
wide = []
113+
tty = []

src/uucore/src/lib/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ pub mod pipes;
4949
pub mod proc_info;
5050
#[cfg(all(unix, feature = "process"))]
5151
pub mod process;
52+
#[cfg(all(target_os = "linux", feature = "tty"))]
53+
pub mod tty;
5254

5355
#[cfg(all(unix, not(target_os = "macos"), feature = "fsxattr"))]
5456
pub mod fsxattr;

src/uucore/src/lib/features/proc_info.rs

Lines changed: 50 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
//! `snice` (TBD)
2020
//!
2121
22+
use crate::features::tty::Teletype;
23+
use std::hash::Hash;
2224
use std::{
2325
collections::{HashMap, HashSet},
2426
fmt::{self, Display, Formatter},
@@ -28,73 +30,6 @@ use std::{
2830
};
2931
use walkdir::{DirEntry, WalkDir};
3032

31-
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32-
pub enum TerminalType {
33-
Tty(u64),
34-
TtyS(u64),
35-
Pts(u64),
36-
}
37-
38-
impl TryFrom<String> for TerminalType {
39-
type Error = ();
40-
41-
fn try_from(value: String) -> Result<Self, Self::Error> {
42-
Self::try_from(value.as_str())
43-
}
44-
}
45-
46-
impl TryFrom<&str> for TerminalType {
47-
type Error = ();
48-
49-
fn try_from(value: &str) -> Result<Self, Self::Error> {
50-
Self::try_from(PathBuf::from(value))
51-
}
52-
}
53-
54-
impl TryFrom<PathBuf> for TerminalType {
55-
type Error = ();
56-
57-
fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
58-
// Three case: /dev/pts/* , /dev/ttyS**, /dev/tty**
59-
60-
let mut iter = value.iter();
61-
// Case 1
62-
63-
// Considering this format: **/**/pts/<num>
64-
if let (Some(_), Some(num)) = (iter.find(|it| *it == "pts"), iter.next()) {
65-
return num
66-
.to_str()
67-
.ok_or(())?
68-
.parse::<u64>()
69-
.map_err(|_| ())
70-
.map(TerminalType::Pts);
71-
};
72-
73-
// Considering this format: **/**/ttyS** then **/**/tty**
74-
let path = value.to_str().ok_or(())?;
75-
76-
let f = |prefix: &str| {
77-
value
78-
.iter()
79-
.last()?
80-
.to_str()?
81-
.strip_prefix(prefix)?
82-
.parse::<u64>()
83-
.ok()
84-
};
85-
86-
if path.contains("ttyS") {
87-
// Case 2
88-
f("ttyS").ok_or(()).map(TerminalType::TtyS)
89-
} else if path.contains("tty") {
90-
// Case 3
91-
f("tty").ok_or(()).map(TerminalType::Tty)
92-
} else {
93-
Err(())
94-
}
95-
}
96-
}
97-
9833
/// State or process
9934
#[derive(Debug, PartialEq, Eq)]
10035
pub enum RunState {
@@ -162,8 +97,16 @@ impl TryFrom<String> for RunState {
16297
}
16398
}
16499

100+
impl TryFrom<&String> for RunState {
101+
type Error = io::Error;
102+
103+
fn try_from(value: &String) -> Result<Self, Self::Error> {
104+
Self::try_from(value.as_str())
105+
}
106+
}
107+
165108
/// Process ID and its information
166-
#[derive(Debug, Clone, Default)]
109+
#[derive(Debug, Clone, Default, PartialEq, Eq)]
167110
pub struct ProcessInformation {
168111
pub pid: usize,
169112
pub cmdline: String,
@@ -177,7 +120,7 @@ pub struct ProcessInformation {
177120
cached_stat: Option<Rc<Vec<String>>>,
178121

179122
cached_start_time: Option<u64>,
180-
cached_tty: Option<Rc<HashSet<TerminalType>>>,
123+
cached_tty: Option<Rc<HashSet<Teletype>>>,
181124
}
182125

183126
impl ProcessInformation {
@@ -252,7 +195,7 @@ impl ProcessInformation {
252195
}
253196

254197
/// Collect information from `/proc/<pid>/stat` file
255-
fn stat(&mut self) -> Rc<Vec<String>> {
198+
pub fn stat(&mut self) -> Rc<Vec<String>> {
256199
if let Some(c) = &self.cached_stat {
257200
return Rc::clone(c);
258201
}
@@ -264,7 +207,7 @@ impl ProcessInformation {
264207
Rc::clone(&result)
265208
}
266209

267-
/// Fetch start time
210+
/// Fetch start time from [ProcessInformation::cached_stat]
268211
///
269212
/// - [The /proc Filesystem: Table 1-4](https://docs.kernel.org/filesystems/proc.html#id10)
270213
pub fn start_time(&mut self) -> Result<u64, io::Error> {
@@ -286,7 +229,7 @@ impl ProcessInformation {
286229
Ok(time)
287230
}
288231

289-
/// Fetch run state
232+
/// Fetch run state from [ProcessInformation::cached_stat]
290233
///
291234
/// - [The /proc Filesystem: Table 1-4](https://docs.kernel.org/filesystems/proc.html#id10)
292235
///
@@ -299,25 +242,38 @@ impl ProcessInformation {
299242

300243
/// This function will scan the `/proc/<pid>/fd` directory
301244
///
245+
/// If the process does not belong to any terminal,
246+
/// the result will contain [TerminalType::Unknown].
247+
///
248+
/// Otherwise [TerminalType::Unknown] does not appear in the result.
249+
///
302250
/// # Error
303251
///
304252
/// If scanned pid had mismatched permission,
305253
/// it will caused [std::io::ErrorKind::PermissionDenied] error.
306-
pub fn ttys(&mut self) -> Result<Rc<HashSet<TerminalType>>, io::Error> {
254+
pub fn ttys(&mut self) -> Result<Rc<HashSet<Teletype>>, io::Error> {
307255
if let Some(tty) = &self.cached_tty {
308256
return Ok(Rc::clone(tty));
309257
}
310258

311259
let path = PathBuf::from(format!("/proc/{}/fd", self.pid));
312260

313-
let result = Rc::new(
314-
fs::read_dir(path)?
315-
.flatten()
316-
.filter(|it| it.path().is_symlink())
317-
.flat_map(|it| fs::read_link(it.path()))
318-
.flat_map(TerminalType::try_from)
319-
.collect::<HashSet<_>>(),
320-
);
261+
let Ok(result) = fs::read_dir(path) else {
262+
return Ok(Rc::new(HashSet::from_iter([Teletype::Unknown])));
263+
};
264+
265+
let mut result = result
266+
.flatten()
267+
.filter(|it| it.path().is_symlink())
268+
.flat_map(|it| fs::read_link(it.path()))
269+
.flat_map(Teletype::try_from)
270+
.collect::<HashSet<_>>();
271+
272+
if result.is_empty() {
273+
result.insert(Teletype::Unknown);
274+
}
275+
276+
let result = Rc::new(result);
321277

322278
self.cached_tty = Some(Rc::clone(&result));
323279

@@ -335,6 +291,15 @@ impl TryFrom<DirEntry> for ProcessInformation {
335291
}
336292
}
337293

294+
impl Hash for ProcessInformation {
295+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
296+
// Make it faster.
297+
self.pid.hash(state);
298+
self.inner_status.hash(state);
299+
self.inner_stat.hash(state);
300+
}
301+
}
302+
338303
/// Parsing `/proc/self/stat` file.
339304
///
340305
/// In some case, the first pair (and the only one pair) will contains whitespace,
@@ -378,47 +343,13 @@ pub fn walk_process() -> impl Iterator<Item = ProcessInformation> {
378343
}
379344

380345
#[cfg(test)]
381-
#[cfg(target_os = "linux")]
382346
mod tests {
383347

348+
use crate::features::tty::Teletype;
349+
384350
use super::*;
385351
use std::str::FromStr;
386352

387-
#[test]
388-
fn test_tty_convention() {
389-
assert_eq!(
390-
TerminalType::try_from("/dev/tty1").unwrap(),
391-
TerminalType::Tty(1)
392-
);
393-
assert_eq!(
394-
TerminalType::try_from("/dev/tty10").unwrap(),
395-
TerminalType::Tty(10)
396-
);
397-
assert_eq!(
398-
TerminalType::try_from("/dev/pts/1").unwrap(),
399-
TerminalType::Pts(1)
400-
);
401-
assert_eq!(
402-
TerminalType::try_from("/dev/pts/10").unwrap(),
403-
TerminalType::Pts(10)
404-
);
405-
assert_eq!(
406-
TerminalType::try_from("/dev/ttyS1").unwrap(),
407-
TerminalType::TtyS(1)
408-
);
409-
assert_eq!(
410-
TerminalType::try_from("/dev/ttyS10").unwrap(),
411-
TerminalType::TtyS(10)
412-
);
413-
assert_eq!(
414-
TerminalType::try_from("ttyS10").unwrap(),
415-
TerminalType::TtyS(10)
416-
);
417-
418-
assert!(TerminalType::try_from("value").is_err());
419-
assert!(TerminalType::try_from("TtyS10").is_err());
420-
}
421-
422353
#[test]
423354
fn test_run_state_conversion() {
424355
assert_eq!(RunState::try_from("R").unwrap(), RunState::Running);
@@ -432,8 +363,6 @@ mod tests {
432363

433364
assert!(RunState::try_from("G").is_err());
434365
assert!(RunState::try_from("Rg").is_err());
435-
436-
assert!(RunState::try_from(String::from("Rg")).is_err());
437366
}
438367

439368
fn current_pid() -> usize {
@@ -457,7 +386,7 @@ mod tests {
457386
}
458387

459388
#[test]
460-
fn test_process_information() {
389+
fn test_pid_entry() {
461390
let current_pid = current_pid();
462391

463392
let mut pid_entry = ProcessInformation::try_new(
@@ -470,18 +399,12 @@ mod tests {
470399
.flatten()
471400
.map(DirEntry::into_path)
472401
.flat_map(|it| it.read_link())
473-
.flat_map(TerminalType::try_from)
402+
.flat_map(Teletype::try_from)
474403
.collect::<HashSet<_>>();
475404

476405
assert_eq!(pid_entry.ttys().unwrap(), result.into());
477406
}
478407

479-
#[test]
480-
fn test_process_information_new() {
481-
let result = ProcessInformation::try_new(PathBuf::from_iter(["/", "proc", "1"]));
482-
assert!(result.is_ok());
483-
}
484-
485408
#[test]
486409
fn test_stat_split() {
487410
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";

0 commit comments

Comments
 (0)