1919//! `snice` (TBD)
2020//!
2121
22+ use crate :: features:: tty:: Teletype ;
23+ use std:: hash:: Hash ;
2224use std:: {
2325 collections:: { HashMap , HashSet } ,
2426 fmt:: { self , Display , Formatter } ,
@@ -28,73 +30,6 @@ use std::{
2830} ;
2931use 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 ) ]
10035pub 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 ) ]
167110pub 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
183126impl 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" ) ]
382346mod 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