pals/
lib.rs

1// Copyright 2018 oooutlk@outlook.com. See the COPYRIGHT
2// file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10//! # pals = Processes' Arguments LiSt
11//!
12//! The main API is `pals()` which returns `ProcList` if succeeded.
13//!
14//! ```rust,no_run
15//! let proc_list = pals::pals().unwrap();
16//! ```
17//!
18//! ## `ProcList` can be regarded as a list of `procs()`.
19//!
20//! ```rust,no_run
21//! let proc_list = pals::pals().unwrap();
22//! let procs = proc_list.procs();
23//! ```
24//!
25//! The following items can be accessed:
26//!
27//! 1. pid -- Process::pid.
28//!
29//! 2. ppid -- Process::ppid.
30//!
31//! 3. nul-terminated arguments in one single string -- Process::arguments.
32//!
33//! 4. argument iterator -- `Process::argv()`.
34//!
35//! 5. parent process -- `ProcList::parent_of()`.
36//!
37//! ```rust,no_run
38//! let proc_list = pals::pals().unwrap();
39//! let mut procs = proc_list.procs();
40//! let first = procs.next().unwrap();
41//! println!( "pid:{:?}, ppid:{:?}", first.pid, first.ppid );
42//! println!( "arguments:{}", first.arguments );
43//! println!( "argv:\n{}", first
44//!     .argv()
45//!     .enumerate()
46//!     .fold( String::new(),
47//!         |acc,(i,arg)| format!( "{}\narg #{}:{}", acc, i, arg ))
48//! );
49//! println!( "parent's pid:{:?}", proc_list.parent_of( first.pid ));
50//! ```
51//!
52//! ## `ProcList` can be regarded as a list of `Proc` trees.
53//!
54//! Besides items mentioned above, the following extra items can be accessed:
55//!
56//! 6. parent nodes -- `Proc::parent()`.
57//!
58//! 7. all child nodes -- `Proc::children()`.
59//!
60//! ```rust
61//! use pals::{Proc, pals};
62//!
63//! pals().map( |proc_list| {
64//!     fn assert_ppid_is_parent_pid( proc: Proc ) {
65//!         proc.parent()
66//!             .map( |parent| assert_eq!( parent.pid, proc.ppid ));
67//!         proc.children()
68//!             .for_each( |subproc| assert_ppid_is_parent_pid( subproc ));
69//!     }
70//!
71//!     proc_list
72//!         .children()
73//!         .for_each( |proc| assert_ppid_is_parent_pid( proc ))
74//! }).unwrap();
75//! ```
76//!
77//! ## `ProcList` can be converted to `trees::Forest`.
78//!
79//! ```rust,no_run
80//! use pals::{Process, pals};
81//! use trees::Forest;
82//!
83//! let proc_list = pals().unwrap();
84//!
85//! let bfs = proc_list
86//!     .bfs()
87//!     .map( ToOwned::to_owned ); // &Process -> Process
88//!
89//! let forest = Forest::<Process>::from( bfs );
90//! ```
91//!
92//! # Binary utility
93//!
94//! See README.md for more.
95
96use anyhow::anyhow;
97
98#[cfg( unix )]
99use anyhow::Context;
100
101#[cfg( target_os = "freebsd" )]
102use freebsd::parse_id;
103
104#[cfg( target_os = "freebsd" )]
105use serde::{Deserialize, de};
106
107use std::{
108    fmt::{self, Debug, Display},
109    mem,
110    ops::Deref,
111    str,
112};
113
114#[cfg( not( target_os = "windows" ))]
115use std::process::Command;
116
117use trees::bfs::{BfsForest, Split, Splitted};
118
119/// Process ID.
120#[derive( Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord )]
121#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
122pub struct Pid( pub u32 );
123
124#[cfg( target_os = "freebsd" )]
125mod freebsd;
126#[cfg( target_os = "freebsd" )]
127use freebsd::{PsOutput, pals_from_procfs};
128
129#[cfg( target_os = "linux" )]
130mod linux;
131#[cfg( target_os = "linux" )]
132use linux::pals_from_procfs;
133
134#[cfg( windows )]
135mod windows;
136
137/// Process list. It can be viewed as a list of process or a list of process
138/// trees.
139#[derive( Debug )]
140#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
141pub struct ProcList {
142    #[cfg_attr( target_os = "freebsd", serde( rename = "process" ))]
143    procs : Vec<ProcNode>,
144    #[cfg_attr( target_os = "freebsd", serde( skip ))]
145    root  : Link,
146}
147
148impl Default for ProcList {
149    fn default() -> Self { ProcList{ procs: Vec::new(), root: Link::default() }}
150}
151
152impl ProcList {
153    /// Gets parent process' pid of the process the pid of which is given.
154    pub fn parent_of( &self, pid: Pid ) -> Option<&Process> {
155        self.locate( pid ).and_then( |index| {
156            let process = &self.procs[ index ].process;
157            if process.pid != process.ppid {
158                Some( process )
159            } else {
160                None
161            }
162        })
163    }
164
165    fn locate( &self, pid: Pid ) -> Option<usize> {
166        self.procs.binary_search_by_key( &pid, ProcNode::id ).ok()
167    }
168
169    fn adopt( &mut self, parent: Option<usize>, child: usize ) -> anyhow::Result<()> {
170        let mut index = parent;
171        let child_pid = self.procs[ child ].pid;
172        let node_count = self.procs[ child ].link.size.descendants + 1;
173        while let Some( i ) = index {
174            self.procs[i].link.size.descendants += node_count;
175            let pid = self.procs[i].pid;
176            if pid == self.procs[i].ppid {
177                break;
178            } else if pid == child_pid {
179                return Err( anyhow!( "error: {:?} caused circuit in process tree.", pid ));
180            }
181            index = self.locate( self.procs[i].ppid );
182        }
183
184        match parent {
185            Some( parent ) => {
186                self.procs[ parent ].link.size.degree += 1;
187                self.procs[ child ].link.sib = self.procs[ parent ].link.child;
188                self.procs[ child ].link.parent = parent;
189                self.procs[ parent ].link.child = child;
190            },
191            None => {
192                self.root.size.degree += 1;
193                self.procs[ child ].link.sib = self.root.child;
194                self.root.child = child;
195            },
196        }
197
198        self.root.size.descendants += node_count;
199
200        Ok(())
201    }
202
203    /// Returns all detected running processes.
204    ///
205    /// # Examples
206    ///
207    /// ```text
208    /// .............
209    /// .  ProcList .
210    /// .   /   \   .
211    /// .  1     4  .
212    /// . / \   / \ .
213    /// .2   3 5   6.
214    /// .............
215    /// ```
216    ///
217    /// This method will returns #1, #2, #3, #4, #5, #6.
218    pub fn procs( &self ) -> impl Iterator<Item=&ProcNode> {
219        self.procs.iter()
220    }
221
222    /// Returns all detected running process trees.
223    ///
224    /// # Examples
225    ///
226    /// ```text
227    /// .............
228    /// .  ProcList .
229    /// .   /   \   .
230    /// .  1     4  .
231    /// . / \   / \ .
232    /// .2   3 5   6.
233    /// .............
234    /// ```
235    ///
236    /// This method will returns #1 and #4.
237    ///
238    /// Use `.children()` recursively to get descendant processes, e.g. #2.
239    pub fn children<'a, 's:'a>( &'s self ) -> Proc<'a> {
240        Proc {
241            proc_list : self,
242            index     : self.root.child,
243            degree    : self.root.size.degree,
244        }
245    }
246
247    /// Returns processes in breadth first search. This method helps to convert
248    /// `ProcList` into `trees::Forest`.
249    ///
250    ///  See [trees](https://crates.io/crates/trees) for more.
251    pub fn bfs<'a, 's:'a>( &'s self ) -> BfsForest<Splitted<Proc<'a>>> {
252        BfsForest::from( self.children(), self.root.size )
253    }
254}
255
256impl Display for ProcList {
257    fn fmt( &self, f: &mut fmt::Formatter ) -> fmt::Result {
258        f.write_str( "[" )?;
259        let mut children = self.children();
260        if let Some( first ) = children.next() {
261            first.display( f, 1, true, true )?;
262        }
263        for child in children {
264            child.display( f, 1, false, false )?;
265        }
266        f.write_str( "]" )
267    }
268}
269
270/// A process agent by which pid,ppid,arguments/argv, parent and child
271/// processes can be accessed.
272#[derive( Copy, Clone )]
273pub struct Proc<'a> {
274    proc_list : &'a ProcList,
275    index     : usize,
276    degree    : usize,
277}
278
279impl<'a> Proc<'a> {
280    /// Gets parent process.
281    pub fn parent( &self ) -> Option<Self> {
282        let parent = self.proc_list.procs[ self.index ].link.parent;
283        if parent == Link::null() {
284            None
285        } else {
286            let degree = self.proc_list.procs[ parent ].link.size.degree;
287            Some( Proc{ proc_list: self.proc_list, index: parent, degree })
288        }
289    }
290
291    /// Gets child processes.
292    pub fn children( &self ) -> Self {
293        let link = &self.proc_list.procs[ self.index ].link;
294        Proc {
295            proc_list : self.proc_list,
296            index     : link.child,
297            degree    : link.size.degree,
298        }
299    }
300
301    fn display( &self, f: &mut fmt::Formatter, ident: usize, is_first_child: bool, is_first_line: bool ) -> fmt::Result {
302        if !is_first_line {
303            if is_first_child {
304                f.write_str("\n")?;
305            } else {
306                f.write_str(",\n")?;
307            }
308            for _ in 0..ident {
309                f.write_str(" ")?;
310            }
311        }
312        let process = &self.proc_list.procs[ self.index ].process;
313        write!( f, "{{cmd: \"{}\", pid:{}"
314            , escape8259::escape( &process.command )
315            , process.pid.0
316        )?;
317
318        let mut ends_with_pid = true;
319
320        let argv = process.argv_to_string();
321        if !argv.is_empty() {
322            ends_with_pid = false;
323            write!( f, ", args:[{}]", argv )?;
324        }
325
326        let mut subprocs = self.children();
327        if let Some( first ) = subprocs.next() {
328            ends_with_pid = false;
329            f.write_str( ", subs:[" )?;
330            first.display( f, ident+1, true, false )?;
331            for proc in subprocs {
332                proc.display( f, ident+1, false, false )?;
333            }
334            f.write_str("]")?;
335        }
336        if ends_with_pid {
337            f.write_str(" ")?;
338        }
339        f.write_str("}")
340    }
341}
342
343impl<'a> Iterator for Proc<'a> {
344    type Item = Self;
345
346    fn next( &mut self ) -> Option<Self::Item> {
347        if self.index == Link::null() {
348            None
349        } else {
350            let index = self.index;
351            let link = &self.proc_list.procs[ index ].link;
352            self.index = link.sib;
353            self.degree -= 1;
354            Some( Proc{ proc_list: self.proc_list, index, degree: link.size.degree })
355        }
356    }
357
358    fn size_hint( &self) -> (usize, Option<usize>) {
359        (self.degree, Some(self.degree) )
360    }
361}
362
363impl<'a> ExactSizeIterator for Proc<'a> {}
364
365impl<'a> Deref for Proc<'a> {
366    type Target = Process;
367
368    fn deref( &self ) -> &Process {
369        if self.index == Link::null() {
370            panic!( "bad deref to `Proc`." );
371        }
372        &self.proc_list.procs[ self.index ]
373    }
374}
375
376impl<'a> Debug for Proc<'a> {
377    fn fmt( &self, f: &mut fmt::Formatter ) -> fmt::Result {
378        self.deref().fmt( f )
379    }
380}
381
382impl<'a> Split for Proc<'a> {
383    type Item = &'a Process;
384    type Iter = Self;
385
386    fn split( self ) -> (Self::Item, Self::Iter, usize) {
387        let process = &self.proc_list.procs[ self.index ];
388        let descendants = process.link.size.descendants;
389        (process, self.children(), descendants)
390    }
391}
392
393#[doc( hidden )]
394#[derive( Debug )]
395#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
396pub struct ProcNode {
397    #[cfg_attr( target_os = "freebsd", serde( flatten ))]
398    pub process : Process,
399    #[cfg_attr( target_os = "freebsd", serde( skip ))]
400        link    : Link,
401}
402
403impl Deref for ProcNode {
404    type Target = Process;
405
406    fn deref( &self ) -> &Process { &self.process }
407}
408
409/// Process holding pid, ppid, command name and arguments of one or more
410///  nul-terminated argument(s).
411#[derive( Clone, Debug, PartialEq, Eq )]
412#[cfg_attr( target_os = "freebsd", derive( Deserialize ))]
413pub struct Process {
414    #[cfg_attr( target_os = "freebsd", serde( deserialize_with = "parse_id" ))]
415    pub pid       : Pid,
416    #[cfg_attr( target_os = "freebsd", serde( deserialize_with = "parse_id" ))]
417    pub ppid      : Pid,
418    pub command   : String,
419    pub arguments : String,
420}
421
422impl ProcNode {
423    fn id( &self ) -> Pid { self.pid }
424
425    pub(crate) fn from( process: Process ) -> Self { ProcNode{ process, link: Link::default() }}
426}
427
428fn argv_iter( argv: &str ) -> impl Iterator<Item=&str> {
429    argv.trim_end_matches('\0').split( '\0' )
430}
431
432impl Process {
433    /// Returns arguments iterator.
434    pub fn argv( &self ) -> impl Iterator<Item=&str> {
435        argv_iter( &self.arguments )
436    }
437
438    fn argv_to_string( &self ) -> String {
439        let mut s = String::new();
440        if !self.arguments.is_empty() {
441            let mut argv = self.argv();
442            if let Some( arg ) = argv.next() {
443                s.push_str( &format!( " \"{}\"", escape8259::escape( arg )));
444                for arg in argv {
445                    s.push_str( &format!( ", \"{}\"", escape8259::escape( arg )));
446                }
447                s.push(' ');
448            }
449        }
450        s
451    }
452}
453
454#[derive( Debug )]
455struct Link {
456    size   : trees::Size,
457    parent : usize,
458    child  : usize,
459    sib    : usize,
460}
461
462impl Link {
463    const fn null() -> usize { std::usize::MAX }
464}
465
466impl Default for Link {
467    fn default() -> Self {
468        Link {
469            parent : Link::null(),
470            child  : Link::null(),
471            sib    : Link::null(),
472            size   : trees::Size::default(),
473        }
474    }
475}
476
477/// Dumps running processes' arguments into a list/forest.
478#[cfg( target_os = "freebsd" )]
479pub fn pals() -> anyhow::Result<ProcList> {
480    pals_from_procfs( "/proc" )
481        .or_else( |_| pals_from_ps_json() )
482        .or_else( |_| pals_from_ps_ww() )
483}
484
485/// Dumps running processes' arguments into a list/forest.
486#[cfg( target_os = "linux" )]
487pub fn pals() -> anyhow::Result<ProcList> {
488    pals_from_procfs( "/proc" )
489        .or_else( |_| pals_from_ps_ww() )
490}
491
492/// Dumps running processes' arguments into a list/forest.
493#[cfg( target_os = "windows" )]
494pub fn pals() -> anyhow::Result<ProcList> {
495    windows::pals_from_wmic()
496}
497
498/// Dumps running processes' arguments into a list/forest.
499#[cfg( not( any( target_os = "freebsd",  target_os = "linux",  target_os = "windows" )))]
500pub fn pals() -> anyhow::Result<ProcList> {
501    pals_from_ps_ww()
502}
503
504#[cfg( not( target_os = "windows" ))]
505pub(crate) fn pals_from_ps_ww() -> anyhow::Result<ProcList> {
506    let output = String::from_utf8(
507        Command::new("ps")
508            .args( &[ "awwo", "pid,ppid,comm,args" ])
509            .output()?
510            .stdout
511    )?;
512    from_table( &output )
513}
514
515#[cfg( target_os = "freebsd" )]
516fn pals_from_ps_json() -> anyhow::Result<ProcList> {
517    let output = String::from_utf8(
518        Command::new("ps")
519            .args( &[ "a", "--libxo", "json", "-o", "pid,ppid,comm,args" ])
520            .output()?
521            .stdout
522    )?;
523    from_json( &output )
524}
525
526#[cfg( target_os = "freebsd" )]
527fn from_json( json: &str ) -> anyhow::Result<ProcList> {
528    let ps_output: PsOutput = serde_json::from_str( json )?;
529
530    build_tree( ps_output.proc_list, ArgvStatus::Parsing )
531}
532
533#[cfg( unix )]
534fn from_table( table: &str ) -> anyhow::Result<ProcList> {
535    let mut lines = table.lines();
536    let header = lines.next().context("ps command should generate some output.")?;
537
538    let ppid_col = header.find(" PID")
539        .context( "PID column should be generated by ps command.")? + 4;
540    let comm_col = header.find(" PPID")
541        .context("PPID column should be generated by ps command.")? + 5;
542    let args_col = header.rfind("COMMAND")
543        .context("COMMAND column should be generated by ps command.")?;
544
545    let procs = lines.try_fold( Vec::new(), |mut procs, line| -> anyhow::Result<Vec<ProcNode>> {
546        let bytes     = line.as_bytes();
547        let pid       = Pid( u32::from_str_radix( str::from_utf8( &bytes[         ..ppid_col ])?.trim(), 10 )? );
548        let ppid      = Pid( u32::from_str_radix( str::from_utf8( &bytes[ ppid_col..comm_col ])?.trim(), 10 )? );
549        let command   = str::from_utf8( &bytes[ comm_col..args_col ])?.trim().to_owned();
550        let arguments = str::from_utf8( &bytes[ args_col..         ])?.trim().to_owned();
551
552        procs.push( ProcNode::from( Process{ pid, ppid, command, arguments }));
553        Ok( procs )
554    })?;
555
556    build_tree( ProcList{ procs, root: Link::default() }, ArgvStatus::Parsing )
557}
558
559enum ArgvStatus {
560    Parsing,
561    #[cfg( unix )]
562    Splitted,
563}
564
565impl ArgvStatus {
566    fn needs_parsing( &self ) -> bool {
567        match self {
568            ArgvStatus::Parsing  => true,
569            #[cfg( unix )]
570            ArgvStatus::Splitted => false,
571        }
572    }
573}
574
575fn build_tree( mut proc_list: ProcList, argv_status: ArgvStatus ) -> anyhow::Result<ProcList> {
576    proc_list.procs.sort_unstable_by_key( ProcNode::id );
577
578    for i in 0..proc_list.procs.len() {
579        let parent = if proc_list.procs[i].pid != proc_list.procs[i].ppid {
580            proc_list.locate( proc_list.procs[i].ppid )
581        } else {
582            None
583        };
584        proc_list.adopt( parent, i )?;
585
586        if argv_status.needs_parsing() {
587            let arguments = &mut proc_list.procs[i].process.arguments;
588
589            #[cfg( unix )]
590            {
591                let mut args = String::with_capacity( arguments.len() + 8 );
592                mem::swap( &mut args, arguments );
593                let mut splitted_args = cmdline_words_parser::parse_posix( &mut args );
594                while let Some( arg ) = splitted_args.next() {
595                    arguments.push_str( arg );
596                    arguments.push( '\0' );
597                }
598            }
599
600            #[cfg( windows )]
601            {
602                let mut splitted_argv = win_argv( arguments );
603                mem::swap( arguments, &mut splitted_argv );
604            }
605        }
606    }
607    Ok( proc_list )
608}
609
610/// Splits command line into arguments, aka argv, using rules defined on Windows
611/// platform. See this
612/// [doc](https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85))
613/// for more.
614pub fn win_argv( cmdline: &str ) -> String {
615    let mut argv = String::new();
616
617    enum Status {
618        Backslashes( usize ),
619        Normal,
620        Spaces,
621    }
622
623    let mut arg = String::new();
624    let mut status = Status::Normal;
625    let mut quoting = false;
626
627    for ch in cmdline.chars() {
628        match status {
629            Status::Backslashes( ref mut count ) => match ch {
630                '\\' => *count += 1,
631                '"' => {
632                    for _ in 0..(*count/2) {
633                        arg.push('\\');
634                    }
635                    if *count%2 == 0 {
636                        quoting = true;
637                    } else {
638                        arg.push('"');
639                    }
640                    status = Status::Normal;
641                },
642                _ => {
643                    for _ in 0..*count {
644                        arg.push('\\');
645                    }
646                    arg.push( ch );
647                    status = Status::Normal;
648                },
649            },
650            Status::Normal => match ch {
651                '"' => quoting = !quoting,
652                ' '| '\t' => {
653                    if quoting {
654                        arg.push( ch );
655                    } else {
656                        status = Status::Spaces;
657                        argv.push_str( &arg );
658                        arg.clear();
659                        argv.push('\0');
660                    }
661                },
662                '\\' => status = Status::Backslashes( 1 ),
663                _ => arg.push( ch ),
664            },
665            Status::Spaces => match ch {
666                '"' => {
667                    status = Status::Normal;
668                    quoting = true;
669                },
670                '\\' => status = Status::Backslashes( 1 ),
671                ' ' | '\t' => (),
672                _ => {
673                    status = Status::Normal;
674                    arg.push( ch );
675                },
676            },
677        }
678    }
679
680    if !arg.is_empty() {
681        argv.push_str( &arg );
682        argv.push('\0');
683    }
684
685    argv
686}
687
688#[cfg( test )]
689mod tests {
690    #[cfg( target_os = "windows" )]
691    use crate::windows::from_utf16_file;
692
693    use std::{
694        env,
695        path::{Path, PathBuf},
696    };
697
698    use super::*;
699
700    #[cfg( target_os = "freebsd" )]
701    const JSON: &'static str = r#"{
702        "process-information": {
703            "process": [
704                {
705                    "pid": "1004",
706                    "ppid": "1002",
707                    "command": "alphabet",
708                    "arguments": "alphabet --alpha 0 --beta 1 --gamma 2"
709                },
710                {
711                    "pid": "1005",
712                    "ppid": "1001",
713                    "command": "cargo",
714                    "arguments": "cargo build --no-default-features --featurues \"nigthly no_std\""
715                },
716                {
717                    "pid": "1002",
718                    "ppid": "1001",
719                    "command": "cargo",
720                    "arguments": "cargo check"
721                },
722                {
723                    "pid": "1003",
724                    "ppid": "1002",
725                    "command": "foo",
726                    "arguments": "foo -bar -baz"
727                },
728
729                {
730                    "pid": "1006",
731                    "ppid": "1005",
732                    "command": "ping",
733                    "arguments": "ping 192.168.1.1"
734                },
735                {
736                    "pid": "1007",
737                    "ppid": "1005",
738                    "command": "ps",
739                    "arguments": "ps aux"
740                },
741                {
742                    "pid": "1001",
743                    "ppid": "999",
744                    "command": "tcsh",
745                    "arguments": "-tcsh (tcsh)"
746                }
747            ]
748        }
749}"#;
750
751    #[cfg( unix )]
752    const TABLE: &'static str = r#" PID PPID COMMAND  COMMAND
7531004 1002 alphabet alphabet --alpha 0 --beta 1 --gamma 2
7541005 1001 cargo    cargo build --no-default-features --featurues "nigthly no_std"
7551002 1001 cargo    cargo check
7561003 1002 foo      foo -bar -baz
7571006 1005 ping     ping 192.168.1.1
7581007 1005 ps       ps aux
7591001  999 tcsh     -tcsh (tcsh)"#;
760
761    #[cfg( unix )]
762    fn fake_procfs() -> PathBuf {
763        let manifest_dir = env::var( "CARGO_MANIFEST_DIR" ).unwrap();
764
765        #[cfg( target_os = "freebsd" )]
766        let path = Path::new( &manifest_dir ).join( "test/freebsd/proc" );
767
768        #[cfg( target_os = "linux" )]
769        let path = Path::new( &manifest_dir ).join( "test/linux/proc" );
770
771        path
772    }
773
774    #[cfg( target_os = "windows" )]
775    fn fake_wmic_utf16_file_path() -> PathBuf {
776        let manifest_dir = env::var( "CARGO_MANIFEST_DIR" ).unwrap();
777        Path::new( &manifest_dir ).join( "test" ).join("windows").join( "pals.output" )
778    }
779
780    #[test]
781    fn ps() {
782        #[cfg( target_os = "freebsd" )]
783        assert!( pals_from_ps_json().is_ok() );
784
785        #[cfg( unix )]
786        assert!( pals_from_ps_ww().is_ok() );
787    }
788
789    #[test]
790    fn sort() {
791        #[cfg( target_os = "freebsd" )]
792        (|| {
793            let proc_list = from_json( JSON ).unwrap();
794            assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
795                vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
796        })();
797
798        #[cfg( unix )]
799        (|| {
800            let proc_list = from_table( TABLE ).unwrap();
801            assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
802                vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
803        })();
804
805        #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
806        (|| {
807            let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
808            assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
809                vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
810        })();
811
812        #[cfg( target_os = "windows" )]
813        (|| {
814            let path = fake_wmic_utf16_file_path();
815            let proc_list = from_utf16_file( &path ).unwrap();
816            assert_eq!( proc_list.procs().map( |proc| proc.pid.0 ).collect::<Vec<_>>(),
817                vec![ 1001, 1002, 1003, 1004, 1005, 1006, 1007 ]);
818        })();
819    }
820
821    #[test]
822    fn bfs() {
823        #[cfg( target_os = "freebsd" )]
824        (|| {
825            let proc_list = from_json( JSON ).unwrap();
826            let visits = proc_list.bfs().iter
827                .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
828                .collect::<Vec<_>>();
829            assert_eq!( visits, vec![
830                (1001, 2, 6),
831                (1005, 2, 2),
832                (1002, 2, 2),
833                (1007, 0, 0),
834                (1006, 0, 0),
835                (1004, 0, 0),
836                (1003, 0, 0),
837            ]);
838        })();
839
840        #[cfg( unix )]
841        (|| {
842            let proc_list = from_table( TABLE ).unwrap();
843            let visits = proc_list.bfs().iter
844                .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
845                .collect::<Vec<_>>();
846            assert_eq!( visits, vec![
847                (1001, 2, 6),
848                (1005, 2, 2),
849                (1002, 2, 2),
850                (1007, 0, 0),
851                (1006, 0, 0),
852                (1004, 0, 0),
853                (1003, 0, 0),
854            ]);
855        })();
856
857        #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
858        (|| {
859            let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
860            let visits = proc_list.bfs().iter
861                .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
862                .collect::<Vec<_>>();
863            assert_eq!( visits, vec![
864                (1001, 2, 6),
865                (1005, 2, 2),
866                (1002, 2, 2),
867                (1007, 0, 0),
868                (1006, 0, 0),
869                (1004, 0, 0),
870                (1003, 0, 0),
871            ]);
872        })();
873
874        #[cfg( target_os = "windows" )]
875        (|| {
876            let path = fake_wmic_utf16_file_path();
877            let proc_list = from_utf16_file( &path ).unwrap();
878            let visits = proc_list.bfs().iter
879                .map( |visit| (visit.data.pid.0, visit.size.degree, visit.size.descendants) )
880                .collect::<Vec<_>>();
881            assert_eq!( visits, vec![
882                (1001, 2, 6),
883                (1005, 2, 2),
884                (1002, 2, 2),
885                (1007, 0, 0),
886                (1006, 0, 0),
887                (1004, 0, 0),
888                (1003, 0, 0),
889            ]);
890        })();
891    }
892
893    #[test]
894    fn forest() {
895        macro_rules! process {
896            ($pid:expr, $ppid:expr, $comm:expr, $args:expr) => {
897                Process{ pid: Pid($pid), ppid: Pid($ppid), command: $comm.to_owned(), arguments: $args.to_owned(), }
898            }
899        }
900
901        let p1 = &process!( 1001,  999, "tcsh"    , "-tcsh\0(tcsh)\0"                                                    );
902        let p5 = &process!( 1005, 1001, "cargo"   , "cargo\0build\0--no-default-features\0--featurues\0nigthly no_std\0" );
903        let p2 = &process!( 1002, 1001, "cargo"   , "cargo\0check\0"                                                     );
904        let p7 = &process!( 1007, 1005, "ps"      , "ps\0aux\0"                                                          );
905        let p6 = &process!( 1006, 1005, "ping"    , "ping\0192.168.1.1\0"                                                );
906        let p4 = &process!( 1004, 1002, "alphabet", "alphabet\0--alpha\00\0--beta\01\0--gamma\02\0"                      );
907        let p3 = &process!( 1003, 1002, "foo"     , "foo\0-bar\0-baz\0"                                                  );
908
909        #[cfg( target_os = "freebsd" )]
910        (|| {
911            let proc_list = from_json( JSON ).unwrap();
912            let bfs = proc_list.bfs();
913            let forest = trees::Forest::<&Process>::from( bfs );
914            let expected = trees::Forest::<&Process>::from_tuple((
915                (p1, (p5, p7, p6), (p2, p4, p3), ),
916            ));
917            assert_eq!( forest, expected );
918        })();
919
920        #[cfg( unix )]
921        (|| {
922            let proc_list = from_table( TABLE ).unwrap();
923            let bfs = proc_list.bfs();
924            let forest = trees::Forest::<&Process>::from( bfs );
925            let expected = trees::Forest::<&Process>::from_tuple((
926                (p1, (p5, p7, p6), (p2, p4, p3), ),
927            ));
928            assert_eq!( forest, expected );
929        })();
930
931        #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
932        (|| {
933            let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
934            let bfs = proc_list.bfs();
935            let forest = trees::Forest::<&Process>::from( bfs );
936            let expected = trees::Forest::<&Process>::from_tuple((
937                (p1, (p5, p7, p6), (p2, p4, p3), ),
938            ));
939            assert_eq!( forest, expected );
940        })();
941
942        #[cfg( target_os = "windows" )]
943        (|| {
944            let path = fake_wmic_utf16_file_path();
945            let proc_list = from_utf16_file( &path ).unwrap();
946            let bfs = proc_list.bfs();
947            let forest = trees::Forest::<&Process>::from( bfs );
948            let expected = trees::Forest::<&Process>::from_tuple((
949                (p1, (p5, p7, p6), (p2, p4, p3), ),
950            ));
951            assert_eq!( forest, expected );
952        })();
953    }
954
955    #[test]
956    fn win_argv_works() {
957        assert_eq!( argv_iter( &win_argv( r#""abc" d e"#       )).collect::<Vec<_>>(), vec![  "abc"    , "d"    , "e" ]);
958        assert_eq!( argv_iter( &win_argv( r#"a\\\b d"e f"g h"# )).collect::<Vec<_>>(), vec![r#"a\\\b"# , "de fg", "h" ]);
959        assert_eq!( argv_iter( &win_argv( r#"a\\\"b c d"#      )).collect::<Vec<_>>(), vec![r#"a\"b"#  , "c"    , "d" ]);
960        assert_eq!( argv_iter( &win_argv( r#"a\\\\"b c" d e"#  )).collect::<Vec<_>>(), vec![r#"a\\b c"#, "d"    , "e" ]);
961    }
962
963    #[test]
964    fn display() {
965
966        #[cfg( any( target_os = "freebsd", target_os = "linux" ))]
967        {
968            let proc_list = pals_from_procfs( &fake_procfs() ).unwrap();
969            assert_eq!( proc_list.to_string(),
970r#"[{cmd: "tcsh", pid:1001, args:[ "-tcsh", "(tcsh)" ], subs:[
971  {cmd: "cargo", pid:1005, args:[ "cargo", "build", "--no-default-features", "--featurues", "nigthly no_std" ], subs:[
972   {cmd: "ps", pid:1007, args:[ "ps", "aux" ]},
973   {cmd: "ping", pid:1006, args:[ "ping", "192.168.1.1" ]}]},
974  {cmd: "cargo", pid:1002, args:[ "cargo", "check" ], subs:[
975   {cmd: "alphabet", pid:1004, args:[ "alphabet", "--alpha", "0", "--beta", "1", "--gamma", "2" ]},
976   {cmd: "foo", pid:1003, args:[ "foo", "-bar", "-baz" ]}]}]}]"# );
977        }
978    }
979}