2020-01-18 11:38:21 +03:00
/*
2021-05-23 12:26:19 +03:00
* Copyright ( c ) 2018 - 2021 , Andreas Kling < kling @ serenityos . org >
2023-05-15 17:44:16 +03:00
* Copyright ( c ) 2023 , Sam Atkins < atkinssj @ serenityos . org >
2023-07-09 17:27:36 +03:00
* Copyright ( c ) 2023 , Tim Ledbetter < timledbetter @ gmail . com >
2020-01-18 11:38:21 +03:00
*
2021-04-22 11:24:48 +03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 11:38:21 +03:00
*/
2021-05-23 12:26:19 +03:00
# include <AK/QuickSort.h>
2023-05-15 17:44:16 +03:00
# include <AK/String.h>
2023-05-15 19:28:48 +03:00
# include <LibCore/Account.h>
2020-03-01 19:46:06 +03:00
# include <LibCore/ArgsParser.h>
2020-02-06 17:04:03 +03:00
# include <LibCore/ProcessStatisticsReader.h>
2021-11-23 17:42:17 +03:00
# include <LibCore/System.h>
# include <LibMain/Main.h>
2022-03-26 09:22:45 +03:00
# include <sys/sysmacros.h>
2018-11-17 17:56:09 +03:00
# include <unistd.h>
2018-10-23 12:57:38 +03:00
2023-07-10 20:44:10 +03:00
static ErrorOr < Optional < String > > tty_stat_to_pseudo_name ( struct stat tty_stat )
2022-03-26 09:22:45 +03:00
{
int tty_device_major = major ( tty_stat . st_rdev ) ;
int tty_device_minor = minor ( tty_stat . st_rdev ) ;
2022-03-30 11:22:27 +03:00
2023-07-10 20:44:10 +03:00
if ( tty_device_major = = 201 )
2023-05-15 17:44:16 +03:00
return String : : formatted ( " pts:{} " , tty_device_minor ) ;
2022-03-30 11:22:27 +03:00
2023-07-10 20:44:10 +03:00
if ( tty_device_major = = 4 )
2023-05-15 17:44:16 +03:00
return String : : formatted ( " tty:{} " , tty_device_minor ) ;
2023-07-10 20:44:10 +03:00
return OptionalNone { } ;
}
static ErrorOr < String > determine_tty_pseudo_name ( )
{
auto tty_stat = TRY ( Core : : System : : fstat ( STDIN_FILENO ) ) ;
auto maybe_tty_pseudo_name = TRY ( tty_stat_to_pseudo_name ( tty_stat ) ) ;
return maybe_tty_pseudo_name . value_or ( " n/a " _short_string ) ;
}
static ErrorOr < String > parse_tty_pseudo_name ( StringView tty_name )
{
auto tty_name_parts = tty_name . split_view ( " : " sv , AK : : SplitBehavior : : KeepEmpty ) ;
String tty_full_name ;
StringView tty_full_name_view ;
if ( tty_name_parts . size ( ) = = 1 ) {
tty_full_name_view = tty_name ;
} else {
if ( tty_name_parts . size ( ) ! = 2 )
return Error : : from_errno ( ENOTTY ) ;
auto tty_device_type = tty_name_parts [ 0 ] ;
auto tty_number = tty_name_parts [ 1 ] ;
if ( tty_device_type = = " tty " sv )
tty_full_name = TRY ( String : : formatted ( " /dev/tty{} " , tty_number ) ) ;
else if ( tty_device_type = = " pts " sv )
tty_full_name = TRY ( String : : formatted ( " /dev/pts/{} " , tty_number ) ) ;
else
return Error : : from_errno ( ENOTTY ) ;
tty_full_name_view = tty_full_name . bytes_as_string_view ( ) ;
2022-03-26 09:22:45 +03:00
}
2023-07-10 20:44:10 +03:00
auto tty_stat = TRY ( Core : : System : : stat ( tty_full_name_view ) ) ;
auto maybe_tty_pseudo_name = TRY ( tty_stat_to_pseudo_name ( tty_stat ) ) ;
if ( ! maybe_tty_pseudo_name . has_value ( ) )
return Error : : from_errno ( ENOTTY ) ;
return maybe_tty_pseudo_name . release_value ( ) ;
2022-03-26 09:22:45 +03:00
}
2023-05-15 16:59:26 +03:00
template < typename Value , typename ParseValue >
Core : : ArgsParser : : Option make_list_option ( Vector < Value > & value_list , char const * help_string , char const * long_name , char short_name , char const * value_name , ParseValue parse_value )
{
return Core : : ArgsParser : : Option {
. argument_mode = Core : : ArgsParser : : OptionArgumentMode : : Required ,
. help_string = help_string ,
. long_name = long_name ,
. short_name = short_name ,
. value_name = value_name ,
. accept_value = [ & ] ( StringView s ) {
auto parts = s . split_view_if ( [ ] ( char c ) { return c = = ' , ' | | c = = ' ' ; } ) ;
for ( auto const & part : parts ) {
auto value = parse_value ( part ) ;
if ( ! value . has_value ( ) )
return false ;
value_list . append ( value . value ( ) ) ;
}
return true ;
} ,
} ;
}
2021-11-23 17:42:17 +03:00
ErrorOr < int > serenity_main ( Main : : Arguments arguments )
2018-10-23 12:57:38 +03:00
{
2021-11-28 01:26:34 +03:00
TRY ( Core : : System : : pledge ( " stdio rpath tty " ) ) ;
2022-03-26 09:22:45 +03:00
auto this_pseudo_tty_name = TRY ( determine_tty_pseudo_name ( ) ) ;
2020-03-01 19:46:06 +03:00
2021-11-28 01:26:34 +03:00
TRY ( Core : : System : : pledge ( " stdio rpath " ) ) ;
2022-10-14 21:56:19 +03:00
TRY ( Core : : System : : unveil ( " /sys/kernel/processes " , " r " ) ) ;
2021-11-23 17:42:17 +03:00
TRY ( Core : : System : : unveil ( " /etc/passwd " , " r " ) ) ;
2023-05-15 19:28:48 +03:00
TRY ( Core : : System : : unveil ( " /etc/group " , " r " ) ) ;
2023-07-10 20:44:10 +03:00
TRY ( Core : : System : : unveil ( " /dev/ " , " r " ) ) ;
2021-11-23 17:42:17 +03:00
TRY ( Core : : System : : unveil ( nullptr , nullptr ) ) ;
2020-02-18 12:42:04 +03:00
2020-03-01 19:46:06 +03:00
enum class Alignment {
Left ,
Right ,
} ;
struct Column {
2023-05-15 17:44:16 +03:00
String title ;
2020-03-01 19:46:06 +03:00
Alignment alignment { Alignment : : Left } ;
int width { 0 } ;
2023-05-15 17:44:16 +03:00
String buffer ;
2020-03-01 19:46:06 +03:00
} ;
bool every_process_flag = false ;
2023-05-14 15:44:40 +03:00
bool every_terminal_process_flag = false ;
2020-03-01 19:46:06 +03:00
bool full_format_flag = false ;
2023-07-09 17:27:36 +03:00
bool provided_filtering_option = false ;
2023-05-15 17:32:06 +03:00
bool provided_quick_pid_list = false ;
2023-05-15 16:59:26 +03:00
Vector < pid_t > pid_list ;
2023-07-10 22:47:55 +03:00
Vector < pid_t > parent_pid_list ;
2023-07-10 20:44:10 +03:00
Vector < DeprecatedString > tty_list ;
2023-05-15 19:28:48 +03:00
Vector < uid_t > uid_list ;
2020-03-01 19:46:06 +03:00
Core : : ArgsParser args_parser ;
2023-05-14 15:44:40 +03:00
args_parser . add_option ( every_terminal_process_flag , " Show every process associated with terminals " , nullptr , ' a ' ) ;
2023-05-14 15:38:48 +03:00
args_parser . add_option ( every_process_flag , " Show every process " , nullptr , ' A ' ) ;
args_parser . add_option ( every_process_flag , " Show every process (Equivalent to -A) " , nullptr , ' e ' ) ;
2020-03-01 19:46:06 +03:00
args_parser . add_option ( full_format_flag , " Full format " , nullptr , ' f ' ) ;
2023-05-15 17:32:06 +03:00
args_parser . add_option ( make_list_option ( pid_list , " Show processes with a matching PID. (Comma- or space-separated list) " , nullptr , ' p ' , " pid-list " , [ & ] ( StringView pid_string ) {
2023-07-09 17:27:36 +03:00
provided_filtering_option = true ;
2023-05-15 17:32:06 +03:00
auto pid = pid_string . to_int ( ) ;
if ( ! pid . has_value ( ) )
warnln ( " Could not parse '{}' as a PID. " , pid_string ) ;
return pid ;
} ) ) ;
2023-07-10 22:47:55 +03:00
args_parser . add_option ( make_list_option ( parent_pid_list , " Show processes with a matching PPID. (Comma- or space-separated list.) " , " ppid " , { } , " pid-list " , [ & ] ( StringView pid_string ) {
provided_filtering_option = true ;
auto pid = pid_string . to_int ( ) ;
if ( ! pid . has_value ( ) )
warnln ( " Could not parse '{}' as a PID. " , pid_string ) ;
return pid ;
} ) ) ;
2023-05-15 17:32:06 +03:00
args_parser . add_option ( make_list_option ( pid_list , " Show processes with a matching PID. (Comma- or space-separated list.) Processes will be listed in the order given. " , nullptr , ' q ' , " pid-list " , [ & ] ( StringView pid_string ) {
provided_quick_pid_list = true ;
2023-05-15 16:59:26 +03:00
auto pid = pid_string . to_int ( ) ;
if ( ! pid . has_value ( ) )
warnln ( " Could not parse '{}' as a PID. " , pid_string ) ;
return pid ;
} ) ) ;
2023-07-10 20:44:10 +03:00
args_parser . add_option ( make_list_option ( tty_list , " Show processes associated with the given terminal. (Comma- or space-separated list.) The short TTY name or the full device path may be used. " , " tty " , ' t ' , " tty-list " , [ & ] ( StringView tty_string ) - > Optional < DeprecatedString > {
provided_filtering_option = true ;
auto tty_pseudo_name_or_error = parse_tty_pseudo_name ( tty_string ) ;
if ( tty_pseudo_name_or_error . is_error ( ) ) {
warnln ( " Could not parse '{}' as a TTY " , tty_string ) ;
return { } ;
}
return tty_pseudo_name_or_error . release_value ( ) . to_deprecated_string ( ) ;
} ) ) ;
2023-05-15 19:28:48 +03:00
args_parser . add_option ( make_list_option ( uid_list , " Show processes with a matching user ID or login name. (Comma- or space-separated list.) " , nullptr , ' u ' , " user-list " , [ & ] ( StringView user_string ) - > Optional < uid_t > {
2023-07-09 17:27:36 +03:00
provided_filtering_option = true ;
2023-05-15 19:28:48 +03:00
if ( auto uid = user_string . to_uint < uid_t > ( ) ; uid . has_value ( ) ) {
return uid . value ( ) ;
}
auto maybe_account = Core : : Account : : from_name ( user_string , Core : : Account : : Read : : PasswdOnly ) ;
if ( maybe_account . is_error ( ) ) {
warnln ( " Could not find user '{}': {} " , user_string , maybe_account . error ( ) ) ;
return { } ;
}
return maybe_account . value ( ) . uid ( ) ;
} ) ) ;
2021-11-23 17:42:17 +03:00
args_parser . parse ( arguments ) ;
2020-03-01 19:46:06 +03:00
2023-07-09 17:27:36 +03:00
if ( provided_filtering_option & & provided_quick_pid_list ) {
warnln ( " The -q option cannot be combined with other filtering options. " ) ;
2023-05-15 17:32:06 +03:00
return 1 ;
}
2020-03-01 19:46:06 +03:00
Vector < Column > columns ;
2019-06-02 13:07:24 +03:00
2023-05-14 15:20:40 +03:00
Optional < size_t > uid_column ;
Optional < size_t > pid_column ;
Optional < size_t > ppid_column ;
Optional < size_t > pgid_column ;
Optional < size_t > sid_column ;
Optional < size_t > state_column ;
Optional < size_t > tty_column ;
Optional < size_t > cmd_column ;
2020-03-01 19:46:06 +03:00
2021-06-03 18:08:03 +03:00
auto add_column = [ & ] ( auto title , auto alignment ) {
2023-05-14 15:16:49 +03:00
columns . unchecked_append ( { title , alignment , 0 , { } } ) ;
2020-03-01 19:46:06 +03:00
return columns . size ( ) - 1 ;
} ;
if ( full_format_flag ) {
2023-05-14 15:16:49 +03:00
TRY ( columns . try_ensure_capacity ( 8 ) ) ;
2023-05-15 17:44:16 +03:00
uid_column = add_column ( " UID " _short_string , Alignment : : Left ) ;
pid_column = add_column ( " PID " _short_string , Alignment : : Right ) ;
ppid_column = add_column ( " PPID " _short_string , Alignment : : Right ) ;
pgid_column = add_column ( " PGID " _short_string , Alignment : : Right ) ;
sid_column = add_column ( " SID " _short_string , Alignment : : Right ) ;
state_column = add_column ( " STATE " _short_string , Alignment : : Left ) ;
tty_column = add_column ( " TTY " _short_string , Alignment : : Left ) ;
cmd_column = add_column ( " CMD " _short_string , Alignment : : Left ) ;
2020-03-01 19:46:06 +03:00
} else {
2023-05-14 15:16:49 +03:00
TRY ( columns . try_ensure_capacity ( 3 ) ) ;
2023-05-15 17:44:16 +03:00
pid_column = add_column ( " PID " _short_string , Alignment : : Right ) ;
tty_column = add_column ( " TTY " _short_string , Alignment : : Left ) ;
cmd_column = add_column ( " CMD " _short_string , Alignment : : Left ) ;
2020-03-01 19:46:06 +03:00
}
2022-12-08 16:50:31 +03:00
auto all_processes = TRY ( Core : : ProcessStatisticsReader : : get_all ( ) ) ;
2019-07-17 22:24:47 +03:00
2022-12-08 16:50:31 +03:00
auto & processes = all_processes . processes ;
2021-08-28 12:11:51 +03:00
2023-05-15 22:25:06 +03:00
// Filter
2023-07-09 18:26:08 +03:00
if ( ! every_process_flag ) {
Vector < Core : : ProcessStatistics > filtered_processes ;
for ( auto const & process : processes ) {
// Default is to show processes from the current TTY
if ( ( ! provided_filtering_option & & process . tty = = this_pseudo_tty_name . bytes_as_string_view ( ) )
| | ( ! pid_list . is_empty ( ) & & pid_list . contains_slow ( process . pid ) )
2023-07-10 22:47:55 +03:00
| | ( ! parent_pid_list . is_empty ( ) & & parent_pid_list . contains_slow ( process . ppid ) )
2023-07-09 18:26:08 +03:00
| | ( ! uid_list . is_empty ( ) & & uid_list . contains_slow ( process . uid ) )
2023-07-10 20:44:10 +03:00
| | ( ! tty_list . is_empty ( ) & & tty_list . contains_slow ( process . tty ) )
2023-07-09 18:26:08 +03:00
| | ( every_terminal_process_flag & & ! process . tty . is_empty ( ) ) ) {
filtered_processes . append ( process ) ;
}
}
processes = move ( filtered_processes ) ;
2023-05-15 17:32:06 +03:00
}
2021-08-28 12:11:51 +03:00
2023-05-15 22:25:06 +03:00
// Sort
2023-05-15 17:32:06 +03:00
if ( provided_quick_pid_list ) {
2023-05-15 16:59:26 +03:00
auto processes_sort_predicate = [ & pid_list ] ( auto & a , auto & b ) {
return pid_list . find_first_index ( a . pid ) . value ( ) < pid_list . find_first_index ( b . pid ) . value ( ) ;
2021-08-28 12:13:15 +03:00
} ;
quick_sort ( processes , processes_sort_predicate ) ;
} else {
quick_sort ( processes , [ ] ( auto & a , auto & b ) { return a . pid < b . pid ; } ) ;
}
2021-05-23 12:26:19 +03:00
2023-05-15 17:44:16 +03:00
Vector < Vector < String > > rows ;
2021-11-23 17:42:17 +03:00
TRY ( rows . try_ensure_capacity ( 1 + processes . size ( ) ) ) ;
2021-06-03 18:08:03 +03:00
2023-05-15 17:44:16 +03:00
Vector < String > header ;
2021-11-23 17:42:17 +03:00
TRY ( header . try_ensure_capacity ( columns . size ( ) ) ) ;
2021-06-03 18:08:03 +03:00
for ( auto & column : columns )
2021-11-23 17:42:17 +03:00
header . unchecked_append ( column . title ) ;
2023-05-14 15:16:49 +03:00
rows . unchecked_append ( move ( header ) ) ;
2021-06-03 18:08:03 +03:00
2021-07-14 21:05:59 +03:00
for ( auto const & process : processes ) {
2023-05-15 17:44:16 +03:00
Vector < String > row ;
2021-11-23 17:42:17 +03:00
TRY ( row . try_resize ( columns . size ( ) ) ) ;
2021-06-03 18:08:03 +03:00
2023-05-14 15:20:40 +03:00
if ( uid_column . has_value ( ) )
row [ * uid_column ] = TRY ( String : : from_deprecated_string ( process . username ) ) ;
if ( pid_column . has_value ( ) )
row [ * pid_column ] = TRY ( String : : number ( process . pid ) ) ;
if ( ppid_column . has_value ( ) )
row [ * ppid_column ] = TRY ( String : : number ( process . ppid ) ) ;
if ( pgid_column . has_value ( ) )
row [ * pgid_column ] = TRY ( String : : number ( process . pgid ) ) ;
if ( sid_column . has_value ( ) )
row [ * sid_column ] = TRY ( String : : number ( process . sid ) ) ;
if ( tty_column . has_value ( ) )
2023-05-15 22:25:06 +03:00
row [ * tty_column ] = process . tty = = " " ? " n/a " _short_string : TRY ( String : : from_deprecated_string ( process . tty ) ) ;
2023-05-14 15:20:40 +03:00
if ( state_column . has_value ( ) )
row [ * state_column ] = process . threads . is_empty ( )
2023-05-15 17:44:16 +03:00
? " Zombie " _short_string
: TRY ( String : : from_deprecated_string ( process . threads . first ( ) . state ) ) ;
2023-05-14 15:20:40 +03:00
if ( cmd_column . has_value ( ) )
row [ * cmd_column ] = TRY ( String : : from_deprecated_string ( process . name ) ) ;
2021-06-03 18:08:03 +03:00
2023-05-14 15:16:49 +03:00
rows . unchecked_append ( move ( row ) ) ;
2021-06-03 18:08:03 +03:00
}
2020-03-01 19:46:06 +03:00
2021-06-03 18:08:03 +03:00
for ( size_t i = 0 ; i < columns . size ( ) ; i + + ) {
auto & column = columns [ i ] ;
for ( auto & row : rows )
2023-05-15 17:44:16 +03:00
column . width = max ( column . width , static_cast < int > ( row [ i ] . code_points ( ) . length ( ) ) ) ;
2021-06-03 18:08:03 +03:00
}
for ( auto & row : rows ) {
for ( size_t i = 0 ; i < columns . size ( ) ; i + + ) {
auto & column = columns [ i ] ;
auto & cell_text = row [ i ] ;
if ( ! column . width ) {
out ( " {} " , cell_text ) ;
continue ;
}
if ( column . alignment = = Alignment : : Right )
out ( " {1:>{0}} " , column . width , cell_text ) ;
else
out ( " {1:{0}} " , column . width , cell_text ) ;
if ( i ! = columns . size ( ) - 1 )
out ( " " ) ;
}
2021-05-31 17:43:25 +03:00
outln ( ) ;
2018-10-23 12:57:38 +03:00
}
2019-07-17 22:24:47 +03:00
2018-10-23 12:57:38 +03:00
return 0 ;
}