2020-05-16 19:05:13 +00:00
/*
2022-01-29 15:24:01 +00:00
* Copyright ( c ) 2020 - 2022 , the SerenityOS developers .
2020-05-16 19:05:13 +00:00
*
2021-04-22 08:24:48 +00:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-05-16 19:05:13 +00:00
*/
# include "Shell.h"
# include "Execution.h"
2020-09-16 00:43:04 +00:00
# include "Formatter.h"
2021-06-01 19:18:08 +00:00
# include <AK/CharacterTypes.h>
2021-01-24 14:28:26 +00:00
# include <AK/Debug.h>
2020-05-16 19:05:13 +00:00
# include <AK/Function.h>
2021-08-18 11:22:52 +00:00
# include <AK/GenericLexer.h>
2022-03-22 20:44:48 +00:00
# include <AK/JsonParser.h>
2020-05-26 11:52:44 +00:00
# include <AK/LexicalPath.h>
2021-05-29 18:06:44 +00:00
# include <AK/QuickSort.h>
2020-05-16 19:05:13 +00:00
# include <AK/ScopeGuard.h>
2021-03-12 16:29:37 +00:00
# include <AK/ScopedValueRollback.h>
2020-05-16 19:05:13 +00:00
# include <AK/StringBuilder.h>
2020-10-15 18:46:52 +00:00
# include <AK/TemporaryChange.h>
2021-05-29 21:15:54 +00:00
# include <AK/URL.h>
2020-05-16 19:05:13 +00:00
# include <LibCore/DirIterator.h>
# include <LibCore/Event.h>
# include <LibCore/EventLoop.h>
2023-05-27 10:52:14 +00:00
# include <LibCore/File.h>
2021-12-21 02:48:19 +00:00
# include <LibCore/System.h>
2022-03-22 20:44:48 +00:00
# include <LibCore/Timer.h>
2023-05-07 19:10:29 +00:00
# include <LibFileSystem/FileSystem.h>
2020-05-16 19:05:13 +00:00
# include <LibLine/Editor.h>
2023-02-11 14:29:15 +00:00
# include <Shell/PosixParser.h>
2020-05-16 19:05:13 +00:00
# include <errno.h>
# include <fcntl.h>
2020-08-11 11:24:16 +00:00
# include <inttypes.h>
2020-05-16 19:05:13 +00:00
# include <pwd.h>
# include <signal.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/mman.h>
# include <sys/stat.h>
# include <sys/utsname.h>
2020-09-23 05:36:30 +00:00
# include <sys/wait.h>
2020-05-16 19:05:13 +00:00
# include <termios.h>
# include <unistd.h>
static bool s_disable_hyperlinks = false ;
2020-07-14 18:52:52 +00:00
extern char * * environ ;
2020-05-16 19:05:13 +00:00
2020-10-01 14:43:01 +00:00
namespace Shell {
2020-09-23 05:36:30 +00:00
void Shell : : setup_signals ( )
{
2020-12-07 16:07:04 +00:00
if ( m_should_reinstall_signal_handlers ) {
Core : : EventLoop : : register_signal ( SIGCHLD , [ this ] ( int ) {
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " SIGCHLD! " ) ;
2020-12-07 16:07:04 +00:00
notify_child_event ( ) ;
} ) ;
Core : : EventLoop : : register_signal ( SIGTSTP , [ this ] ( auto ) {
auto job = current_job ( ) ;
kill_job ( job , SIGTSTP ) ;
if ( job ) {
job - > set_is_suspended ( true ) ;
job - > unblock ( ) ;
}
} ) ;
}
2020-09-23 05:36:30 +00:00
}
2022-01-29 15:24:01 +00:00
void Shell : : print_path ( StringView path )
2020-05-16 19:05:13 +00:00
{
2020-08-05 05:30:01 +00:00
if ( s_disable_hyperlinks | | ! m_is_interactive ) {
2022-01-29 15:24:01 +00:00
out ( " {} " , path ) ;
2020-05-16 19:05:13 +00:00
return ;
}
2021-05-29 21:15:54 +00:00
auto url = URL : : create_with_file_scheme ( path , { } , hostname ) ;
out ( " \033 ]8;;{} \033 \\ {} \033 ]8;; \033 \\ " , url . serialize ( ) , path ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : prompt ( ) const
2020-05-16 19:05:13 +00:00
{
2023-08-31 10:04:21 +00:00
if ( m_next_scheduled_prompt_text . has_value ( ) )
return m_next_scheduled_prompt_text . release_value ( ) ;
2022-12-04 18:02:33 +00:00
auto build_prompt = [ & ] ( ) - > DeprecatedString {
2020-05-16 19:05:13 +00:00
auto * ps1 = getenv ( " PROMPT " ) ;
if ( ! ps1 ) {
if ( uid = = 0 )
return " # " ;
StringBuilder builder ;
2021-05-07 18:45:21 +00:00
builder . appendff ( " \033 ]0;{}@{}:{} \007 " , username , hostname , cwd ) ;
builder . appendff ( " \033 [31;1m{} \033 [0m@ \033 [37;1m{} \033 [0m: \033 [32;1m{} \033 [0m$> " , username , hostname , cwd ) ;
2022-12-06 01:12:49 +00:00
return builder . to_deprecated_string ( ) ;
2020-05-16 19:05:13 +00:00
}
StringBuilder builder ;
for ( char * ptr = ps1 ; * ptr ; + + ptr ) {
if ( * ptr = = ' \\ ' ) {
+ + ptr ;
if ( ! * ptr )
break ;
switch ( * ptr ) {
case ' X ' :
2022-07-11 17:32:29 +00:00
builder . append ( " \033 ]0; " sv ) ;
2020-05-16 19:05:13 +00:00
break ;
case ' a ' :
builder . append ( 0x07 ) ;
break ;
case ' e ' :
builder . append ( 0x1b ) ;
break ;
case ' u ' :
builder . append ( username ) ;
break ;
case ' h ' :
2022-07-11 19:53:29 +00:00
builder . append ( { hostname , strlen ( hostname ) } ) ;
2020-05-16 19:05:13 +00:00
break ;
case ' w ' : {
2022-12-04 18:02:33 +00:00
DeprecatedString home_path = getenv ( " HOME " ) ;
2020-05-16 19:05:13 +00:00
if ( cwd . starts_with ( home_path ) ) {
builder . append ( ' ~ ' ) ;
builder . append ( cwd . substring_view ( home_path . length ( ) , cwd . length ( ) - home_path . length ( ) ) ) ;
} else {
builder . append ( cwd ) ;
}
break ;
}
case ' p ' :
builder . append ( uid = = 0 ? ' # ' : ' $ ' ) ;
break ;
}
continue ;
}
builder . append ( * ptr ) ;
}
2022-12-06 01:12:49 +00:00
return builder . to_deprecated_string ( ) ;
2020-05-16 19:05:13 +00:00
} ;
2020-06-17 13:35:06 +00:00
return build_prompt ( ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : expand_tilde ( StringView expression )
2020-05-16 19:05:13 +00:00
{
2021-02-23 19:42:32 +00:00
VERIFY ( expression . starts_with ( ' ~ ' ) ) ;
2020-05-16 19:05:13 +00:00
StringBuilder login_name ;
size_t first_slash_index = expression . length ( ) ;
for ( size_t i = 1 ; i < expression . length ( ) ; + + i ) {
if ( expression [ i ] = = ' / ' ) {
first_slash_index = i ;
break ;
}
login_name . append ( expression [ i ] ) ;
}
StringBuilder path ;
for ( size_t i = first_slash_index ; i < expression . length ( ) ; + + i )
path . append ( expression [ i ] ) ;
if ( login_name . is_empty ( ) ) {
2022-04-01 17:58:27 +00:00
char const * home = getenv ( " HOME " ) ;
2020-05-16 19:05:13 +00:00
if ( ! home ) {
auto passwd = getpwuid ( getuid ( ) ) ;
2021-02-23 19:42:32 +00:00
VERIFY ( passwd & & passwd - > pw_dir ) ;
2022-12-06 01:12:49 +00:00
return DeprecatedString : : formatted ( " {}/{} " , passwd - > pw_dir , path . to_deprecated_string ( ) ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-06 01:12:49 +00:00
return DeprecatedString : : formatted ( " {}/{} " , home , path . to_deprecated_string ( ) ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-06 01:12:49 +00:00
auto passwd = getpwnam ( login_name . to_deprecated_string ( ) . characters ( ) ) ;
2020-05-16 19:05:13 +00:00
if ( ! passwd )
return expression ;
2021-02-23 19:42:32 +00:00
VERIFY ( passwd - > pw_dir ) ;
2020-05-16 19:05:13 +00:00
2022-12-06 01:12:49 +00:00
return DeprecatedString : : formatted ( " {}/{} " , passwd - > pw_dir , path . to_deprecated_string ( ) ) ;
2020-05-16 19:05:13 +00:00
}
2021-11-10 23:55:02 +00:00
bool Shell : : is_glob ( StringView s )
2020-05-16 19:05:13 +00:00
{
for ( size_t i = 0 ; i < s . length ( ) ; i + + ) {
char c = s . characters_without_null_termination ( ) [ i ] ;
if ( c = = ' * ' | | c = = ' ? ' )
return true ;
}
return false ;
}
2021-11-10 23:55:02 +00:00
Vector < StringView > Shell : : split_path ( StringView path )
2020-05-16 19:05:13 +00:00
{
Vector < StringView > parts ;
size_t substart = 0 ;
for ( size_t i = 0 ; i < path . length ( ) ; i + + ) {
2020-06-17 13:35:06 +00:00
char ch = path [ i ] ;
2020-05-16 19:05:13 +00:00
if ( ch ! = ' / ' )
continue ;
size_t sublen = i - substart ;
if ( sublen ! = 0 )
parts . append ( path . substring_view ( substart , sublen ) ) ;
substart = i + 1 ;
}
size_t taillen = path . length ( ) - substart ;
if ( taillen ! = 0 )
parts . append ( path . substring_view ( substart , taillen ) ) ;
return parts ;
}
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > Shell : : expand_globs ( StringView path , StringView base )
2020-05-16 19:05:13 +00:00
{
2020-09-18 21:48:44 +00:00
auto explicitly_set_base = false ;
if ( path . starts_with ( ' / ' ) ) {
2022-07-11 17:32:29 +00:00
base = " / " sv ;
2020-09-18 21:48:44 +00:00
explicitly_set_base = true ;
}
2020-05-16 19:05:13 +00:00
auto parts = split_path ( path ) ;
2022-12-04 18:02:33 +00:00
DeprecatedString base_string = base ;
2020-06-17 13:35:06 +00:00
struct stat statbuf ;
if ( lstat ( base_string . characters ( ) , & statbuf ) < 0 ) {
perror ( " lstat " ) ;
return { } ;
}
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
StringBuilder resolved_base_path_builder ;
2023-05-27 11:15:03 +00:00
resolved_base_path_builder . append ( FileSystem : : real_path ( base ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2020-06-17 13:35:06 +00:00
if ( S_ISDIR ( statbuf . st_mode ) )
resolved_base_path_builder . append ( ' / ' ) ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
auto resolved_base = resolved_base_path_builder . string_view ( ) ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
auto results = expand_globs ( move ( parts ) , resolved_base ) ;
2020-05-16 19:05:13 +00:00
2020-09-18 21:48:44 +00:00
if ( explicitly_set_base & & base = = " / " )
resolved_base = resolved_base . substring_view ( 1 , resolved_base . length ( ) - 1 ) ;
2020-06-17 13:35:06 +00:00
for ( auto & entry : results ) {
entry = entry . substring ( resolved_base . length ( ) , entry . length ( ) - resolved_base . length ( ) ) ;
if ( entry . is_empty ( ) )
entry = " . " ;
2020-05-16 19:05:13 +00:00
}
2020-06-17 13:35:06 +00:00
// Make the output predictable and nice.
quick_sort ( results ) ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
return results ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > Shell : : expand_globs ( Vector < StringView > path_segments , StringView base )
2020-05-16 19:05:13 +00:00
{
2020-06-17 13:35:06 +00:00
if ( path_segments . is_empty ( ) ) {
2022-12-04 18:02:33 +00:00
DeprecatedString base_str = base ;
2021-01-12 14:15:26 +00:00
struct stat statbuf ;
if ( lstat ( base_str . characters ( ) , & statbuf ) < 0 )
return { } ;
return { move ( base_str ) } ;
2020-05-16 19:05:13 +00:00
}
2020-06-17 13:35:06 +00:00
auto first_segment = path_segments . take_first ( ) ;
if ( is_glob ( first_segment ) ) {
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > result ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
Core : : DirIterator di ( base , Core : : DirIterator : : SkipParentAndBaseDir ) ;
if ( di . has_error ( ) )
return { } ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
while ( di . has_next ( ) ) {
2022-12-04 18:02:33 +00:00
DeprecatedString path = di . next_path ( ) ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
// Dotfiles have to be explicitly requested
if ( path [ 0 ] = = ' . ' & & first_segment [ 0 ] ! = ' . ' )
continue ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
if ( path . matches ( first_segment , CaseSensitivity : : CaseSensitive ) ) {
StringBuilder builder ;
builder . append ( base ) ;
if ( ! base . ends_with ( ' / ' ) )
builder . append ( ' / ' ) ;
builder . append ( path ) ;
2021-06-12 11:24:45 +00:00
result . extend ( expand_globs ( path_segments , builder . string_view ( ) ) ) ;
2020-06-17 13:35:06 +00:00
}
}
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
return result ;
} else {
StringBuilder builder ;
builder . append ( base ) ;
if ( ! base . ends_with ( ' / ' ) )
builder . append ( ' / ' ) ;
builder . append ( first_segment ) ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
return expand_globs ( move ( path_segments ) , builder . string_view ( ) ) ;
}
2020-05-16 19:05:13 +00:00
}
2023-02-18 14:09:41 +00:00
ErrorOr < Vector < AST : : Command > > Shell : : expand_aliases ( Vector < AST : : Command > initial_commands )
2020-07-13 05:00:09 +00:00
{
Vector < AST : : Command > commands ;
2023-02-18 14:09:41 +00:00
Function < ErrorOr < void > ( AST : : Command & ) > resolve_aliases_and_append = [ & ] ( auto & command ) - > ErrorOr < void > {
2020-07-13 05:00:09 +00:00
if ( ! command . argv . is_empty ( ) ) {
auto alias = resolve_alias ( command . argv [ 0 ] ) ;
if ( ! alias . is_null ( ) ) {
auto argv0 = command . argv . take_first ( ) ;
2023-02-11 14:29:15 +00:00
auto subcommand_ast = parse ( alias , false ) ;
2020-07-13 05:00:09 +00:00
if ( subcommand_ast ) {
while ( subcommand_ast - > is_execute ( ) ) {
auto * ast = static_cast < AST : : Execute * > ( subcommand_ast . ptr ( ) ) ;
subcommand_ast = ast - > command ( ) ;
}
2020-09-26 20:06:27 +00:00
auto subcommand_nonnull = subcommand_ast . release_nonnull ( ) ;
2021-04-23 14:46:57 +00:00
NonnullRefPtr < AST : : Node > substitute = adopt_ref ( * new AST : : Join ( subcommand_nonnull - > position ( ) ,
2020-09-26 20:06:27 +00:00
subcommand_nonnull ,
2021-04-23 14:46:57 +00:00
adopt_ref ( * new AST : : CommandLiteral ( subcommand_nonnull - > position ( ) , command ) ) ) ) ;
2023-02-18 14:09:41 +00:00
auto res = TRY ( substitute - > run ( * this ) ) ;
for ( auto & subst_command : TRY ( res - > resolve_as_commands ( * this ) ) ) {
2020-07-13 05:00:09 +00:00
if ( ! subst_command . argv . is_empty ( ) & & subst_command . argv . first ( ) = = argv0 ) // Disallow an alias resolving to itself.
commands . append ( subst_command ) ;
else
2023-02-18 14:09:41 +00:00
TRY ( resolve_aliases_and_append ( subst_command ) ) ;
2020-07-13 05:00:09 +00:00
}
} else {
commands . append ( command ) ;
}
} else {
commands . append ( command ) ;
}
} else {
commands . append ( command ) ;
}
2023-02-18 14:09:41 +00:00
return { } ;
2020-07-13 05:00:09 +00:00
} ;
for ( auto & command : initial_commands )
2023-02-18 14:09:41 +00:00
TRY ( resolve_aliases_and_append ( command ) ) ;
2020-07-13 05:00:09 +00:00
return commands ;
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : resolve_path ( DeprecatedString path ) const
2020-05-16 19:05:13 +00:00
{
2020-06-17 13:35:06 +00:00
if ( ! path . starts_with ( ' / ' ) )
2022-12-04 18:02:33 +00:00
path = DeprecatedString : : formatted ( " {}/{} " , cwd , path ) ;
2020-05-16 19:05:13 +00:00
2023-05-27 11:15:03 +00:00
return FileSystem : : real_path ( path ) . release_value_but_fixme_should_propagate_errors ( ) . to_deprecated_string ( ) ;
2020-06-17 13:35:06 +00:00
}
2020-05-16 19:05:13 +00:00
2022-01-29 15:24:01 +00:00
Shell : : LocalFrame * Shell : : find_frame_containing_local_variable ( StringView name )
2020-07-11 21:12:46 +00:00
{
2020-11-01 09:58:58 +00:00
for ( size_t i = m_local_frames . size ( ) ; i > 0 ; - - i ) {
auto & frame = m_local_frames [ i - 1 ] ;
2023-03-06 16:16:25 +00:00
if ( frame - > local_variables . contains ( name ) )
return frame ;
2020-07-11 21:12:46 +00:00
}
return nullptr ;
}
2023-04-29 17:03:37 +00:00
ErrorOr < RefPtr < AST : : Value const > > Shell : : look_up_local_variable ( StringView name ) const
2020-06-17 13:35:06 +00:00
{
2020-07-11 21:12:46 +00:00
if ( auto * frame = find_frame_containing_local_variable ( name ) )
return frame - > local_variables . get ( name ) . value ( ) ;
2020-09-13 09:29:34 +00:00
if ( auto index = name . to_uint ( ) ; index . has_value ( ) )
return get_argument ( index . value ( ) ) ;
return nullptr ;
}
2023-02-18 14:09:41 +00:00
ErrorOr < RefPtr < AST : : Value const > > Shell : : get_argument ( size_t index ) const
2020-09-13 09:29:34 +00:00
{
if ( index = = 0 )
2023-02-18 14:09:41 +00:00
return adopt_ref ( * new AST : : StringValue ( TRY ( String : : from_deprecated_string ( current_script ) ) ) ) ;
2020-09-13 09:29:34 +00:00
- - index ;
2023-04-29 17:03:37 +00:00
if ( auto argv = TRY ( look_up_local_variable ( " ARGV " sv ) ) ) {
2020-09-13 09:29:34 +00:00
if ( argv - > is_list_without_resolution ( ) ) {
2023-02-20 17:26:54 +00:00
AST : : ListValue const * list = static_cast < AST : : ListValue const * > ( argv . ptr ( ) ) ;
2020-09-13 09:29:34 +00:00
if ( list - > values ( ) . size ( ) < = index )
return nullptr ;
return list - > values ( ) . at ( index ) ;
}
if ( index ! = 0 )
return nullptr ;
return argv ;
}
2020-07-11 21:12:46 +00:00
return nullptr ;
2020-06-17 13:35:06 +00:00
}
2020-05-16 19:05:13 +00:00
2023-02-18 14:09:41 +00:00
ErrorOr < DeprecatedString > Shell : : local_variable_or ( StringView name , DeprecatedString const & replacement ) const
2020-06-17 13:35:06 +00:00
{
2023-04-29 17:03:37 +00:00
auto value = TRY ( look_up_local_variable ( name ) ) ;
2020-06-17 13:35:06 +00:00
if ( value ) {
StringBuilder builder ;
2023-02-18 14:09:41 +00:00
builder . join ( ' ' , TRY ( const_cast < AST : : Value & > ( * value ) . resolve_as_list ( const_cast < Shell & > ( * this ) ) ) ) ;
2022-12-06 01:12:49 +00:00
return builder . to_deprecated_string ( ) ;
2020-05-16 19:05:13 +00:00
}
2020-06-17 13:35:06 +00:00
return replacement ;
}
2020-05-16 19:05:13 +00:00
2022-12-04 18:02:33 +00:00
void Shell : : set_local_variable ( DeprecatedString const & name , RefPtr < AST : : Value > value , bool only_in_current_frame )
2020-06-17 13:35:06 +00:00
{
2020-11-01 09:59:25 +00:00
if ( ! only_in_current_frame ) {
if ( auto * frame = find_frame_containing_local_variable ( name ) ) {
frame - > local_variables . set ( name , move ( value ) ) ;
return ;
}
}
2023-04-19 08:51:03 +00:00
LocalFrame * selected_frame = nullptr ;
if ( m_in_posix_mode ) {
// POSIX mode: Drop everything in the closest function frame (or the global frame if there is no function frame).
auto & closest_function_frame = m_local_frames . last_matching ( [ ] ( auto & frame ) { return frame - > is_function_frame ; } ) . value ( ) ;
selected_frame = closest_function_frame . ptr ( ) ;
} else {
selected_frame = m_local_frames . last ( ) . ptr ( ) ;
}
selected_frame - > local_variables . set ( name , move ( value ) ) ;
2020-06-17 13:35:06 +00:00
}
2020-05-16 19:05:13 +00:00
2022-01-29 15:24:01 +00:00
void Shell : : unset_local_variable ( StringView name , bool only_in_current_frame )
2020-06-17 13:35:06 +00:00
{
2020-11-01 09:59:25 +00:00
if ( ! only_in_current_frame ) {
if ( auto * frame = find_frame_containing_local_variable ( name ) )
frame - > local_variables . remove ( name ) ;
return ;
}
2023-03-06 16:16:25 +00:00
m_local_frames . last ( ) - > local_variables . remove ( name ) ;
2020-07-11 21:12:46 +00:00
}
2022-12-04 18:02:33 +00:00
void Shell : : define_function ( DeprecatedString name , Vector < DeprecatedString > argnames , RefPtr < AST : : Node > body )
2020-09-13 11:24:33 +00:00
{
2022-04-20 19:36:00 +00:00
add_entry_to_cache ( { RunnablePath : : Kind : : Function , name } ) ;
2020-09-13 11:24:33 +00:00
m_functions . set ( name , { name , move ( argnames ) , move ( body ) } ) ;
}
2022-01-29 15:24:01 +00:00
bool Shell : : has_function ( StringView name )
2020-09-13 11:24:33 +00:00
{
return m_functions . contains ( name ) ;
}
bool Shell : : invoke_function ( const AST : : Command & command , int & retval )
{
if ( command . argv . is_empty ( ) )
return false ;
StringView name = command . argv . first ( ) ;
2022-12-04 18:02:33 +00:00
TemporaryChange < DeprecatedString > script_change { current_script , name } ;
2020-09-13 11:24:33 +00:00
auto function_option = m_functions . get ( name ) ;
if ( ! function_option . has_value ( ) )
return false ;
auto & function = function_option . value ( ) ;
if ( ! function . body ) {
retval = 0 ;
return true ;
}
if ( command . argv . size ( ) - 1 < function . arguments . size ( ) ) {
2022-12-04 18:02:33 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , DeprecatedString : : formatted ( " Expected at least {} arguments to {}, but got {} " , function . arguments . size ( ) , function . name , command . argv . size ( ) - 1 ) , command . position ) ;
2020-09-13 11:24:33 +00:00
retval = 1 ;
return true ;
}
2023-04-19 08:51:03 +00:00
auto frame = push_frame ( DeprecatedString : : formatted ( " function {} " , function . name ) , LocalFrameKind : : FunctionOrGlobal ) ;
2020-09-13 11:24:33 +00:00
size_t index = 0 ;
for ( auto & arg : function . arguments ) {
+ + index ;
2021-04-23 14:46:57 +00:00
set_local_variable ( arg , adopt_ref ( * new AST : : StringValue ( command . argv [ index ] ) ) , true ) ;
2020-09-13 11:24:33 +00:00
}
auto argv = command . argv ;
argv . take_first ( ) ;
2021-04-23 14:46:57 +00:00
set_local_variable ( " ARGV " , adopt_ref ( * new AST : : ListValue ( move ( argv ) ) ) , true ) ;
2020-09-13 11:24:33 +00:00
2020-12-07 16:07:04 +00:00
Core : : EventLoop loop ;
setup_signals ( ) ;
2021-12-02 11:01:47 +00:00
( void ) function . body - > run ( * this ) ;
2020-09-13 11:24:33 +00:00
2022-01-05 04:30:06 +00:00
retval = last_return_code . value_or ( 0 ) ;
2020-09-13 11:24:33 +00:00
return true ;
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : format ( StringView source , ssize_t & cursor ) const
2020-09-16 00:43:04 +00:00
{
2023-02-11 14:29:15 +00:00
Formatter formatter ( source , cursor , m_in_posix_mode ) ;
2020-09-16 00:43:04 +00:00
auto result = formatter . format ( ) ;
cursor = formatter . cursor ( ) ;
return result ;
}
2023-04-19 08:51:03 +00:00
Shell : : Frame Shell : : push_frame ( DeprecatedString name , Shell : : LocalFrameKind kind )
2020-07-11 21:12:46 +00:00
{
2023-04-19 08:51:03 +00:00
m_local_frames . append ( make < LocalFrame > ( name , decltype ( LocalFrame : : local_variables ) { } , kind ) ) ;
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " New frame '{}' at {:p} " , name , & m_local_frames . last ( ) ) ;
2023-03-06 16:16:25 +00:00
return { m_local_frames , * m_local_frames . last ( ) } ;
2020-07-11 21:12:46 +00:00
}
void Shell : : pop_frame ( )
{
2021-02-23 19:42:32 +00:00
VERIFY ( m_local_frames . size ( ) > 1 ) ;
2021-12-02 11:01:47 +00:00
( void ) m_local_frames . take_last ( ) ;
2020-07-11 21:12:46 +00:00
}
Shell : : Frame : : ~ Frame ( )
{
if ( ! should_destroy_frame )
return ;
2023-03-06 16:16:25 +00:00
if ( frames . last ( ) ! = & frame ) {
2020-12-06 17:21:40 +00:00
dbgln ( " Frame destruction order violation near {:p} (container = {:p}) in '{}' " , & frame , this , frame . name ) ;
dbgln ( " Current frames: " ) ;
2020-11-01 09:58:11 +00:00
for ( auto & frame : frames )
2023-03-06 16:16:25 +00:00
dbgln ( " - {:p}: {} " , & frame , frame - > name ) ;
2021-02-23 19:42:32 +00:00
VERIFY_NOT_REACHED ( ) ;
2020-11-01 09:58:11 +00:00
}
2021-12-02 11:01:47 +00:00
( void ) frames . take_last ( ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : resolve_alias ( StringView name ) const
2020-06-17 14:21:44 +00:00
{
return m_aliases . get ( name ) . value_or ( { } ) ;
}
2022-04-20 19:36:00 +00:00
Optional < Shell : : RunnablePath > Shell : : runnable_path_for ( StringView name )
2020-08-03 00:04:27 +00:00
{
2023-05-27 10:52:14 +00:00
auto parts = name . find ( ' / ' ) ;
if ( parts . has_value ( ) ) {
auto file_or_error = Core : : File : : open ( name , Core : : File : : OpenMode : : Read ) ;
if ( ! file_or_error . is_error ( )
& & ! FileSystem : : is_directory ( file_or_error . value ( ) - > fd ( ) )
& & ! Core : : System : : access ( name , X_OK ) . is_error ( ) )
2022-04-20 19:36:00 +00:00
return RunnablePath { RunnablePath : : Kind : : Executable , name } ;
}
2023-05-27 10:52:14 +00:00
auto * found = binary_search ( cached_path . span ( ) , name , nullptr , RunnablePathComparator { } ) ;
2022-04-20 19:36:00 +00:00
if ( ! found )
return { } ;
return * found ;
}
2022-12-04 18:02:33 +00:00
Optional < DeprecatedString > Shell : : help_path_for ( Vector < RunnablePath > visited , Shell : : RunnablePath const & runnable_path )
2022-04-20 19:36:00 +00:00
{
switch ( runnable_path . kind ) {
case RunnablePath : : Kind : : Executable : {
LexicalPath lexical_path ( runnable_path . path ) ;
return lexical_path . basename ( ) ;
}
2020-08-03 00:04:27 +00:00
2022-04-20 19:36:00 +00:00
case RunnablePath : : Kind : : Alias : {
if ( visited . contains_slow ( runnable_path ) )
return { } ; // Break out of an alias loop
auto resolved = resolve_alias ( runnable_path . path ) ;
auto * runnable = binary_search ( cached_path . span ( ) , resolved , nullptr , RunnablePathComparator { } ) ;
if ( ! runnable )
return { } ;
visited . append ( runnable_path ) ;
return help_path_for ( visited , * runnable ) ;
}
default :
return { } ;
}
2020-08-03 00:04:27 +00:00
}
2021-11-10 23:55:02 +00:00
int Shell : : run_command ( StringView cmd , Optional < SourcePosition > source_position_override )
2020-05-16 19:05:13 +00:00
{
2020-10-01 14:43:01 +00:00
// The default-constructed mode of the shell
// should not be used for execution!
2021-02-23 19:42:32 +00:00
VERIFY ( ! m_default_constructed ) ;
2020-10-01 14:43:01 +00:00
2020-12-10 14:55:13 +00:00
take_error ( ) ;
2022-01-05 04:30:06 +00:00
if ( ! last_return_code . has_value ( ) )
last_return_code = 0 ;
2021-12-21 02:48:19 +00:00
2021-01-03 09:08:20 +00:00
ScopedValueRollback source_position_rollback { m_source_position } ;
if ( source_position_override . has_value ( ) )
m_source_position = move ( source_position_override ) ;
if ( ! m_source_position . has_value ( ) )
m_source_position = SourcePosition { . source_file = { } , . literal_source_text = cmd , . position = { } } ;
2020-05-16 19:05:13 +00:00
if ( cmd . is_empty ( ) )
return 0 ;
2023-02-11 14:29:15 +00:00
auto command = parse ( cmd , m_is_interactive ) ;
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
if ( ! command )
return 0 ;
2020-05-16 19:05:13 +00:00
2021-03-29 21:47:20 +00:00
if constexpr ( SH_DEBUG ) {
dbgln ( " Command follows " ) ;
2023-02-18 07:26:59 +00:00
( void ) command - > dump ( 0 ) ;
2021-03-29 21:47:20 +00:00
}
2020-07-11 21:11:24 +00:00
2020-06-23 14:40:41 +00:00
if ( command - > is_syntax_error ( ) ) {
2020-06-28 14:12:57 +00:00
auto & error_node = command - > syntax_error_node ( ) ;
auto & position = error_node . position ( ) ;
2023-02-18 06:45:08 +00:00
raise_error ( ShellError : : EvaluatedSyntaxError , error_node . error_text ( ) . bytes_as_string_view ( ) , position ) ;
2021-01-03 09:08:20 +00:00
}
if ( ! has_error ( ShellError : : None ) ) {
possibly_print_error ( ) ;
take_error ( ) ;
2020-06-23 14:40:41 +00:00
return 1 ;
}
2020-06-17 13:35:06 +00:00
tcgetattr ( 0 , & termios ) ;
2020-05-16 19:05:13 +00:00
2021-12-02 11:01:47 +00:00
( void ) command - > run ( * this ) ;
2020-05-16 19:05:13 +00:00
2021-01-03 09:08:20 +00:00
if ( ! has_error ( ShellError : : None ) ) {
possibly_print_error ( ) ;
take_error ( ) ;
return 1 ;
}
2022-01-05 04:30:06 +00:00
return last_return_code . value_or ( 0 ) ;
2020-06-17 13:35:06 +00:00
}
2021-12-21 02:48:19 +00:00
ErrorOr < RefPtr < Job > > Shell : : run_command ( const AST : : Command & command )
2020-06-17 13:35:06 +00:00
{
FileDescriptionCollector fds ;
2020-05-24 18:30:46 +00:00
2020-10-27 15:01:11 +00:00
if ( options . verbose )
2020-10-28 13:50:42 +00:00
warnln ( " + {} " , command ) ;
2020-06-29 01:52:58 +00:00
2020-08-09 15:38:02 +00:00
// If the command is empty, store the redirections and apply them to all later commands.
2020-09-07 16:19:53 +00:00
if ( command . argv . is_empty ( ) & & ! command . should_immediately_execute_next ) {
2021-06-12 11:24:45 +00:00
m_global_redirections . extend ( command . redirections ) ;
2021-01-20 03:26:10 +00:00
for ( auto & next_in_chain : command . next_chain )
2022-01-05 04:30:06 +00:00
run_tail ( command , next_in_chain , last_return_code . value_or ( 0 ) ) ;
2020-08-09 15:38:02 +00:00
return nullptr ;
}
2020-06-17 13:35:06 +00:00
// Resolve redirections.
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < AST : : Rewiring > > rewirings ;
2021-12-21 02:48:19 +00:00
auto resolve_redirection = [ & ] ( auto & redirection ) - > ErrorOr < void > {
2023-03-06 13:17:01 +00:00
auto rewiring = TRY ( redirection - > apply ( ) ) ;
2020-05-24 18:30:46 +00:00
2020-10-27 18:08:37 +00:00
if ( rewiring - > fd_action ! = AST : : Rewiring : : Close : : ImmediatelyCloseNew )
2020-06-22 11:07:20 +00:00
rewirings . append ( * rewiring ) ;
2020-10-27 18:08:37 +00:00
if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : Old ) {
fds . add ( rewiring - > old_fd ) ;
} else if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : New ) {
if ( rewiring - > new_fd ! = - 1 )
fds . add ( rewiring - > new_fd ) ;
} else if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : ImmediatelyCloseNew ) {
fds . add ( rewiring - > new_fd ) ;
} else if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : RefreshNew ) {
2021-02-23 19:42:32 +00:00
VERIFY ( rewiring - > other_pipe_end ) ;
2020-06-22 11:07:20 +00:00
int pipe_fd [ 2 ] ;
int rc = pipe ( pipe_fd ) ;
2021-12-21 02:48:19 +00:00
if ( rc < 0 )
return Error : : from_syscall ( " pipe " sv , rc ) ;
2020-10-27 18:08:37 +00:00
rewiring - > new_fd = pipe_fd [ 1 ] ;
rewiring - > other_pipe_end - > new_fd = pipe_fd [ 0 ] ; // This fd will be added to the collection on one of the next iterations.
fds . add ( pipe_fd [ 1 ] ) ;
} else if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : RefreshOld ) {
2021-02-23 19:42:32 +00:00
VERIFY ( rewiring - > other_pipe_end ) ;
2020-10-27 18:08:37 +00:00
int pipe_fd [ 2 ] ;
int rc = pipe ( pipe_fd ) ;
2021-12-21 02:48:19 +00:00
if ( rc < 0 )
return Error : : from_syscall ( " pipe " sv , rc ) ;
2020-10-27 18:08:37 +00:00
rewiring - > old_fd = pipe_fd [ 1 ] ;
rewiring - > other_pipe_end - > old_fd = pipe_fd [ 0 ] ; // This fd will be added to the collection on one of the next iterations.
2020-06-22 11:07:20 +00:00
fds . add ( pipe_fd [ 1 ] ) ;
2020-06-17 13:35:06 +00:00
}
2021-12-21 02:48:19 +00:00
return { } ;
2020-08-09 15:38:02 +00:00
} ;
2020-05-16 19:05:13 +00:00
2021-12-21 02:48:19 +00:00
auto apply_rewirings = [ & ] ( ) - > ErrorOr < void > {
2020-09-07 16:19:53 +00:00
for ( auto & rewiring : rewirings ) {
2020-12-06 17:21:40 +00:00
2023-03-06 13:17:01 +00:00
dbgln_if ( SH_DEBUG , " in {}<{}>, dup2({}, {}) " , command . argv . is_empty ( ) ? " (<Empty>) " sv : command . argv [ 0 ] , getpid ( ) , rewiring - > old_fd , rewiring - > new_fd ) ;
int rc = dup2 ( rewiring - > old_fd , rewiring - > new_fd ) ;
2021-12-21 02:48:19 +00:00
if ( rc < 0 )
return Error : : from_syscall ( " dup2 " sv , rc ) ;
2023-03-06 13:17:01 +00:00
// {new,old}_fd is closed via the `fds` collector, but rewiring->other_pipe_end->{new,old}_fd
2020-09-07 16:19:53 +00:00
// isn't yet in that collector when the first child spawns.
2023-03-06 13:17:01 +00:00
if ( rewiring - > other_pipe_end ) {
if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : RefreshNew ) {
if ( rewiring - > other_pipe_end & & close ( rewiring - > other_pipe_end - > new_fd ) < 0 )
2020-10-27 18:08:37 +00:00
perror ( " close other pipe end " ) ;
2023-03-06 13:17:01 +00:00
} else if ( rewiring - > fd_action = = AST : : Rewiring : : Close : : RefreshOld ) {
if ( rewiring - > other_pipe_end & & close ( rewiring - > other_pipe_end - > old_fd ) < 0 )
2020-10-27 18:08:37 +00:00
perror ( " close other pipe end " ) ;
}
}
2020-09-07 16:19:53 +00:00
}
2021-12-21 02:48:19 +00:00
return { } ;
2020-09-07 16:19:53 +00:00
} ;
2020-12-07 16:07:04 +00:00
TemporaryChange signal_handler_install { m_should_reinstall_signal_handlers , false } ;
2021-12-21 02:48:19 +00:00
for ( auto & redirection : m_global_redirections )
TRY ( resolve_redirection ( redirection ) ) ;
2020-05-16 19:05:13 +00:00
2021-12-21 02:48:19 +00:00
for ( auto & redirection : command . redirections )
TRY ( resolve_redirection ( redirection ) ) ;
2020-05-16 19:05:13 +00:00
2023-02-18 09:00:17 +00:00
if ( int local_return_code = 0 ; command . should_wait & & TRY ( run_builtin ( command , rewirings , local_return_code ) ) ) {
2022-01-05 04:30:06 +00:00
last_return_code = local_return_code ;
2020-09-01 04:12:16 +00:00
for ( auto & next_in_chain : command . next_chain )
2022-01-05 04:30:06 +00:00
run_tail ( command , next_in_chain , * last_return_code ) ;
2020-08-14 18:00:48 +00:00
return nullptr ;
2020-09-01 04:12:16 +00:00
}
2020-08-14 18:00:48 +00:00
2020-12-14 19:39:17 +00:00
auto can_be_run_in_current_process = command . should_wait & & ! command . pipeline & & ! command . argv . is_empty ( ) ;
2020-09-13 11:24:33 +00:00
if ( can_be_run_in_current_process & & has_function ( command . argv . first ( ) ) ) {
SavedFileDescriptors fds { rewirings } ;
2021-12-21 02:48:19 +00:00
for ( auto & rewiring : rewirings )
2023-03-06 13:17:01 +00:00
TRY ( Core : : System : : dup2 ( rewiring - > old_fd , rewiring - > new_fd ) ) ;
2020-09-13 11:24:33 +00:00
2022-01-05 04:30:06 +00:00
if ( int local_return_code = 0 ; invoke_function ( command , local_return_code ) ) {
last_return_code = local_return_code ;
2020-09-13 11:24:33 +00:00
for ( auto & next_in_chain : command . next_chain )
2022-01-05 04:30:06 +00:00
run_tail ( command , next_in_chain , * last_return_code ) ;
2020-09-13 11:24:33 +00:00
return nullptr ;
}
}
2021-01-18 06:36:13 +00:00
if ( command . argv . is_empty ( )
& & ! command . next_chain . is_empty ( )
& & command . should_immediately_execute_next
& & command . redirections . is_empty ( )
& & command . next_chain . first ( ) . node - > should_override_execution_in_current_process ( ) ) {
2021-01-06 12:53:28 +00:00
for ( auto & next_in_chain : command . next_chain )
2022-01-05 04:30:06 +00:00
run_tail ( command , next_in_chain , last_return_code . value_or ( 0 ) ) ;
2021-01-06 12:53:28 +00:00
return nullptr ;
}
2022-04-01 17:58:27 +00:00
Vector < char const * > argv ;
2023-02-18 06:45:08 +00:00
Vector < DeprecatedString > copy_argv ;
2020-06-17 13:35:06 +00:00
argv . ensure_capacity ( command . argv . size ( ) + 1 ) ;
2020-05-16 19:05:13 +00:00
2023-02-18 06:45:08 +00:00
for ( auto & arg : command . argv ) {
copy_argv . append ( arg . to_deprecated_string ( ) ) ;
argv . append ( copy_argv . last ( ) . characters ( ) ) ;
}
2020-06-17 13:35:06 +00:00
argv . append ( nullptr ) ;
2020-05-16 19:05:13 +00:00
2021-12-21 02:48:19 +00:00
auto sync_pipe = TRY ( Core : : System : : pipe2 ( 0 ) ) ;
auto child = TRY ( Core : : System : : fork ( ) ) ;
2020-08-11 11:24:46 +00:00
2020-06-25 12:45:23 +00:00
if ( child = = 0 ) {
2020-08-11 20:44:34 +00:00
close ( sync_pipe [ 1 ] ) ;
2020-09-07 18:15:31 +00:00
m_pid = getpid ( ) ;
Core : : EventLoop : : notify_forked ( Core : : EventLoop : : ForkEvent : : Child ) ;
2020-12-07 16:07:04 +00:00
TemporaryChange signal_handler_install { m_should_reinstall_signal_handlers , true } ;
2020-08-11 11:24:46 +00:00
2021-12-21 02:48:19 +00:00
if ( auto result = apply_rewirings ( ) ; result . is_error ( ) ) {
warnln ( " Shell: Failed to apply rewirings in {}: {} " , copy_argv [ 0 ] , result . error ( ) ) ;
2020-09-09 19:07:38 +00:00
_exit ( 126 ) ;
2021-12-21 02:48:19 +00:00
}
2020-05-16 19:05:13 +00:00
2020-06-17 13:35:06 +00:00
fds . collect ( ) ;
2020-08-11 20:44:34 +00:00
u8 c ;
while ( read ( sync_pipe [ 0 ] , & c , 1 ) < 0 ) {
if ( errno ! = EINTR ) {
2021-12-21 02:48:19 +00:00
warnln ( " Shell: Failed to sync in {}: {} " , copy_argv [ 0 ] , Error : : from_syscall ( " read " sv , - errno ) ) ;
2020-08-11 20:44:34 +00:00
// There's nothing interesting we can do here.
break ;
}
}
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " Synced up with parent, we're good to exec() " ) ;
2020-09-09 19:07:38 +00:00
2020-08-11 20:44:34 +00:00
close ( sync_pipe [ 0 ] ) ;
2020-09-09 19:07:38 +00:00
if ( ! m_is_subshell & & command . should_wait )
tcsetattr ( 0 , TCSANOW , & default_termios ) ;
2021-12-14 13:38:59 +00:00
m_is_subshell = true ;
2020-09-07 18:15:31 +00:00
if ( command . should_immediately_execute_next ) {
2021-02-23 19:42:32 +00:00
VERIFY ( command . argv . is_empty ( ) ) ;
2020-09-07 18:15:31 +00:00
2020-12-07 16:07:04 +00:00
Core : : EventLoop mainloop ;
setup_signals ( ) ;
2020-09-07 18:15:31 +00:00
for ( auto & next_in_chain : command . next_chain )
2020-10-25 23:01:29 +00:00
run_tail ( command , next_in_chain , 0 ) ;
2020-09-07 18:15:31 +00:00
2022-01-05 04:30:06 +00:00
_exit ( last_return_code . value_or ( 0 ) ) ;
2020-09-07 18:15:31 +00:00
}
2023-02-18 09:00:17 +00:00
if ( int local_return_code = 0 ; TRY ( run_builtin ( command , { } , local_return_code ) ) )
2022-01-05 04:30:06 +00:00
_exit ( local_return_code ) ;
2020-10-27 15:01:11 +00:00
2022-01-05 04:30:06 +00:00
if ( int local_return_code = 0 ; invoke_function ( command , local_return_code ) )
_exit ( local_return_code ) ;
2020-09-13 11:24:33 +00:00
2020-10-27 15:01:11 +00:00
// We no longer need the jobs here.
jobs . clear ( ) ;
2020-12-29 19:42:34 +00:00
execute_process ( move ( argv ) ) ;
2021-02-23 19:42:32 +00:00
VERIFY_NOT_REACHED ( ) ;
2020-06-17 13:35:06 +00:00
}
2020-05-16 19:05:13 +00:00
2020-08-11 20:44:34 +00:00
close ( sync_pipe [ 0 ] ) ;
2020-08-11 11:24:46 +00:00
bool is_first = ! command . pipeline | | ( command . pipeline & & command . pipeline - > pgid = = - 1 ) ;
if ( command . pipeline ) {
if ( is_first ) {
command . pipeline - > pgid = child ;
}
}
pid_t pgid = is_first ? child : ( command . pipeline ? command . pipeline - > pgid : child ) ;
2020-12-06 21:17:21 +00:00
if ( ! m_is_subshell | | command . pipeline ) {
2021-12-21 02:48:19 +00:00
auto result = Core : : System : : setpgid ( child , pgid ) ;
if ( result . is_error ( ) & & m_is_interactive )
warnln ( " Shell: {} " , result . error ( ) ) ;
2020-08-11 11:24:46 +00:00
2020-09-12 19:11:07 +00:00
if ( ! m_is_subshell ) {
2021-04-19 10:05:21 +00:00
// There's no reason to care about the errors here
// either we're in a tty, we're interactive, and this works
// or we're not, and it fails - in which case, we don't need
// stdin/stdout handoff to child processes anyway.
tcsetpgrp ( STDOUT_FILENO , pgid ) ;
tcsetpgrp ( STDIN_FILENO , pgid ) ;
2020-09-12 19:11:07 +00:00
}
2020-08-11 11:24:46 +00:00
}
2020-08-11 20:44:34 +00:00
while ( write ( sync_pipe [ 1 ] , " x " , 1 ) < 0 ) {
if ( errno ! = EINTR ) {
2021-12-21 02:48:19 +00:00
warnln ( " Shell: Failed to sync with {}: {} " , copy_argv [ 0 ] , Error : : from_syscall ( " write " sv , - errno ) ) ;
2020-08-11 20:44:34 +00:00
// There's nothing interesting we can do here.
break ;
}
}
close ( sync_pipe [ 1 ] ) ;
2020-06-17 13:35:06 +00:00
StringBuilder cmd ;
2022-07-11 20:10:18 +00:00
cmd . join ( ' ' , command . argv ) ;
2020-05-16 19:05:13 +00:00
2020-09-07 18:15:31 +00:00
auto command_copy = AST : : Command ( command ) ;
// Clear the next chain if it's to be immediately executed
// as the child will run this chain.
if ( command . should_immediately_execute_next )
command_copy . next_chain . clear ( ) ;
2023-01-26 18:58:09 +00:00
auto job = Job : : create ( child , pgid , cmd . to_deprecated_string ( ) , find_last_job_id ( ) + 1 , move ( command_copy ) ) ;
2020-06-17 13:35:06 +00:00
jobs . set ( ( u64 ) child , job ) ;
2020-05-24 18:30:46 +00:00
2020-09-01 04:12:16 +00:00
job - > on_exit = [ this ] ( auto job ) {
2020-07-12 14:21:47 +00:00
if ( ! job - > exited ( ) )
return ;
2020-12-10 14:02:18 +00:00
2020-08-15 18:50:39 +00:00
if ( job - > is_running_in_background ( ) & & job - > should_announce_exit ( ) )
2021-05-02 06:57:53 +00:00
warnln ( " Shell: Job {} ({}) exited \n " , job - > job_id ( ) , job - > cmd ( ) ) ;
2020-12-06 17:22:43 +00:00
else if ( job - > signaled ( ) & & job - > should_announce_signal ( ) )
2021-05-02 06:57:53 +00:00
warnln ( " Shell: Job {} ({}) {} \n " , job - > job_id ( ) , job - > cmd ( ) , strsignal ( job - > termination_signal ( ) ) ) ;
2020-09-01 04:12:16 +00:00
last_return_code = job - > exit_code ( ) ;
2020-06-17 13:35:06 +00:00
job - > disown ( ) ;
2020-09-01 04:12:16 +00:00
2021-05-24 12:20:16 +00:00
if ( m_editor & & job - > exit_code ( ) = = 0 & & is_allowed_to_modify_termios ( job - > command ( ) ) ) {
m_editor - > refetch_default_termios ( ) ;
default_termios = m_editor - > default_termios ( ) ;
termios = m_editor - > termios ( ) ;
}
2020-09-01 04:12:16 +00:00
run_tail ( job ) ;
2020-06-17 13:35:06 +00:00
} ;
2020-05-24 18:30:46 +00:00
2020-06-22 11:07:20 +00:00
fds . collect ( ) ;
2020-06-17 13:35:06 +00:00
return * job ;
}
2020-05-24 18:30:46 +00:00
2023-02-18 09:00:17 +00:00
ErrorOr < void > Shell : : execute_process ( Span < StringView > argv )
{
Vector < DeprecatedString > strings ;
Vector < char const * > args ;
TRY ( strings . try_ensure_capacity ( argv . size ( ) ) ) ;
TRY ( args . try_ensure_capacity ( argv . size ( ) + 1 ) ) ;
for ( auto & entry : argv ) {
strings . unchecked_append ( entry ) ;
args . unchecked_append ( strings . last ( ) . characters ( ) ) ;
}
args . append ( nullptr ) ;
// NOTE: noreturn.
execute_process ( move ( args ) ) ;
}
2022-04-01 17:58:27 +00:00
void Shell : : execute_process ( Vector < char const * > & & argv )
2020-12-29 19:42:34 +00:00
{
2022-03-24 20:49:48 +00:00
for ( auto & promise : m_active_promises ) {
2022-10-09 21:38:06 +00:00
MUST ( Core : : System : : pledge ( " stdio rpath exec " sv , promise . data . exec_promises ) ) ;
2022-03-24 20:49:48 +00:00
for ( auto & item : promise . data . unveils )
2022-10-09 21:38:06 +00:00
MUST ( Core : : System : : unveil ( item . path , item . access ) ) ;
2022-03-24 20:49:48 +00:00
}
2020-12-29 19:42:34 +00:00
int rc = execvp ( argv [ 0 ] , const_cast < char * const * > ( argv . data ( ) ) ) ;
if ( rc < 0 ) {
2022-07-11 17:32:29 +00:00
auto parts = StringView { argv [ 0 ] , strlen ( argv [ 0 ] ) } . split_view ( ' / ' ) ;
2021-05-02 08:17:30 +00:00
if ( parts . size ( ) = = 1 ) {
// If this is a path in the current directory and it caused execvp() to fail,
// simply don't attempt to execute it, see #6774.
warnln ( " {}: Command not found. " , argv [ 0 ] ) ;
_exit ( 127 ) ;
}
2020-12-29 19:42:34 +00:00
int saved_errno = errno ;
struct stat st ;
if ( stat ( argv [ 0 ] , & st ) ) {
2021-05-02 06:57:53 +00:00
warnln ( " stat({}): {} " , argv [ 0 ] , strerror ( errno ) ) ;
2021-01-15 10:22:20 +00:00
// Return code 127 on command not found.
_exit ( 127 ) ;
2020-12-29 19:42:34 +00:00
}
if ( ! ( st . st_mode & S_IXUSR ) ) {
2021-05-02 06:57:53 +00:00
warnln ( " {}: Not executable " , argv [ 0 ] ) ;
2021-01-15 10:22:20 +00:00
// Return code 126 when file is not executable.
2020-12-29 19:42:34 +00:00
_exit ( 126 ) ;
}
if ( saved_errno = = ENOENT ) {
2021-05-02 06:51:33 +00:00
do {
2023-05-27 11:15:03 +00:00
auto path_as_string_or_error = String : : from_utf8 ( { argv [ 0 ] , strlen ( argv [ 0 ] ) } ) ;
if ( path_as_string_or_error . is_error ( ) )
break ;
auto file_result = Core : : File : : open ( path_as_string_or_error . value ( ) , Core : : File : : OpenMode : : Read ) ;
2021-05-02 06:51:33 +00:00
if ( file_result . is_error ( ) )
break ;
2023-05-27 11:15:03 +00:00
auto buffered_file_result = Core : : InputBufferedFile : : create ( file_result . release_value ( ) ) ;
if ( buffered_file_result . is_error ( ) )
break ;
auto file = buffered_file_result . release_value ( ) ;
Array < u8 , 1 * KiB > line_buf ;
auto line_or_error = file - > read_line ( line_buf ) ;
if ( line_or_error . is_error ( ) )
break ;
auto line = line_or_error . release_value ( ) ;
2022-07-11 17:32:29 +00:00
if ( ! line . starts_with ( " #! " sv ) )
2021-05-02 06:51:33 +00:00
break ;
GenericLexer shebang_lexer { line . substring_view ( 2 ) } ;
2022-12-06 01:12:49 +00:00
auto shebang = shebang_lexer . consume_until ( is_any_of ( " \n \r " sv ) ) . to_deprecated_string ( ) ;
2021-05-02 06:51:33 +00:00
argv . prepend ( shebang . characters ( ) ) ;
2020-12-29 19:42:34 +00:00
int rc = execvp ( argv [ 0 ] , const_cast < char * const * > ( argv . data ( ) ) ) ;
2021-05-02 06:51:33 +00:00
if ( rc < 0 ) {
2021-05-02 06:57:53 +00:00
warnln ( " {}: Invalid interpreter \" {} \" : {} " , argv [ 0 ] , shebang . characters ( ) , strerror ( errno ) ) ;
2021-05-02 06:51:33 +00:00
_exit ( 126 ) ;
}
} while ( false ) ;
2021-05-02 06:57:53 +00:00
warnln ( " {}: Command not found. " , argv [ 0 ] ) ;
2020-12-29 19:42:34 +00:00
} else {
if ( S_ISDIR ( st . st_mode ) ) {
2021-05-02 06:57:53 +00:00
warnln ( " Shell: {}: Is a directory " , argv [ 0 ] ) ;
2020-12-29 19:42:34 +00:00
_exit ( 126 ) ;
}
2021-05-02 06:57:53 +00:00
warnln ( " execvp({}): {} " , argv [ 0 ] , strerror ( saved_errno ) ) ;
2020-12-29 19:42:34 +00:00
}
_exit ( 126 ) ;
}
2021-02-23 19:42:32 +00:00
VERIFY_NOT_REACHED ( ) ;
2020-12-29 19:42:34 +00:00
}
2020-10-25 23:01:29 +00:00
void Shell : : run_tail ( const AST : : Command & invoking_command , const AST : : NodeWithAction & next_in_chain , int head_exit_code )
2020-09-01 04:12:16 +00:00
{
2020-12-10 14:55:13 +00:00
if ( m_error ! = ShellError : : None ) {
possibly_print_error ( ) ;
2021-01-03 09:08:20 +00:00
if ( ! is_control_flow ( m_error ) )
take_error ( ) ;
2020-12-10 14:55:13 +00:00
return ;
}
2020-09-07 18:15:31 +00:00
auto evaluate = [ & ] {
if ( next_in_chain . node - > would_execute ( ) ) {
2021-12-02 11:01:47 +00:00
( void ) next_in_chain . node - > run ( * this ) ;
2020-09-07 18:15:31 +00:00
return ;
}
2020-10-25 23:01:29 +00:00
auto node = next_in_chain . node ;
if ( ! invoking_command . should_wait )
2021-04-23 14:46:57 +00:00
node = adopt_ref ( static_cast < AST : : Node & > ( * new AST : : Background ( next_in_chain . node - > position ( ) , move ( node ) ) ) ) ;
2021-12-02 11:01:47 +00:00
( void ) adopt_ref ( static_cast < AST : : Node & > ( * new AST : : Execute ( next_in_chain . node - > position ( ) , move ( node ) ) ) ) - > run ( * this ) ;
2020-09-07 18:15:31 +00:00
} ;
2020-09-01 04:12:16 +00:00
switch ( next_in_chain . action ) {
case AST : : NodeWithAction : : And :
2020-09-07 18:15:31 +00:00
if ( head_exit_code = = 0 )
evaluate ( ) ;
2020-09-01 04:12:16 +00:00
break ;
case AST : : NodeWithAction : : Or :
2020-09-07 18:15:31 +00:00
if ( head_exit_code ! = 0 )
evaluate ( ) ;
2020-09-01 04:12:16 +00:00
break ;
case AST : : NodeWithAction : : Sequence :
2020-09-07 18:15:31 +00:00
evaluate ( ) ;
2020-09-01 04:12:16 +00:00
break ;
}
}
void Shell : : run_tail ( RefPtr < Job > job )
{
if ( auto cmd = job - > command_ptr ( ) ) {
2021-08-30 18:12:48 +00:00
deferred_invoke ( [ = , this ] {
2020-09-01 04:12:16 +00:00
for ( auto & next_in_chain : cmd - > next_chain ) {
2020-10-25 23:01:29 +00:00
run_tail ( * cmd , next_in_chain , job - > exit_code ( ) ) ;
2020-09-01 04:12:16 +00:00
}
} ) ;
}
}
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < Job > > Shell : : run_commands ( Vector < AST : : Command > & commands )
2020-07-13 05:00:09 +00:00
{
2020-12-10 14:55:13 +00:00
if ( m_error ! = ShellError : : None ) {
possibly_print_error ( ) ;
2021-01-03 09:08:20 +00:00
if ( ! is_control_flow ( m_error ) )
take_error ( ) ;
2020-12-10 14:55:13 +00:00
return { } ;
}
2023-03-06 13:17:01 +00:00
Vector < NonnullRefPtr < Job > > spawned_jobs ;
2020-07-13 05:00:09 +00:00
for ( auto & command : commands ) {
2021-03-29 21:47:20 +00:00
if constexpr ( SH_DEBUG ) {
dbgln ( " Command " ) ;
for ( auto & arg : command . argv )
dbgln ( " argv: {} " , arg ) ;
for ( auto & redir : command . redirections ) {
2023-03-06 13:17:01 +00:00
if ( redir - > is_path_redirection ( ) ) {
2021-03-29 21:47:20 +00:00
auto path_redir = ( const AST : : PathRedirection * ) & redir ;
dbgln ( " redir path '{}' <-({})-> {} " , path_redir - > path , ( int ) path_redir - > direction , path_redir - > fd ) ;
2023-03-06 13:17:01 +00:00
} else if ( redir - > is_fd_redirection ( ) ) {
2021-03-29 21:47:20 +00:00
auto * fdredir = ( const AST : : FdRedirection * ) & redir ;
dbgln ( " redir fd {} -> {} " , fdredir - > old_fd , fdredir - > new_fd ) ;
2023-03-06 13:17:01 +00:00
} else if ( redir - > is_close_redirection ( ) ) {
2021-03-29 21:47:20 +00:00
auto close_redir = ( const AST : : CloseRedirection * ) & redir ;
dbgln ( " close fd {} " , close_redir - > fd ) ;
} else {
VERIFY_NOT_REACHED ( ) ;
}
2020-07-13 05:00:09 +00:00
}
}
2021-12-21 02:48:19 +00:00
auto job_result = run_command ( command ) ;
if ( job_result . is_error ( ) ) {
2022-12-04 18:02:33 +00:00
raise_error ( ShellError : : LaunchError , DeprecatedString : : formatted ( " {} while running '{}' " , job_result . error ( ) , command . argv . first ( ) ) , command . position ) ;
2021-12-21 02:48:19 +00:00
break ;
}
auto job = job_result . release_value ( ) ;
2020-07-13 15:59:52 +00:00
if ( ! job )
continue ;
2020-07-13 05:00:09 +00:00
2020-09-01 04:12:16 +00:00
spawned_jobs . append ( * job ) ;
2020-07-13 05:00:09 +00:00
if ( command . should_wait ) {
block_on_job ( job ) ;
} else {
2020-09-01 14:33:19 +00:00
job - > set_running_in_background ( true ) ;
if ( ! command . is_pipe_source & & command . should_notify_if_in_background )
2020-08-15 18:50:39 +00:00
job - > set_should_announce_exit ( true ) ;
2020-07-13 05:00:09 +00:00
}
}
2021-01-03 09:08:20 +00:00
if ( m_error ! = ShellError : : None ) {
possibly_print_error ( ) ;
if ( ! is_control_flow ( m_error ) )
take_error ( ) ;
}
2020-09-01 04:12:16 +00:00
return spawned_jobs ;
2020-07-13 05:00:09 +00:00
}
2022-12-04 18:02:33 +00:00
bool Shell : : run_file ( DeprecatedString const & filename , bool explicitly_invoked )
2020-06-17 15:07:44 +00:00
{
2020-09-13 09:29:34 +00:00
TemporaryChange script_change { current_script , filename } ;
2020-09-28 09:02:36 +00:00
TemporaryChange interactive_change { m_is_interactive , false } ;
2021-01-03 09:08:20 +00:00
TemporaryChange < Optional < SourcePosition > > source_change { m_source_position , SourcePosition { . source_file = filename , . literal_source_text = { } , . position = { } } } ;
2020-09-28 09:02:36 +00:00
2023-05-18 13:32:38 +00:00
auto file_or_error = Core : : File : : open ( filename , Core : : File : : OpenMode : : Read ) ;
if ( file_or_error . is_error ( ) ) {
auto error = DeprecatedString : : formatted ( " '{}': {} " , escape_token_for_single_quotes ( filename ) , file_or_error . error ( ) ) ;
2020-07-05 14:57:37 +00:00
if ( explicitly_invoked )
2021-01-03 09:08:20 +00:00
raise_error ( ShellError : : OpenFailure , error ) ;
2020-07-05 15:18:26 +00:00
else
2021-01-03 09:08:20 +00:00
dbgln ( " open() failed for {} " , error ) ;
2020-06-17 15:07:44 +00:00
return false ;
}
2023-05-18 13:32:38 +00:00
auto file = file_or_error . release_value ( ) ;
auto data_or_error = file - > read_until_eof ( ) ;
if ( data_or_error . is_error ( ) ) {
auto error = DeprecatedString : : formatted ( " '{}': {} " , escape_token_for_single_quotes ( filename ) , data_or_error . error ( ) ) ;
if ( explicitly_invoked )
raise_error ( ShellError : : OpenFailure , error ) ;
else
dbgln ( " reading after open() failed for {} " , error ) ;
return false ;
}
return run_command ( data_or_error . value ( ) ) = = 0 ;
2020-06-17 15:07:44 +00:00
}
2021-05-24 12:20:16 +00:00
bool Shell : : is_allowed_to_modify_termios ( const AST : : Command & command ) const
{
if ( command . argv . is_empty ( ) )
return false ;
2023-04-29 17:03:37 +00:00
auto value = look_up_local_variable ( " PROGRAMS_ALLOWED_TO_MODIFY_DEFAULT_TERMIOS " sv ) ;
2023-02-18 14:09:41 +00:00
if ( value . is_error ( ) )
return false ;
if ( ! value . value ( ) )
2021-05-24 12:20:16 +00:00
return false ;
2023-02-18 14:09:41 +00:00
auto result = const_cast < AST : : Value & > ( * value . value ( ) ) . resolve_as_list ( const_cast < Shell & > ( * this ) ) ;
if ( result . is_error ( ) )
return false ;
return result . value ( ) . contains_slow ( command . argv [ 0 ] ) ;
2021-05-24 12:20:16 +00:00
}
2020-08-04 18:07:47 +00:00
void Shell : : restore_ios ( )
2020-06-17 13:35:06 +00:00
{
2020-09-07 18:15:31 +00:00
if ( m_is_subshell )
return ;
2020-06-17 13:35:06 +00:00
tcsetattr ( 0 , TCSANOW , & termios ) ;
2020-08-04 18:07:47 +00:00
tcsetpgrp ( STDOUT_FILENO , m_pid ) ;
tcsetpgrp ( STDIN_FILENO , m_pid ) ;
2020-06-17 13:35:06 +00:00
}
2020-05-16 19:05:13 +00:00
2020-10-28 14:15:17 +00:00
void Shell : : block_on_pipeline ( RefPtr < AST : : Pipeline > pipeline )
{
if ( ! pipeline )
return ;
for ( auto & it : jobs ) {
if ( auto cmd = it . value - > command_ptr ( ) ; cmd - > pipeline = = pipeline & & cmd - > is_pipe_source )
block_on_job ( it . value ) ;
}
}
2020-06-17 13:35:06 +00:00
void Shell : : block_on_job ( RefPtr < Job > job )
{
2023-02-20 17:26:54 +00:00
TemporaryChange < Job * > current_job { m_current_job , job . ptr ( ) } ;
2020-07-12 14:21:47 +00:00
2020-06-17 13:35:06 +00:00
if ( ! job )
return ;
2020-05-16 19:05:13 +00:00
2021-03-29 21:49:02 +00:00
if ( job - > is_suspended ( ) & & ! job - > shell_did_continue ( ) )
2020-09-01 04:12:16 +00:00
return ; // We cannot wait for a suspended job.
2020-08-15 18:50:39 +00:00
ScopeGuard io_restorer { [ & ] ( ) {
if ( job - > exited ( ) & & ! job - > is_running_in_background ( ) ) {
restore_ios ( ) ;
}
} } ;
2021-01-19 20:59:40 +00:00
bool job_exited { false } ;
2020-06-17 13:35:06 +00:00
job - > on_exit = [ & , old_exit = move ( job - > on_exit ) ] ( auto job ) {
if ( old_exit )
old_exit ( job ) ;
2021-01-19 20:59:40 +00:00
job_exited = true ;
2020-06-17 13:35:06 +00:00
} ;
2020-08-15 18:50:39 +00:00
if ( job - > exited ( ) )
2020-06-17 13:35:06 +00:00
return ;
2020-05-24 22:02:41 +00:00
2021-01-19 20:59:40 +00:00
while ( ! job_exited )
Core : : EventLoop : : current ( ) . pump ( ) ;
2020-10-28 14:15:17 +00:00
// If the job is part of a pipeline, wait for the rest of the members too.
if ( auto command = job - > command_ptr ( ) )
block_on_pipeline ( command - > pipeline ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : get_history_path ( )
2020-05-16 19:05:13 +00:00
{
2020-10-25 23:29:37 +00:00
if ( auto histfile = getenv ( " HISTFILE " ) )
return { histfile } ;
2022-12-04 18:02:33 +00:00
return DeprecatedString : : formatted ( " {}/.history " , home ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : escape_token_for_single_quotes ( StringView token )
2021-01-03 09:08:20 +00:00
{
2021-01-11 09:34:59 +00:00
// `foo bar \n '` -> `'foo bar \n '"'"`
2021-01-03 09:08:20 +00:00
StringBuilder builder ;
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2021-01-11 09:34:59 +00:00
auto started_single_quote = true ;
2021-01-03 09:08:20 +00:00
for ( auto c : token ) {
switch ( c ) {
case ' \' ' :
2022-07-11 17:32:29 +00:00
builder . append ( " \" ' \" " sv ) ;
2021-01-11 09:34:59 +00:00
started_single_quote = false ;
continue ;
default :
builder . append ( c ) ;
if ( ! started_single_quote ) {
started_single_quote = true ;
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2021-01-11 09:34:59 +00:00
}
2021-01-03 09:08:20 +00:00
break ;
2021-01-11 09:34:59 +00:00
}
}
if ( started_single_quote )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2021-01-11 09:34:59 +00:00
2023-01-26 18:58:09 +00:00
return builder . to_deprecated_string ( ) ;
2021-01-11 09:34:59 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : escape_token_for_double_quotes ( StringView token )
2021-01-11 09:34:59 +00:00
{
// `foo bar \n $x 'blah "hello` -> `"foo bar \\n $x 'blah \"hello"`
StringBuilder builder ;
builder . append ( ' " ' ) ;
for ( auto c : token ) {
switch ( c ) {
case ' \" ' :
2022-07-11 17:32:29 +00:00
builder . append ( " \\ \" " sv ) ;
2021-01-11 09:34:59 +00:00
continue ;
case ' \\ ' :
2022-07-11 17:32:29 +00:00
builder . append ( " \\ \\ " sv ) ;
2021-01-11 09:34:59 +00:00
continue ;
2021-01-03 09:08:20 +00:00
default :
2021-01-11 09:34:59 +00:00
builder . append ( c ) ;
2021-01-03 09:08:20 +00:00
break ;
}
}
2021-01-11 09:34:59 +00:00
builder . append ( ' " ' ) ;
2023-01-26 18:58:09 +00:00
return builder . to_deprecated_string ( ) ;
2021-01-03 09:08:20 +00:00
}
2022-03-06 08:28:49 +00:00
Shell : : SpecialCharacterEscapeMode Shell : : special_character_escape_mode ( u32 code_point , EscapeMode mode )
2021-01-09 00:02:19 +00:00
{
2021-05-10 06:39:40 +00:00
switch ( code_point ) {
2021-01-09 00:02:19 +00:00
case ' \' ' :
2022-03-06 08:28:49 +00:00
if ( mode = = EscapeMode : : DoubleQuotedString )
return SpecialCharacterEscapeMode : : Untouched ;
return SpecialCharacterEscapeMode : : Escaped ;
2021-01-09 00:02:19 +00:00
case ' " ' :
case ' $ ' :
2022-03-06 08:28:49 +00:00
case ' \\ ' :
if ( mode = = EscapeMode : : SingleQuotedString )
return SpecialCharacterEscapeMode : : Untouched ;
return SpecialCharacterEscapeMode : : Escaped ;
2021-01-09 00:02:19 +00:00
case ' | ' :
case ' > ' :
case ' < ' :
case ' ( ' :
case ' ) ' :
case ' { ' :
case ' } ' :
case ' & ' :
2021-05-10 06:39:40 +00:00
case ' ; ' :
2022-03-06 09:34:31 +00:00
case ' ? ' :
case ' * ' :
2021-01-09 00:02:19 +00:00
case ' ' :
2022-03-06 08:28:49 +00:00
if ( mode = = EscapeMode : : SingleQuotedString | | mode = = EscapeMode : : DoubleQuotedString )
return SpecialCharacterEscapeMode : : Untouched ;
2021-05-10 06:39:40 +00:00
return SpecialCharacterEscapeMode : : Escaped ;
case ' \n ' :
case ' \t ' :
case ' \r ' :
return SpecialCharacterEscapeMode : : QuotedAsEscape ;
2021-01-09 00:02:19 +00:00
default :
2021-05-10 06:39:40 +00:00
// FIXME: Should instead use unicode's "graphic" property (categories L, M, N, P, S, Zs)
2021-06-01 19:18:08 +00:00
if ( is_ascii ( code_point ) )
return is_ascii_printable ( code_point ) ? SpecialCharacterEscapeMode : : Untouched : SpecialCharacterEscapeMode : : QuotedAsHex ;
2021-05-10 06:39:40 +00:00
return SpecialCharacterEscapeMode : : Untouched ;
2021-01-09 00:02:19 +00:00
}
}
2022-12-04 18:02:33 +00:00
static DeprecatedString do_escape ( Shell : : EscapeMode escape_mode , auto & token )
2020-05-16 19:05:13 +00:00
{
2022-03-06 09:34:31 +00:00
StringBuilder builder ;
for ( auto c : token ) {
static_assert ( sizeof ( c ) = = sizeof ( u32 ) | | sizeof ( c ) = = sizeof ( u8 ) ) ;
switch ( Shell : : special_character_escape_mode ( c , escape_mode ) ) {
case Shell : : SpecialCharacterEscapeMode : : Untouched :
if constexpr ( sizeof ( c ) = = sizeof ( u8 ) )
2021-05-10 06:39:40 +00:00
builder . append ( c ) ;
2022-03-06 09:34:31 +00:00
else
builder . append ( Utf32View { & c , 1 } ) ;
break ;
case Shell : : SpecialCharacterEscapeMode : : Escaped :
if ( escape_mode = = Shell : : EscapeMode : : SingleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2022-03-06 09:34:31 +00:00
builder . append ( ' \\ ' ) ;
builder . append ( c ) ;
if ( escape_mode = = Shell : : EscapeMode : : SingleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2022-03-06 09:34:31 +00:00
break ;
case Shell : : SpecialCharacterEscapeMode : : QuotedAsEscape :
if ( escape_mode = = Shell : : EscapeMode : : SingleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2022-03-06 09:34:31 +00:00
if ( escape_mode ! = Shell : : EscapeMode : : DoubleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " \" " sv ) ;
2022-03-06 09:34:31 +00:00
switch ( c ) {
case ' \n ' :
2022-07-11 17:32:29 +00:00
builder . append ( R " ( \n ) " sv ) ;
2021-05-10 06:39:40 +00:00
break ;
2022-03-06 09:34:31 +00:00
case ' \t ' :
2022-07-11 17:32:29 +00:00
builder . append ( R " ( \t ) " sv ) ;
2021-05-10 06:39:40 +00:00
break ;
2022-03-06 09:34:31 +00:00
case ' \r ' :
2022-07-11 17:32:29 +00:00
builder . append ( R " ( \r ) " sv ) ;
2021-05-10 06:39:40 +00:00
break ;
2022-03-06 09:34:31 +00:00
default :
VERIFY_NOT_REACHED ( ) ;
2021-05-10 06:39:40 +00:00
}
2022-03-06 09:34:31 +00:00
if ( escape_mode ! = Shell : : EscapeMode : : DoubleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " \" " sv ) ;
2022-03-06 09:34:31 +00:00
if ( escape_mode = = Shell : : EscapeMode : : SingleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2022-03-06 09:34:31 +00:00
break ;
case Shell : : SpecialCharacterEscapeMode : : QuotedAsHex :
if ( escape_mode = = Shell : : EscapeMode : : SingleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2022-03-06 09:34:31 +00:00
if ( escape_mode ! = Shell : : EscapeMode : : DoubleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " \" " sv ) ;
2022-03-06 09:34:31 +00:00
if ( c < = NumericLimits < u8 > : : max ( ) )
builder . appendff ( R " ( \ x{:0>2x}) " , static_cast < u8 > ( c ) ) ;
else
builder . appendff ( R " ( \ u{:0>8x}) " , static_cast < u32 > ( c ) ) ;
if ( escape_mode ! = Shell : : EscapeMode : : DoubleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " \" " sv ) ;
2022-03-06 09:34:31 +00:00
if ( escape_mode = = Shell : : EscapeMode : : SingleQuotedString )
2022-07-11 17:32:29 +00:00
builder . append ( " ' " sv ) ;
2022-03-06 09:34:31 +00:00
break ;
2021-05-10 06:39:40 +00:00
}
2022-03-06 09:34:31 +00:00
}
2020-05-16 19:05:13 +00:00
2023-01-26 18:58:09 +00:00
return builder . to_deprecated_string ( ) ;
2022-03-06 09:34:31 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : escape_token ( Utf32View token , EscapeMode escape_mode )
2022-03-06 09:34:31 +00:00
{
return do_escape ( escape_mode , token ) ;
}
2020-05-16 19:05:13 +00:00
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : escape_token ( StringView token , EscapeMode escape_mode )
2022-03-06 09:34:31 +00:00
{
2021-05-10 06:39:40 +00:00
Utf8View view { token } ;
if ( view . validate ( ) )
2022-03-06 09:34:31 +00:00
return do_escape ( escape_mode , view ) ;
return do_escape ( escape_mode , token ) ;
2020-05-16 19:05:13 +00:00
}
2022-12-04 18:02:33 +00:00
DeprecatedString Shell : : unescape_token ( StringView token )
2020-05-16 19:05:13 +00:00
{
StringBuilder builder ;
enum {
Free ,
Escaped
} state { Free } ;
for ( auto c : token ) {
switch ( state ) {
case Escaped :
builder . append ( c ) ;
state = Free ;
break ;
case Free :
if ( c = = ' \\ ' )
state = Escaped ;
else
builder . append ( c ) ;
break ;
}
}
if ( state = = Escaped )
builder . append ( ' \\ ' ) ;
2023-01-26 18:58:09 +00:00
return builder . to_deprecated_string ( ) ;
2020-05-16 19:05:13 +00:00
}
void Shell : : cache_path ( )
{
2021-03-07 06:12:24 +00:00
if ( ! m_is_interactive )
return ;
2020-05-16 19:05:13 +00:00
if ( ! cached_path . is_empty ( ) )
cached_path . clear_with_capacity ( ) ;
2020-07-07 18:12:32 +00:00
// Add shell builtins to the cache.
2022-04-01 17:58:27 +00:00
for ( auto const & builtin_name : builtin_names )
2022-04-20 19:36:00 +00:00
cached_path . append ( { RunnablePath : : Kind : : Builtin , escape_token ( builtin_name ) } ) ;
2020-05-16 19:05:13 +00:00
2021-03-06 12:28:26 +00:00
// Add functions to the cache.
for ( auto & function : m_functions ) {
auto name = escape_token ( function . key ) ;
if ( cached_path . contains_slow ( name ) )
continue ;
2022-04-20 19:36:00 +00:00
cached_path . append ( { RunnablePath : : Kind : : Function , name } ) ;
2021-03-06 12:28:26 +00:00
}
2020-07-07 18:12:32 +00:00
// Add aliases to the cache.
2022-04-01 17:58:27 +00:00
for ( auto const & alias : m_aliases ) {
2020-07-07 18:12:32 +00:00
auto name = escape_token ( alias . key ) ;
if ( cached_path . contains_slow ( name ) )
continue ;
2022-04-20 19:36:00 +00:00
cached_path . append ( { RunnablePath : : Kind : : Alias , name } ) ;
2020-05-16 19:05:13 +00:00
}
2023-05-29 20:20:05 +00:00
// TODO: Can we make this rely on Core::System::resolve_executable_from_environment()?
2022-12-04 18:02:33 +00:00
DeprecatedString path = getenv ( " PATH " ) ;
2020-07-07 18:12:32 +00:00
if ( ! path . is_empty ( ) ) {
auto directories = path . split ( ' : ' ) ;
2022-04-01 17:58:27 +00:00
for ( auto const & directory : directories ) {
2020-07-07 18:12:32 +00:00
Core : : DirIterator programs ( directory . characters ( ) , Core : : DirIterator : : SkipDots ) ;
while ( programs . has_next ( ) ) {
auto program = programs . next_path ( ) ;
2022-12-04 18:02:33 +00:00
auto program_path = DeprecatedString : : formatted ( " {}/{} " , directory , program ) ;
2020-07-07 18:12:32 +00:00
auto escaped_name = escape_token ( program ) ;
if ( cached_path . contains_slow ( escaped_name ) )
continue ;
if ( access ( program_path . characters ( ) , X_OK ) = = 0 )
2022-04-20 19:36:00 +00:00
cached_path . append ( { RunnablePath : : Kind : : Executable , escaped_name } ) ;
2020-07-07 18:12:32 +00:00
}
}
}
2020-05-17 10:34:22 +00:00
2020-05-16 19:05:13 +00:00
quick_sort ( cached_path ) ;
}
2022-04-20 19:36:00 +00:00
void Shell : : add_entry_to_cache ( RunnablePath const & entry )
2020-08-03 09:06:42 +00:00
{
size_t index = 0 ;
2022-04-20 19:36:00 +00:00
auto match = binary_search ( cached_path . span ( ) , entry , & index , RunnablePathComparator { } ) ;
2020-08-03 09:06:42 +00:00
if ( match )
return ;
2022-04-20 19:36:00 +00:00
while ( index < cached_path . size ( ) & & strcmp ( cached_path [ index ] . path . characters ( ) , entry . path . characters ( ) ) < 0 ) {
2020-08-03 09:06:42 +00:00
index + + ;
}
cached_path . insert ( index , entry ) ;
}
2022-01-29 15:24:01 +00:00
void Shell : : remove_entry_from_cache ( StringView entry )
2021-07-12 20:00:25 +00:00
{
size_t index { 0 } ;
2022-04-20 19:36:00 +00:00
auto match = binary_search ( cached_path . span ( ) , entry , & index , RunnablePathComparator { } ) ;
2021-07-12 20:00:25 +00:00
if ( match )
cached_path . remove ( index ) ;
}
2023-02-18 14:09:41 +00:00
ErrorOr < void > Shell : : highlight ( Line : : Editor & editor ) const
2020-05-16 19:05:13 +00:00
{
2020-06-17 13:35:06 +00:00
auto line = editor . line ( ) ;
2023-02-11 14:29:15 +00:00
auto ast = parse ( line , m_is_interactive ) ;
2020-06-17 13:35:06 +00:00
if ( ! ast )
2023-02-18 14:09:41 +00:00
return { } ;
return ast - > highlight_in_editor ( editor , const_cast < Shell & > ( * this ) ) ;
2020-05-16 19:05:13 +00:00
}
2020-09-23 05:36:30 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete ( )
2020-05-16 19:05:13 +00:00
{
2022-04-11 21:57:16 +00:00
m_completion_stack_info = { } ;
return complete ( m_editor - > line ( m_editor - > cursor ( ) ) ) ;
}
2020-05-16 19:05:13 +00:00
2022-04-11 21:57:16 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete ( StringView line )
{
2023-02-11 14:29:15 +00:00
auto ast = parse ( line , m_is_interactive ) ;
2020-05-19 04:12:01 +00:00
2020-06-17 13:35:06 +00:00
if ( ! ast )
2020-05-19 04:12:01 +00:00
return { } ;
2023-02-18 13:57:14 +00:00
return ast - > complete_for_editor ( * this , line . length ( ) ) . release_value_but_fixme_should_propagate_errors ( ) ;
2020-06-17 13:35:06 +00:00
}
2020-05-16 19:05:13 +00:00
2022-03-22 20:44:48 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete_path ( StringView base , StringView part , size_t offset , ExecutableOnly executable_only , AST : : Node const * command_node , AST : : Node const * node , EscapeMode escape_mode )
2020-06-17 13:35:06 +00:00
{
2022-07-11 17:32:29 +00:00
auto token = offset ? part . substring_view ( 0 , offset ) : " " sv ;
2022-12-04 18:02:33 +00:00
DeprecatedString path ;
2020-05-16 19:05:13 +00:00
ssize_t last_slash = token . length ( ) - 1 ;
while ( last_slash > = 0 & & token [ last_slash ] ! = ' / ' )
- - last_slash ;
2022-03-22 20:44:48 +00:00
if ( command_node ) {
auto program_results = complete_via_program_itself ( offset , command_node , node , escape_mode , { } ) ;
if ( ! program_results . is_error ( ) )
return program_results . release_value ( ) ;
}
2020-06-23 14:40:41 +00:00
StringBuilder path_builder ;
auto init_slash_part = token . substring_view ( 0 , last_slash + 1 ) ;
auto last_slash_part = token . substring_view ( last_slash + 1 , token . length ( ) - last_slash - 1 ) ;
2021-05-02 08:17:30 +00:00
bool allow_direct_children = true ;
2020-06-23 14:40:41 +00:00
// Depending on the base, we will have to prepend cwd.
if ( base . is_empty ( ) ) {
// '' /foo -> absolute
// '' foo -> relative
if ( ! token . starts_with ( ' / ' ) )
path_builder . append ( cwd ) ;
path_builder . append ( ' / ' ) ;
path_builder . append ( init_slash_part ) ;
2021-05-02 08:17:30 +00:00
if ( executable_only = = ExecutableOnly : : Yes & & init_slash_part . is_empty ( ) )
allow_direct_children = false ;
2020-05-16 19:05:13 +00:00
} else {
2020-06-23 14:40:41 +00:00
// /foo * -> absolute
// foo * -> relative
if ( ! base . starts_with ( ' / ' ) )
path_builder . append ( cwd ) ;
path_builder . append ( ' / ' ) ;
path_builder . append ( base ) ;
path_builder . append ( ' / ' ) ;
path_builder . append ( init_slash_part ) ;
2020-05-16 19:05:13 +00:00
}
2023-01-26 18:58:09 +00:00
path = path_builder . to_deprecated_string ( ) ;
2020-06-23 14:40:41 +00:00
token = last_slash_part ;
2020-05-16 19:05:13 +00:00
// the invariant part of the token is actually just the last segment
// e. in `cd /foo/bar', 'bar' is the invariant
// since we are not suggesting anything starting with
// `/foo/', but rather just `bar...'
2022-03-06 08:28:49 +00:00
auto token_length = escape_token ( token , escape_mode ) . length ( ) ;
2022-04-14 21:18:56 +00:00
size_t static_offset = 0 ;
2022-02-28 13:58:47 +00:00
auto invariant_offset = token_length ;
2020-10-01 14:43:01 +00:00
if ( m_editor )
2022-02-28 13:58:47 +00:00
m_editor - > transform_suggestion_offsets ( invariant_offset , static_offset ) ;
2020-05-16 19:05:13 +00:00
// only suggest dot-files if path starts with a dot
Core : : DirIterator files ( path ,
token . starts_with ( ' . ' ) ? Core : : DirIterator : : SkipParentAndBaseDir : Core : : DirIterator : : SkipDots ) ;
2020-06-17 13:35:06 +00:00
Vector < Line : : CompletionSuggestion > suggestions ;
2020-05-16 19:05:13 +00:00
while ( files . has_next ( ) ) {
auto file = files . next_path ( ) ;
if ( file . starts_with ( token ) ) {
struct stat program_status ;
2022-12-04 18:02:33 +00:00
auto file_path = DeprecatedString : : formatted ( " {}/{} " , path , file ) ;
2020-05-16 19:05:13 +00:00
int stat_error = stat ( file_path . characters ( ) , & program_status ) ;
2021-04-20 14:17:38 +00:00
if ( ! stat_error & & ( executable_only = = ExecutableOnly : : No | | access ( file_path . characters ( ) , X_OK ) = = 0 ) ) {
2020-05-19 04:12:01 +00:00
if ( S_ISDIR ( program_status . st_mode ) ) {
2022-07-11 17:32:29 +00:00
suggestions . append ( { escape_token ( file , escape_mode ) , " / " sv } ) ;
2020-05-19 04:12:01 +00:00
} else {
2022-07-11 17:32:29 +00:00
if ( ! allow_direct_children & & ! file . contains ( ' / ' ) )
2021-05-02 08:17:30 +00:00
continue ;
2022-07-11 17:32:29 +00:00
suggestions . append ( { escape_token ( file , escape_mode ) , " " sv } ) ;
2020-05-19 04:12:01 +00:00
}
2020-10-03 13:54:49 +00:00
suggestions . last ( ) . input_offset = token_length ;
2022-02-28 13:58:47 +00:00
suggestions . last ( ) . invariant_offset = invariant_offset ;
suggestions . last ( ) . static_offset = static_offset ;
2020-05-16 19:05:13 +00:00
}
}
}
2022-09-12 12:28:16 +00:00
// The results of DirIterator are in the order they appear on-disk.
// Instead, return suggestions in lexicographical order.
quick_sort ( suggestions , [ ] ( auto & a , auto & b ) { return a . text_string < b . text_string ; } ) ;
2020-05-16 19:05:13 +00:00
return suggestions ;
}
2022-03-06 08:28:49 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete_program_name ( StringView name , size_t offset , EscapeMode escape_mode )
2020-06-17 13:35:06 +00:00
{
2020-12-29 15:13:16 +00:00
auto match = binary_search (
cached_path . span ( ) ,
name ,
nullptr ,
2022-02-05 11:06:34 +00:00
[ ] ( auto & name , auto & program ) {
return strncmp (
name . characters_without_null_termination ( ) ,
2022-04-20 19:36:00 +00:00
program . path . characters ( ) ,
2022-02-05 11:06:34 +00:00
name . length ( ) ) ;
} ) ;
2020-06-17 13:35:06 +00:00
if ( ! match )
2022-07-11 17:32:29 +00:00
return complete_path ( " " sv , name , offset , ExecutableOnly : : Yes , nullptr , nullptr , escape_mode ) ;
2020-06-17 13:35:06 +00:00
2022-12-04 18:02:33 +00:00
DeprecatedString completion = match - > path ;
2022-03-06 08:28:49 +00:00
auto token_length = escape_token ( name , escape_mode ) . length ( ) ;
2022-02-28 13:58:47 +00:00
auto invariant_offset = token_length ;
size_t static_offset = 0 ;
2020-10-01 14:43:01 +00:00
if ( m_editor )
2022-02-28 13:58:47 +00:00
m_editor - > transform_suggestion_offsets ( invariant_offset , static_offset ) ;
2020-06-17 13:35:06 +00:00
// Now that we have a program name starting with our token, we look at
// other program names starting with our token and cut off any mismatching
// characters.
Vector < Line : : CompletionSuggestion > suggestions ;
int index = match - cached_path . data ( ) ;
2022-04-20 19:36:00 +00:00
for ( int i = index - 1 ; i > = 0 & & cached_path [ i ] . path . starts_with ( name ) ; - - i )
2022-07-11 17:32:29 +00:00
suggestions . append ( { cached_path [ i ] . path , " " sv } ) ;
2022-04-20 19:36:00 +00:00
for ( size_t i = index + 1 ; i < cached_path . size ( ) & & cached_path [ i ] . path . starts_with ( name ) ; + + i )
2022-07-11 17:32:29 +00:00
suggestions . append ( { cached_path [ i ] . path , " " sv } ) ;
suggestions . append ( { cached_path [ index ] . path , " " sv } ) ;
2022-02-28 13:58:47 +00:00
for ( auto & entry : suggestions ) {
entry . input_offset = token_length ;
entry . invariant_offset = invariant_offset ;
entry . static_offset = static_offset ;
}
2020-06-17 13:35:06 +00:00
return suggestions ;
}
2022-01-29 15:24:01 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete_variable ( StringView name , size_t offset )
2020-06-17 13:35:06 +00:00
{
Vector < Line : : CompletionSuggestion > suggestions ;
2022-07-11 17:32:29 +00:00
auto pattern = offset ? name . substring_view ( 0 , offset ) : " " sv ;
2020-06-17 13:35:06 +00:00
2022-02-28 13:58:47 +00:00
auto invariant_offset = offset ;
size_t static_offset = 0 ;
2020-10-01 14:43:01 +00:00
if ( m_editor )
2022-02-28 13:58:47 +00:00
m_editor - > transform_suggestion_offsets ( invariant_offset , static_offset ) ;
2020-06-17 13:35:06 +00:00
// Look at local variables.
2020-07-11 21:12:46 +00:00
for ( auto & frame : m_local_frames ) {
2023-03-06 16:16:25 +00:00
for ( auto & variable : frame - > local_variables ) {
2020-07-11 21:12:46 +00:00
if ( variable . key . starts_with ( pattern ) & & ! suggestions . contains_slow ( variable . key ) )
suggestions . append ( variable . key ) ;
}
2020-06-17 13:35:06 +00:00
}
// Look at the environment.
for ( auto i = 0 ; environ [ i ] ; + + i ) {
2022-07-11 19:53:29 +00:00
StringView entry { environ [ i ] , strlen ( environ [ i ] ) } ;
2020-06-17 13:35:06 +00:00
if ( entry . starts_with ( pattern ) ) {
auto parts = entry . split_view ( ' = ' ) ;
if ( parts . is_empty ( ) | | parts . first ( ) . is_empty ( ) )
continue ;
2022-12-04 18:02:33 +00:00
DeprecatedString name = parts . first ( ) ;
2020-06-17 13:35:06 +00:00
if ( suggestions . contains_slow ( name ) )
continue ;
suggestions . append ( move ( name ) ) ;
}
}
2022-02-28 13:58:47 +00:00
for ( auto & entry : suggestions ) {
entry . input_offset = offset ;
entry . invariant_offset = invariant_offset ;
entry . static_offset = static_offset ;
}
2020-06-17 13:35:06 +00:00
return suggestions ;
}
2022-01-29 15:24:01 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete_user ( StringView name , size_t offset )
2020-06-23 14:40:41 +00:00
{
Vector < Line : : CompletionSuggestion > suggestions ;
2022-07-11 17:32:29 +00:00
auto pattern = offset ? name . substring_view ( 0 , offset ) : " " sv ;
2020-06-23 14:40:41 +00:00
2022-02-28 13:58:47 +00:00
auto invariant_offset = offset ;
size_t static_offset = 0 ;
2020-10-01 14:43:01 +00:00
if ( m_editor )
2022-02-28 13:58:47 +00:00
m_editor - > transform_suggestion_offsets ( invariant_offset , static_offset ) ;
2020-06-23 14:40:41 +00:00
Core : : DirIterator di ( " /home " , Core : : DirIterator : : SkipParentAndBaseDir ) ;
if ( di . has_error ( ) )
return suggestions ;
while ( di . has_next ( ) ) {
2022-12-04 18:02:33 +00:00
DeprecatedString name = di . next_path ( ) ;
2020-10-03 13:54:49 +00:00
if ( name . starts_with ( pattern ) ) {
2020-06-23 14:40:41 +00:00
suggestions . append ( name ) ;
2022-02-28 13:58:47 +00:00
auto & suggestion = suggestions . last ( ) ;
suggestion . input_offset = offset ;
suggestion . invariant_offset = invariant_offset ;
suggestion . static_offset = static_offset ;
2020-10-03 13:54:49 +00:00
}
2020-06-23 14:40:41 +00:00
}
return suggestions ;
}
2022-03-22 20:44:48 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete_option ( StringView program_name , StringView option , size_t offset , AST : : Node const * command_node , AST : : Node const * node )
2020-06-29 01:56:06 +00:00
{
2022-03-22 20:44:48 +00:00
if ( command_node ) {
auto program_results = complete_via_program_itself ( offset , command_node , node , EscapeMode : : Bareword , program_name ) ;
if ( ! program_results . is_error ( ) )
return program_results . release_value ( ) ;
}
2020-06-29 01:56:06 +00:00
size_t start = 0 ;
2020-07-05 14:08:38 +00:00
while ( start < option . length ( ) & & option [ start ] = = ' - ' & & start < 2 )
2020-06-29 01:56:06 +00:00
+ + start ;
2022-07-11 17:32:29 +00:00
auto option_pattern = offset > start ? option . substring_view ( start , offset - start ) : " " sv ;
2022-02-28 13:58:47 +00:00
auto invariant_offset = offset ;
size_t static_offset = 0 ;
2020-10-01 14:43:01 +00:00
if ( m_editor )
2022-02-28 13:58:47 +00:00
m_editor - > transform_suggestion_offsets ( invariant_offset , static_offset ) ;
2020-06-29 01:56:06 +00:00
2020-12-06 17:21:40 +00:00
dbgln ( " Shell::complete_option({}, {}) " , program_name , option_pattern ) ;
2022-03-22 20:44:48 +00:00
return { } ;
}
2020-06-29 01:56:06 +00:00
2022-03-22 20:44:48 +00:00
ErrorOr < Vector < Line : : CompletionSuggestion > > Shell : : complete_via_program_itself ( size_t , AST : : Node const * command_node , AST : : Node const * node , EscapeMode , StringView known_program_name )
{
if ( ! command_node )
return Error : : from_string_literal ( " Cannot complete null command " ) ;
if ( command_node - > would_execute ( ) )
return Error : : from_string_literal ( " Refusing to complete nodes that would execute " ) ;
2023-02-18 06:45:08 +00:00
String program_name_storage ;
2022-03-22 20:44:48 +00:00
if ( known_program_name . is_null ( ) ) {
auto node = command_node - > leftmost_trivial_literal ( ) ;
if ( ! node )
return Error : : from_string_literal ( " Cannot complete " ) ;
2023-02-18 13:57:14 +00:00
program_name_storage = TRY ( TRY ( const_cast < AST : : Node & > ( * node ) . run ( * this ) ) - > resolve_as_string ( * this ) ) ;
2022-03-22 20:44:48 +00:00
known_program_name = program_name_storage ;
}
AST : : Command completion_command ;
2023-02-18 06:45:08 +00:00
completion_command . argv . append ( program_name_storage ) ;
2023-02-18 14:09:41 +00:00
completion_command = TRY ( expand_aliases ( { completion_command } ) ) . last ( ) ;
2022-03-22 20:44:48 +00:00
2023-02-18 14:09:41 +00:00
auto completion_utility_name = TRY ( String : : formatted ( " _complete_{} " , completion_command . argv [ 0 ] ) ) ;
2022-04-20 19:36:00 +00:00
if ( binary_search ( cached_path . span ( ) , completion_utility_name , nullptr , RunnablePathComparator { } ) ! = nullptr )
2022-03-22 20:44:48 +00:00
completion_command . argv [ 0 ] = completion_utility_name ;
2022-03-29 11:36:20 +00:00
else if ( ! options . invoke_program_for_autocomplete )
return Error : : from_string_literal ( " Refusing to use the program itself as completion source " ) ;
2022-03-22 20:44:48 +00:00
2023-08-08 02:26:17 +00:00
completion_command . argv . extend ( { " --complete " _string , " -- " _string } ) ;
2022-03-22 20:44:48 +00:00
struct Visitor : public AST : : NodeVisitor {
2022-04-15 22:48:56 +00:00
Visitor ( Shell & shell , AST : : Position position )
2022-03-22 20:44:48 +00:00
: shell ( shell )
2022-04-15 22:48:56 +00:00
, completion_position ( position )
2022-03-22 20:44:48 +00:00
{
lists . empend ( ) ;
}
Shell & shell ;
AST : : Position completion_position ;
2023-02-18 06:45:08 +00:00
Vector < Vector < String > > lists ;
2022-03-22 20:44:48 +00:00
bool fail { false } ;
void push_list ( ) { lists . empend ( ) ; }
2023-02-18 06:45:08 +00:00
Vector < String > pop_list ( ) { return lists . take_last ( ) ; }
Vector < String > & list ( ) { return lists . last ( ) ; }
2022-03-22 20:44:48 +00:00
bool should_include ( AST : : Node const * node ) const { return node - > position ( ) . end_offset < = completion_position . end_offset ; }
virtual void visit ( AST : : BarewordLiteral const * node ) override
{
if ( should_include ( node ) )
list ( ) . append ( node - > text ( ) ) ;
}
virtual void visit ( AST : : BraceExpansion const * node ) override
{
2023-02-18 13:57:14 +00:00
if ( should_include ( node ) ) {
auto value = static_cast < AST : : Node * > ( const_cast < AST : : BraceExpansion * > ( node ) ) - > run ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
auto entries = value - > resolve_as_list ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
list ( ) . extend ( move ( entries ) ) ;
}
2022-03-22 20:44:48 +00:00
}
virtual void visit ( AST : : CommandLiteral const * node ) override
{
if ( should_include ( node ) )
list ( ) . extend ( node - > command ( ) . argv ) ;
}
virtual void visit ( AST : : DynamicEvaluate const * node ) override
{
if ( should_include ( node ) )
fail = true ;
}
virtual void visit ( AST : : DoubleQuotedString const * node ) override
{
if ( ! should_include ( node ) )
return ;
push_list ( ) ;
AST : : NodeVisitor : : visit ( node ) ;
auto list = pop_list ( ) ;
StringBuilder builder ;
2022-07-11 17:32:29 +00:00
builder . join ( " " sv , list ) ;
2023-02-18 06:45:08 +00:00
this - > list ( ) . append ( builder . to_string ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-03-22 20:44:48 +00:00
}
virtual void visit ( AST : : Glob const * node ) override
{
if ( should_include ( node ) )
list ( ) . append ( node - > text ( ) ) ;
}
virtual void visit ( AST : : Heredoc const * node ) override
{
if ( ! should_include ( node ) )
return ;
push_list ( ) ;
AST : : NodeVisitor : : visit ( node ) ;
auto list = pop_list ( ) ;
StringBuilder builder ;
2022-07-11 17:32:29 +00:00
builder . join ( " " sv , list ) ;
2023-02-18 06:45:08 +00:00
this - > list ( ) . append ( builder . to_string ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-03-22 20:44:48 +00:00
}
virtual void visit ( AST : : ImmediateExpression const * node ) override
{
if ( should_include ( node ) )
fail = true ;
}
virtual void visit ( AST : : Range const * node ) override
{
if ( ! should_include ( node ) )
return ;
push_list ( ) ;
node - > start ( ) - > visit ( * this ) ;
list ( ) . append ( pop_list ( ) . first ( ) ) ;
}
virtual void visit ( AST : : SimpleVariable const * node ) override
{
2023-02-18 13:57:14 +00:00
if ( should_include ( node ) ) {
auto values = static_cast < AST : : Node * > ( const_cast < AST : : SimpleVariable * > ( node ) ) - > run ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
auto entries = values - > resolve_as_list ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
list ( ) . extend ( move ( entries ) ) ;
}
2022-03-22 20:44:48 +00:00
}
virtual void visit ( AST : : SpecialVariable const * node ) override
{
2023-02-18 13:57:14 +00:00
if ( should_include ( node ) ) {
auto values = static_cast < AST : : Node * > ( const_cast < AST : : SpecialVariable * > ( node ) ) - > run ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
auto entries = values - > resolve_as_list ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
list ( ) . extend ( move ( entries ) ) ;
}
2022-03-22 20:44:48 +00:00
}
virtual void visit ( AST : : Juxtaposition const * node ) override
{
if ( ! should_include ( node ) )
return ;
push_list ( ) ;
node - > left ( ) - > visit ( * this ) ;
auto left = pop_list ( ) ;
push_list ( ) ;
node - > right ( ) - > visit ( * this ) ;
auto right = pop_list ( ) ;
StringBuilder builder ;
for ( auto & left_entry : left ) {
for ( auto & right_entry : right ) {
builder . append ( left_entry ) ;
builder . append ( right_entry ) ;
2023-02-18 06:45:08 +00:00
list ( ) . append ( builder . to_string ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-03-22 20:44:48 +00:00
builder . clear ( ) ;
}
2022-02-28 13:58:47 +00:00
}
2022-03-22 20:44:48 +00:00
}
2022-02-28 13:58:47 +00:00
2022-03-22 20:44:48 +00:00
virtual void visit ( AST : : StringLiteral const * node ) override
{
if ( should_include ( node ) )
list ( ) . append ( node - > text ( ) ) ;
2020-06-29 01:56:06 +00:00
}
2022-03-22 20:44:48 +00:00
virtual void visit ( AST : : Tilde const * node ) override
{
2023-02-18 13:57:14 +00:00
if ( should_include ( node ) ) {
auto values = static_cast < AST : : Node * > ( const_cast < AST : : Tilde * > ( node ) ) - > run ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
auto entries = values - > resolve_as_list ( shell ) . release_value_but_fixme_should_propagate_errors ( ) ;
list ( ) . extend ( move ( entries ) ) ;
}
2022-03-22 20:44:48 +00:00
}
virtual void visit ( AST : : PathRedirectionNode const * ) override { }
virtual void visit ( AST : : CloseFdRedirection const * ) override { }
virtual void visit ( AST : : Fd2FdRedirection const * ) override { }
virtual void visit ( AST : : Execute const * ) override { }
virtual void visit ( AST : : ReadRedirection const * ) override { }
virtual void visit ( AST : : ReadWriteRedirection const * ) override { }
virtual void visit ( AST : : WriteAppendRedirection const * ) override { }
virtual void visit ( AST : : WriteRedirection const * ) override { }
2022-04-15 22:48:56 +00:00
} visitor { * this , node ? node - > position ( ) : AST : : Position ( ) } ;
2022-03-22 20:44:48 +00:00
command_node - > visit ( visitor ) ;
if ( visitor . fail )
return Error : : from_string_literal ( " Cannot complete " ) ;
completion_command . argv . extend ( visitor . list ( ) ) ;
2023-08-07 09:12:38 +00:00
auto devnull = " /dev/null " _string ;
2022-03-22 20:44:48 +00:00
completion_command . should_wait = true ;
2023-02-18 06:45:08 +00:00
completion_command . redirections . append ( AST : : PathRedirection : : create ( devnull , STDERR_FILENO , AST : : PathRedirection : : Write ) ) ;
completion_command . redirections . append ( AST : : PathRedirection : : create ( devnull , STDIN_FILENO , AST : : PathRedirection : : Read ) ) ;
2022-03-22 20:44:48 +00:00
auto execute_node = make_ref_counted < AST : : Execute > (
AST : : Position { } ,
make_ref_counted < AST : : CommandLiteral > ( AST : : Position { } , move ( completion_command ) ) ,
true ) ;
Vector < Line : : CompletionSuggestion > suggestions ;
2023-01-11 16:31:10 +00:00
auto timer = TRY ( Core : : Timer : : create_single_shot ( 300 , [ & ] {
2022-03-22 20:44:48 +00:00
Core : : EventLoop : : current ( ) . quit ( 1 ) ;
2023-01-11 16:31:10 +00:00
} ) ) ;
2022-03-22 20:44:48 +00:00
timer - > start ( ) ;
2022-03-24 20:49:48 +00:00
// Restrict the process to effectively readonly access to the FS.
auto scoped_promise = promise ( {
2023-04-12 09:18:17 +00:00
. exec_promises = " stdio rpath prot_exec map_fixed no_error " ,
2022-03-24 20:49:48 +00:00
. unveils = {
{ " / " , " rx " } ,
} ,
} ) ;
2022-04-11 21:57:16 +00:00
{
TemporaryChange change ( m_is_interactive , false ) ;
2023-02-18 13:57:14 +00:00
TRY ( execute_node - > for_each_entry ( * this , [ & ] ( NonnullRefPtr < AST : : Value > entry ) - > ErrorOr < IterationDecision > {
auto result = TRY ( entry - > resolve_as_string ( * this ) ) ;
2022-04-11 21:57:16 +00:00
JsonParser parser ( result ) ;
auto parsed_result = parser . parse ( ) ;
if ( parsed_result . is_error ( ) )
return IterationDecision : : Continue ;
auto parsed = parsed_result . release_value ( ) ;
if ( parsed . is_object ( ) ) {
auto & object = parsed . as_object ( ) ;
2022-12-21 21:00:22 +00:00
auto kind = object . get_deprecated_string ( " kind " sv ) . value_or ( " plain " ) ;
2022-04-11 21:57:16 +00:00
if ( kind = = " path " ) {
2022-12-21 21:00:22 +00:00
auto base = object . get_deprecated_string ( " base " sv ) . value_or ( " " ) ;
auto part = object . get_deprecated_string ( " part " sv ) . value_or ( " " ) ;
auto executable_only = object . get_bool ( " executable_only " sv ) . value_or ( false ) ? ExecutableOnly : : Yes : ExecutableOnly : : No ;
2022-04-11 21:57:16 +00:00
suggestions . extend ( complete_path ( base , part , part . length ( ) , executable_only , nullptr , nullptr ) ) ;
} else if ( kind = = " program " ) {
2022-12-21 21:00:22 +00:00
auto name = object . get_deprecated_string ( " name " sv ) . value_or ( " " ) ;
2022-04-11 21:57:16 +00:00
suggestions . extend ( complete_program_name ( name , name . length ( ) ) ) ;
} else if ( kind = = " proxy " ) {
if ( m_completion_stack_info . size_free ( ) < 4 * KiB ) {
dbgln ( " Not enough stack space, recursion? " ) ;
return IterationDecision : : Continue ;
}
2022-12-21 21:00:22 +00:00
auto argv = object . get_deprecated_string ( " argv " sv ) . value_or ( " " ) ;
2022-04-11 21:57:16 +00:00
dbgln ( " Proxy completion for {} " , argv ) ;
suggestions . extend ( complete ( argv ) ) ;
} else if ( kind = = " plain " ) {
Line : : CompletionSuggestion suggestion {
2022-12-21 21:00:22 +00:00
object . get_deprecated_string ( " completion " sv ) . value_or ( " " ) ,
object . get_deprecated_string ( " trailing_trivia " sv ) . value_or ( " " ) ,
object . get_deprecated_string ( " display_trivia " sv ) . value_or ( " " ) ,
2022-04-11 21:57:16 +00:00
} ;
2022-12-21 21:00:22 +00:00
suggestion . static_offset = object . get_u64 ( " static_offset " sv ) . value_or ( 0 ) ;
suggestion . invariant_offset = object . get_u64 ( " invariant_offset " sv ) . value_or ( 0 ) ;
suggestion . allow_commit_without_listing = object . get_bool ( " allow_commit_without_listing " sv ) . value_or ( true ) ;
2022-04-11 21:57:16 +00:00
suggestions . append ( move ( suggestion ) ) ;
} else {
dbgln ( " LibLine: Unhandled completion kind: {} " , kind ) ;
}
} else {
2022-12-06 01:12:49 +00:00
suggestions . append ( parsed . to_deprecated_string ( ) ) ;
2022-04-11 21:57:16 +00:00
}
2022-03-22 20:44:48 +00:00
2022-04-11 21:57:16 +00:00
return IterationDecision : : Continue ;
2023-02-18 13:57:14 +00:00
} ) ) ;
2022-04-11 21:57:16 +00:00
}
2022-03-22 20:44:48 +00:00
auto pgid = getpgrp ( ) ;
tcsetpgrp ( STDOUT_FILENO , pgid ) ;
tcsetpgrp ( STDIN_FILENO , pgid ) ;
if ( suggestions . is_empty ( ) )
return Error : : from_string_literal ( " No results " ) ;
2020-06-29 01:56:06 +00:00
return suggestions ;
}
2022-01-29 15:24:01 +00:00
Vector < Line : : CompletionSuggestion > Shell : : complete_immediate_function_name ( StringView name , size_t offset )
2021-03-05 13:03:23 +00:00
{
Vector < Line : : CompletionSuggestion > suggestions ;
2022-02-28 13:58:47 +00:00
auto invariant_offset = offset ;
size_t static_offset = 0 ;
if ( m_editor )
m_editor - > transform_suggestion_offsets ( invariant_offset , static_offset ) ;
# define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(fn_name) \
if ( auto name_view = # fn_name # # sv ; name_view . starts_with ( name ) ) \
2022-07-11 17:32:29 +00:00
suggestions . append ( { name_view , " " sv } ) ;
2021-03-05 13:03:23 +00:00
ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS ( ) ;
# undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
2022-02-28 13:58:47 +00:00
for ( auto & entry : suggestions ) {
entry . input_offset = offset ;
entry . invariant_offset = invariant_offset ;
entry . static_offset = static_offset ;
}
2021-03-05 13:03:23 +00:00
return suggestions ;
}
2020-08-17 13:35:50 +00:00
void Shell : : bring_cursor_to_beginning_of_a_line ( ) const
{
struct winsize ws ;
2020-09-23 05:36:30 +00:00
if ( m_editor ) {
ws = m_editor - > terminal_size ( ) ;
2020-08-17 13:35:50 +00:00
} else {
if ( ioctl ( STDERR_FILENO , TIOCGWINSZ , & ws ) < 0 ) {
// Very annoying assumptions.
ws . ws_col = 80 ;
ws . ws_row = 25 ;
}
}
2020-08-17 21:17:57 +00:00
// Black with Cyan background.
constexpr auto default_mark = " \ e[30;46m% \ e[0m " ;
2022-12-04 18:02:33 +00:00
DeprecatedString eol_mark = getenv ( " PROMPT_EOL_MARK " ) ;
2020-08-17 21:17:57 +00:00
if ( eol_mark . is_null ( ) )
eol_mark = default_mark ;
2021-01-10 12:53:04 +00:00
size_t eol_mark_length = Line : : Editor : : actual_rendered_string_metrics ( eol_mark ) . line_metrics . last ( ) . total_length ( ) ;
2020-08-17 21:17:57 +00:00
if ( eol_mark_length > = ws . ws_col ) {
eol_mark = default_mark ;
eol_mark_length = 1 ;
}
fputs ( eol_mark . characters ( ) , stderr ) ;
2020-08-17 13:35:50 +00:00
2021-12-29 17:37:31 +00:00
// We write a line's worth of whitespace to the terminal. This way, we ensure that
// the prompt ends up on a new line even if there is dangling output on the current line.
size_t fill_count = ws . ws_col - eol_mark_length ;
2022-12-04 18:02:33 +00:00
auto fill_buffer = DeprecatedString : : repeated ( ' ' , fill_count ) ;
2021-12-29 17:37:31 +00:00
fwrite ( fill_buffer . characters ( ) , 1 , fill_count , stderr ) ;
2020-08-17 13:35:50 +00:00
putc ( ' \r ' , stderr ) ;
}
2021-01-11 09:34:59 +00:00
bool Shell : : has_history_event ( StringView source )
{
struct : public AST : : NodeVisitor {
2021-12-04 09:09:09 +00:00
virtual void visit ( const AST : : HistoryEvent * node ) override
2021-01-11 09:34:59 +00:00
{
has_history_event = true ;
AST : : NodeVisitor : : visit ( node ) ;
}
bool has_history_event { false } ;
} visitor ;
2021-03-08 07:29:32 +00:00
auto ast = Parser { source , true } . parse ( ) ;
if ( ! ast )
return false ;
ast - > visit ( visitor ) ;
2021-01-11 09:34:59 +00:00
return visitor . has_history_event ;
}
2023-06-11 12:54:54 +00:00
void Shell : : setup_keybinds ( )
{
m_editor - > register_key_input_callback ( ' \n ' , [ this ] ( Line : : Editor & editor ) {
auto ast = parse ( editor . line ( ) , false ) ;
if ( ast & & ast - > is_syntax_error ( ) & & ast - > syntax_error_node ( ) . is_continuable ( ) )
return true ;
return EDITOR_INTERNAL_FUNCTION ( finish ) ( editor ) ;
} ) ;
}
2023-08-31 10:04:21 +00:00
void Shell : : set_user_prompt ( )
{
if ( ! has_function ( " PROMPT " sv ) )
return ;
if ( ! m_prompt_command_node )
m_prompt_command_node = Parser { " shell_set_active_prompt -- ${join \" \\ n \" $(PROMPT)} " sv } . parse ( ) ;
( void ) m_prompt_command_node - > run ( this ) ;
}
2020-05-25 12:27:07 +00:00
bool Shell : : read_single_line ( )
2020-05-16 19:05:13 +00:00
{
2021-12-21 02:48:19 +00:00
while ( true ) {
2023-08-31 10:04:21 +00:00
set_user_prompt ( ) ;
2021-12-21 02:48:19 +00:00
restore_ios ( ) ;
bring_cursor_to_beginning_of_a_line ( ) ;
2023-06-11 12:54:54 +00:00
m_editor - > initialize ( ) ;
setup_keybinds ( ) ;
2021-12-21 02:48:19 +00:00
auto line_result = m_editor - > get_line ( prompt ( ) ) ;
if ( line_result . is_error ( ) ) {
auto is_eof = line_result . error ( ) = = Line : : Editor : : Error : : Eof ;
auto is_empty = line_result . error ( ) = = Line : : Editor : : Error : : Empty ;
if ( is_eof | | is_empty ) {
// Pretend the user tried to execute builtin_exit()
2022-07-11 17:32:29 +00:00
auto exit_code = run_command ( " exit " sv ) ;
2021-12-21 02:48:19 +00:00
if ( exit_code ! = 0 ) {
// If we didn't end up actually calling exit(), and the command didn't succeed, just pretend it's all okay
// unless we can't, then just quit anyway.
if ( ! is_empty )
continue ;
}
}
2020-05-30 18:05:40 +00:00
Core : : EventLoop : : current ( ) . quit ( 1 ) ;
return false ;
}
2020-05-25 12:27:07 +00:00
2021-12-21 02:48:19 +00:00
auto & line = line_result . value ( ) ;
2020-05-16 19:05:13 +00:00
2021-12-21 02:48:19 +00:00
if ( line . is_empty ( ) )
return true ;
2020-05-16 19:05:13 +00:00
2021-12-21 02:48:19 +00:00
run_command ( line ) ;
2020-05-16 19:05:13 +00:00
2021-12-21 02:48:19 +00:00
if ( ! has_history_event ( line ) )
m_editor - > add_to_history ( line ) ;
2021-01-11 09:34:59 +00:00
2021-12-21 02:48:19 +00:00
return true ;
}
2020-05-16 19:05:13 +00:00
}
void Shell : : custom_event ( Core : : CustomEvent & event )
{
if ( event . custom_type ( ) = = ReadLine ) {
2020-05-25 12:27:07 +00:00
if ( read_single_line ( ) )
Core : : EventLoop : : current ( ) . post_event ( * this , make < Core : : CustomEvent > ( ShellEventType : : ReadLine ) ) ;
2020-05-16 19:05:13 +00:00
return ;
}
}
2020-09-30 10:35:12 +00:00
void Shell : : notify_child_event ( )
{
Vector < u64 > disowned_jobs ;
// Workaround the fact that we can't receive *who* exactly changed state.
// The child might still be alive (and even running) when this signal is dispatched to us
// so just...repeat until we find a suitable child.
// This, of course, will mean that someone can send us a SIGCHILD and we'd be spinning here
2022-06-24 07:32:19 +00:00
// until the next child event we can actually handle, so stop after spending a total of 5110us (~5ms) on it.
2020-09-30 10:35:12 +00:00
bool found_child = false ;
2022-06-24 07:32:19 +00:00
size_t const max_tries = 10 ;
size_t valid_attempts = max_tries ;
useconds_t backoff_usec = 20 ;
int backoff_multiplier = 2 ;
for ( ; ; ) {
if ( found_child | | - - valid_attempts = = 0 )
break ;
2020-09-30 10:35:12 +00:00
// Ignore stray SIGCHLD when there are no jobs.
if ( jobs . is_empty ( ) )
return ;
2022-06-24 07:32:19 +00:00
if ( valid_attempts < max_tries - 1 ) {
usleep ( backoff_usec ) ;
backoff_usec * = backoff_multiplier ;
}
2020-09-30 10:35:12 +00:00
for ( auto & it : jobs ) {
auto job_id = it . key ;
auto & job = * it . value ;
2020-10-01 09:11:31 +00:00
2020-09-30 10:35:12 +00:00
int wstatus = 0 ;
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " waitpid({} = {}) = ... " , job . pid ( ) , job . cmd ( ) ) ;
2020-09-30 10:35:12 +00:00
auto child_pid = waitpid ( job . pid ( ) , & wstatus , WNOHANG | WUNTRACED ) ;
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " ... = {} - exited: {}, suspended: {} " , child_pid , WIFEXITED ( wstatus ) , WIFSTOPPED ( wstatus ) ) ;
2020-09-30 10:35:12 +00:00
if ( child_pid < 0 ) {
if ( errno = = ECHILD ) {
// The child process went away before we could process its death, just assume it exited all ok.
// FIXME: This should never happen, the child should stay around until we do the waitpid above.
child_pid = job . pid ( ) ;
} else {
2021-02-23 19:42:32 +00:00
VERIFY_NOT_REACHED ( ) ;
2020-09-30 10:35:12 +00:00
}
}
if ( child_pid = = 0 ) {
// If the child existed, but wasn't dead.
2021-07-16 18:40:03 +00:00
if ( job . is_suspended ( ) | | job . shell_did_continue ( ) ) {
// The job was suspended, and someone sent it a SIGCONT.
2021-03-29 21:49:02 +00:00
job . set_is_suspended ( false ) ;
2021-07-16 18:40:03 +00:00
if ( job . shell_did_continue ( ) )
job . set_shell_did_continue ( false ) ;
2021-03-29 21:49:02 +00:00
found_child = true ;
}
2020-09-30 10:35:12 +00:00
continue ;
}
if ( child_pid = = job . pid ( ) ) {
if ( WIFSIGNALED ( wstatus ) & & ! WIFSTOPPED ( wstatus ) ) {
2022-01-09 05:49:01 +00:00
auto signal = WTERMSIG ( wstatus ) ;
job . set_signalled ( signal ) ;
if ( signal = = SIGINT )
raise_error ( ShellError : : InternalControlFlowInterrupted , " Interrupted " sv , job . command ( ) . position ) ;
else if ( signal = = SIGKILL )
raise_error ( ShellError : : InternalControlFlowKilled , " Interrupted " sv , job . command ( ) . position ) ;
2020-09-30 10:35:12 +00:00
} else if ( WIFEXITED ( wstatus ) ) {
job . set_has_exit ( WEXITSTATUS ( wstatus ) ) ;
} else if ( WIFSTOPPED ( wstatus ) ) {
job . unblock ( ) ;
job . set_is_suspended ( true ) ;
}
found_child = true ;
}
if ( job . should_be_disowned ( ) )
disowned_jobs . append ( job_id ) ;
}
for ( auto job_id : disowned_jobs ) {
jobs . remove ( job_id ) ;
}
2022-06-24 07:32:19 +00:00
}
2020-09-30 10:35:12 +00:00
}
2020-10-01 14:43:01 +00:00
Shell : : Shell ( )
: m_default_constructed ( true )
{
2023-04-19 08:51:03 +00:00
push_frame ( " main " , LocalFrameKind : : FunctionOrGlobal ) . leak_frame ( ) ;
2020-10-01 14:43:01 +00:00
int rc = gethostname ( hostname , Shell : : HostNameSize ) ;
if ( rc < 0 )
perror ( " gethostname " ) ;
{
auto * pw = getpwuid ( getuid ( ) ) ;
if ( pw ) {
username = pw - > pw_name ;
home = pw - > pw_dir ;
setenv ( " HOME " , pw - > pw_dir , 1 ) ;
}
endpwent ( ) ;
}
// For simplicity, start at the user's home directory.
this - > cwd = home ;
setenv ( " PWD " , home . characters ( ) , 1 ) ;
// Add the default PATH vars.
{
StringBuilder path ;
2022-07-11 19:53:29 +00:00
auto const * path_env_ptr = getenv ( " PATH " ) ;
if ( path_env_ptr ! = NULL )
path . append ( { path_env_ptr , strlen ( path_env_ptr ) } ) ;
2020-10-01 14:43:01 +00:00
if ( path . length ( ) )
2022-07-11 17:32:29 +00:00
path . append ( " : " sv ) ;
2022-08-20 15:01:53 +00:00
path . append ( DEFAULT_PATH_SV ) ;
2022-12-06 01:12:49 +00:00
setenv ( " PATH " , path . to_deprecated_string ( ) . characters ( ) , true ) ;
2020-10-01 14:43:01 +00:00
}
cache_path ( ) ;
}
2023-08-11 10:11:15 +00:00
void Shell : : initialize ( bool attempt_interactive )
2020-05-16 19:05:13 +00:00
{
uid = getuid ( ) ;
tcsetpgrp ( 0 , getpgrp ( ) ) ;
2020-06-17 13:35:06 +00:00
m_pid = getpid ( ) ;
2020-05-16 19:05:13 +00:00
2023-04-19 08:51:03 +00:00
push_frame ( " main " , LocalFrameKind : : FunctionOrGlobal ) . leak_frame ( ) ;
2020-07-11 21:12:46 +00:00
2020-05-16 19:05:13 +00:00
int rc = gethostname ( hostname , Shell : : HostNameSize ) ;
if ( rc < 0 )
perror ( " gethostname " ) ;
2020-08-05 05:30:01 +00:00
auto istty = isatty ( STDIN_FILENO ) ;
2021-03-07 06:12:24 +00:00
m_is_interactive = attempt_interactive & & istty ;
2020-08-05 05:30:01 +00:00
if ( istty ) {
rc = ttyname_r ( 0 , ttyname , Shell : : TTYNameSize ) ;
if ( rc < 0 )
perror ( " ttyname_r " ) ;
} else {
ttyname [ 0 ] = 0 ;
}
2020-05-16 19:05:13 +00:00
{
auto * cwd = getcwd ( nullptr , 0 ) ;
this - > cwd = cwd ;
setenv ( " PWD " , cwd , 1 ) ;
free ( cwd ) ;
}
{
auto * pw = getpwuid ( getuid ( ) ) ;
if ( pw ) {
username = pw - > pw_name ;
home = pw - > pw_dir ;
setenv ( " HOME " , pw - > pw_dir , 1 ) ;
}
endpwent ( ) ;
}
directory_stack . append ( cwd ) ;
2021-03-07 06:12:24 +00:00
if ( m_is_interactive ) {
m_editor - > load_history ( get_history_path ( ) ) ;
cache_path ( ) ;
}
2023-08-11 10:11:15 +00:00
}
2020-12-01 09:25:14 +00:00
2023-08-11 10:11:15 +00:00
Shell : : Shell ( Line : : Editor & editor , bool attempt_interactive , bool posix_mode )
: m_in_posix_mode ( posix_mode )
, m_editor ( editor )
{
initialize ( attempt_interactive ) ;
2021-05-11 07:12:36 +00:00
start_timer ( 3000 ) ;
2020-05-16 19:05:13 +00:00
}
Shell : : ~ Shell ( )
2023-08-11 10:11:15 +00:00
{
destroy ( ) ;
}
void Shell : : destroy ( )
2020-05-16 19:05:13 +00:00
{
2020-10-01 14:43:01 +00:00
if ( m_default_constructed )
return ;
2020-05-24 16:55:01 +00:00
stop_all_jobs ( ) ;
2021-03-07 06:12:24 +00:00
if ( ! m_is_interactive )
return ;
2020-10-25 23:25:41 +00:00
m_editor - > save_history ( get_history_path ( ) ) ;
2020-05-16 19:05:13 +00:00
}
2020-05-24 16:55:01 +00:00
void Shell : : stop_all_jobs ( )
{
if ( ! jobs . is_empty ( ) ) {
2020-10-28 13:51:40 +00:00
if ( m_is_interactive & & ! m_is_subshell )
printf ( " Killing active jobs \n " ) ;
2020-05-24 16:55:01 +00:00
for ( auto & entry : jobs ) {
2020-10-28 13:51:40 +00:00
if ( entry . value - > is_suspended ( ) ) {
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " Job {} is suspended " , entry . value - > pid ( ) ) ;
2020-07-12 14:21:47 +00:00
kill_job ( entry . value , SIGCONT ) ;
2020-05-24 16:55:01 +00:00
}
2020-07-12 14:21:47 +00:00
kill_job ( entry . value , SIGHUP ) ;
2020-05-24 16:55:01 +00:00
}
usleep ( 10000 ) ; // Wait for a bit before killing the job
for ( auto & entry : jobs ) {
2021-03-29 21:47:20 +00:00
dbgln_if ( SH_DEBUG , " Actively killing {} ({}) " , entry . value - > pid ( ) , entry . value - > cmd ( ) ) ;
2020-10-28 13:51:40 +00:00
kill_job ( entry . value , SIGKILL ) ;
2020-05-24 16:55:01 +00:00
}
2020-10-27 13:46:20 +00:00
jobs . clear ( ) ;
2020-05-24 16:55:01 +00:00
}
}
2020-05-26 11:11:03 +00:00
2020-05-26 14:24:57 +00:00
u64 Shell : : find_last_job_id ( ) const
{
u64 job_id = 0 ;
for ( auto & entry : jobs ) {
if ( entry . value - > job_id ( ) > job_id )
job_id = entry . value - > job_id ( ) ;
}
return job_id ;
}
2023-02-20 17:26:54 +00:00
Job * Shell : : find_job ( u64 id , bool is_pid )
2020-06-17 13:35:06 +00:00
{
for ( auto & entry : jobs ) {
2021-04-23 13:56:27 +00:00
if ( is_pid ) {
if ( entry . value - > pid ( ) = = static_cast < int > ( id ) )
return entry . value ;
} else {
if ( entry . value - > job_id ( ) = = id )
return entry . value ;
}
2020-06-17 13:35:06 +00:00
}
return nullptr ;
}
2022-04-01 17:58:27 +00:00
void Shell : : kill_job ( Job const * job , int sig )
2020-07-12 14:21:47 +00:00
{
if ( ! job )
return ;
2020-10-28 13:51:40 +00:00
if ( killpg ( job - > pgid ( ) , sig ) < 0 ) {
if ( kill ( job - > pid ( ) , sig ) < 0 ) {
2021-01-18 22:22:04 +00:00
if ( errno ! = ESRCH )
perror ( " kill " ) ;
2020-10-28 13:51:40 +00:00
}
}
2020-07-12 14:21:47 +00:00
}
2020-12-10 14:55:13 +00:00
void Shell : : possibly_print_error ( ) const
{
switch ( m_error ) {
case ShellError : : EvaluatedSyntaxError :
warnln ( " Shell Syntax Error: {} " , m_error_description ) ;
2021-01-03 09:08:20 +00:00
break ;
2021-03-12 23:40:18 +00:00
case ShellError : : InvalidSliceContentsError :
2020-12-10 14:55:13 +00:00
case ShellError : : InvalidGlobError :
case ShellError : : NonExhaustiveMatchRules :
warnln ( " Shell: {} " , m_error_description ) ;
2021-01-03 09:08:20 +00:00
break ;
case ShellError : : OpenFailure :
warnln ( " Shell: Open failed for {} " , m_error_description ) ;
break ;
2021-09-05 22:59:52 +00:00
case ShellError : : OutOfMemory :
warnln ( " Shell: Hit an OOM situation " ) ;
break ;
2021-12-21 02:48:19 +00:00
case ShellError : : LaunchError :
warnln ( " Shell: {} " , m_error_description ) ;
break ;
2023-02-16 06:20:14 +00:00
case ShellError : : PipeFailure :
warnln ( " Shell: pipe() failed for {} " , m_error_description ) ;
break ;
case ShellError : : WriteFailure :
warnln ( " Shell: write() failed for {} " , m_error_description ) ;
break ;
2020-12-10 14:55:13 +00:00
case ShellError : : InternalControlFlowBreak :
case ShellError : : InternalControlFlowContinue :
2022-01-09 05:49:01 +00:00
case ShellError : : InternalControlFlowInterrupted :
case ShellError : : InternalControlFlowKilled :
2020-12-10 14:55:13 +00:00
return ;
case ShellError : : None :
return ;
}
2021-01-03 09:08:20 +00:00
if ( m_source_position . has_value ( ) & & m_source_position - > position . has_value ( ) ) {
auto & source_position = m_source_position . value ( ) ;
auto do_line = [ & ] ( auto line , auto & current_line ) {
auto is_in_range = line > = ( i64 ) source_position . position - > start_line . line_number & & line < = ( i64 ) source_position . position - > end_line . line_number ;
warnln ( " {:>3}| {} " , line , current_line ) ;
if ( is_in_range ) {
warn ( " \x1b [31m " ) ;
size_t length_written_so_far = 0 ;
if ( line = = ( i64 ) source_position . position - > start_line . line_number ) {
2021-06-01 18:35:27 +00:00
warn ( " {:~>{}} " , " " , 5 + source_position . position - > start_line . line_column ) ;
2021-01-03 09:08:20 +00:00
length_written_so_far + = source_position . position - > start_line . line_column ;
} else {
2021-06-01 18:35:27 +00:00
warn ( " {:~>{}} " , " " , 5 ) ;
2021-01-03 09:08:20 +00:00
}
if ( line = = ( i64 ) source_position . position - > end_line . line_number ) {
2021-06-01 18:35:27 +00:00
warn ( " {:^>{}} " , " " , source_position . position - > end_line . line_column - length_written_so_far ) ;
2021-01-03 09:08:20 +00:00
length_written_so_far + = source_position . position - > start_line . line_column ;
} else {
2021-06-01 18:35:27 +00:00
warn ( " {:^>{}} " , " " , current_line . length ( ) - length_written_so_far ) ;
2021-01-03 09:08:20 +00:00
}
warnln ( " \x1b [0m " ) ;
}
} ;
int line = - 1 ;
2023-05-27 11:12:13 +00:00
// FIXME: Support arbitrarily long lines?
auto line_buf_or_error = ByteBuffer : : create_uninitialized ( 4 * KiB ) ;
if ( line_buf_or_error . is_error ( ) ) {
warnln ( " Shell: Internal error while trying to display source information: {} (while allocating line buffer for {}) " , line_buf_or_error . error ( ) , source_position . source_file ) ;
return ;
}
auto line_buf = line_buf_or_error . release_value ( ) ;
StringView current_line ;
2021-01-03 09:08:20 +00:00
i64 line_to_skip_to = max ( source_position . position - > start_line . line_number , 2ul ) - 2 ;
if ( ! source_position . source_file . is_null ( ) ) {
2023-05-27 11:12:13 +00:00
auto file_or_error = Core : : File : : open ( source_position . source_file , Core : : File : : OpenMode : : Read ) ;
if ( file_or_error . is_error ( ) ) {
warnln ( " Shell: Internal error while trying to display source information: {} (while reading '{}') " , file_or_error . error ( ) , source_position . source_file ) ;
2021-01-03 09:08:20 +00:00
return ;
}
2023-05-27 11:12:13 +00:00
auto file = Core : : InputBufferedFile : : create ( file_or_error . release_value ( ) ) ;
2021-01-03 09:08:20 +00:00
while ( line < line_to_skip_to ) {
2023-05-27 11:12:13 +00:00
if ( file . value ( ) - > is_eof ( ) )
2021-01-03 09:08:20 +00:00
return ;
2023-05-27 11:12:13 +00:00
auto current_line_or_error = file . value ( ) - > read_line ( line_buf ) ;
if ( current_line_or_error . is_error ( ) ) {
warnln ( " Shell: Internal error while trying to display source information: {} (while reading line {} of '{}') " , current_line_or_error . error ( ) , line , source_position . source_file ) ;
return ;
}
current_line = current_line_or_error . release_value ( ) ;
2021-01-03 09:08:20 +00:00
+ + line ;
}
for ( ; line < ( i64 ) source_position . position - > end_line . line_number + 2 ; + + line ) {
do_line ( line , current_line ) ;
2023-05-27 11:12:13 +00:00
if ( file . value ( ) - > is_eof ( ) ) {
current_line = " " sv ;
} else {
auto current_line_or_error = file . value ( ) - > read_line ( line_buf ) ;
if ( current_line_or_error . is_error ( ) ) {
warnln ( " Shell: Internal error while trying to display source information: {} (while reading line {} of '{}') " , current_line_or_error . error ( ) , line , source_position . source_file ) ;
return ;
}
current_line = current_line_or_error . release_value ( ) ;
}
2021-01-03 09:08:20 +00:00
}
} else if ( ! source_position . literal_source_text . is_empty ( ) ) {
GenericLexer lexer { source_position . literal_source_text } ;
while ( line < line_to_skip_to ) {
if ( lexer . is_eof ( ) )
return ;
current_line = lexer . consume_line ( ) ;
+ + line ;
}
for ( ; line < ( i64 ) source_position . position - > end_line . line_number + 2 ; + + line ) {
do_line ( line , current_line ) ;
if ( lexer . is_eof ( ) )
2023-05-27 11:12:13 +00:00
current_line = " " sv ;
2021-01-03 09:08:20 +00:00
else
current_line = lexer . consume_line ( ) ;
}
}
}
warnln ( ) ;
2020-12-10 14:55:13 +00:00
}
2022-01-29 15:24:01 +00:00
Optional < int > Shell : : resolve_job_spec ( StringView str )
2021-04-23 13:56:27 +00:00
{
if ( ! str . starts_with ( ' % ' ) )
return { } ;
// %number -> job id <number>
if ( auto number = str . substring_view ( 1 ) . to_uint ( ) ; number . has_value ( ) )
return number . value ( ) ;
// '%?str' -> iterate jobs and pick one with `str' in its command
// Note: must be quoted, since '?' will turn it into a glob - pretty ugly...
GenericLexer lexer { str . substring_view ( 1 ) } ;
if ( ! lexer . consume_specific ( ' ? ' ) )
return { } ;
auto search_term = lexer . remaining ( ) ;
for ( auto & it : jobs ) {
if ( it . value - > cmd ( ) . contains ( search_term ) )
return it . key ;
}
return { } ;
}
2021-05-11 07:12:36 +00:00
void Shell : : timer_event ( Core : : TimerEvent & event )
{
event . accept ( ) ;
if ( m_is_subshell )
return ;
2022-07-11 19:53:29 +00:00
auto const * autosave_env_ptr = getenv ( " HISTORY_AUTOSAVE_TIME_MS " ) ;
auto option = autosave_env_ptr ! = NULL ? StringView { autosave_env_ptr , strlen ( autosave_env_ptr ) } : StringView { } ;
2021-05-11 07:12:36 +00:00
auto time = option . to_uint ( ) ;
if ( ! time . has_value ( ) | | time . value ( ) = = 0 ) {
m_history_autosave_time . clear ( ) ;
stop_timer ( ) ;
start_timer ( 3000 ) ;
return ;
}
if ( m_history_autosave_time ! = time ) {
m_history_autosave_time = time . value ( ) ;
stop_timer ( ) ;
start_timer ( m_history_autosave_time . value ( ) ) ;
}
if ( ! m_history_autosave_time . has_value ( ) )
return ;
2021-05-11 15:06:03 +00:00
if ( m_editor & & m_editor - > is_history_dirty ( ) )
2021-05-11 07:12:36 +00:00
m_editor - > save_history ( get_history_path ( ) ) ;
}
2023-02-11 14:29:15 +00:00
RefPtr < AST : : Node > Shell : : parse ( StringView input , bool interactive , bool as_command ) const
{
if ( m_in_posix_mode ) {
Posix : : Parser parser ( input ) ;
if ( as_command ) {
auto node = parser . parse ( ) ;
if constexpr ( SHELL_POSIX_PARSER_DEBUG ) {
dbgln ( " Parsed with the POSIX Parser: " ) ;
2023-02-18 07:26:59 +00:00
( void ) node - > dump ( 0 ) ;
2023-02-11 14:29:15 +00:00
}
return node ;
}
return parser . parse_word_list ( ) ;
}
Parser parser { input , interactive } ;
if ( as_command )
return parser . parse ( ) ;
auto nodes = parser . parse_as_multiple_expressions ( ) ;
return make_ref_counted < AST : : ListConcatenate > (
2023-03-06 13:17:01 +00:00
nodes . is_empty ( ) ? AST : : Position { 0 , 0 , { 0 , 0 } , { 0 , 0 } } : nodes . first ( ) - > position ( ) ,
2023-02-11 14:29:15 +00:00
move ( nodes ) ) ;
}
2020-09-23 05:36:30 +00:00
void FileDescriptionCollector : : collect ( )
{
for ( auto fd : m_fds )
close ( fd ) ;
m_fds . clear ( ) ;
}
FileDescriptionCollector : : ~ FileDescriptionCollector ( )
{
collect ( ) ;
}
void FileDescriptionCollector : : add ( int fd )
{
m_fds . append ( fd ) ;
}
2023-03-06 13:17:01 +00:00
SavedFileDescriptors : : SavedFileDescriptors ( Vector < NonnullRefPtr < AST : : Rewiring > > const & intended_rewirings )
2020-09-23 05:36:30 +00:00
{
for ( auto & rewiring : intended_rewirings ) {
2023-03-06 13:17:01 +00:00
int new_fd = dup ( rewiring - > new_fd ) ;
2020-09-23 05:36:30 +00:00
if ( new_fd < 0 ) {
if ( errno ! = EBADF )
perror ( " dup " ) ;
// The fd that will be overwritten isn't open right now,
// it will be cleaned up by the exec()-side collector
// and we have nothing to do here, so just ignore this error.
continue ;
}
2021-05-13 08:01:47 +00:00
auto flags = fcntl ( new_fd , F_GETFD ) ;
auto rc = fcntl ( new_fd , F_SETFD , flags | FD_CLOEXEC ) ;
2021-02-23 19:42:32 +00:00
VERIFY ( rc = = 0 ) ;
2020-09-23 05:36:30 +00:00
2023-03-06 13:17:01 +00:00
m_saves . append ( { rewiring - > new_fd , new_fd } ) ;
2020-09-23 05:36:30 +00:00
m_collector . add ( new_fd ) ;
}
}
SavedFileDescriptors : : ~ SavedFileDescriptors ( )
{
for ( auto & save : m_saves ) {
if ( dup2 ( save . saved , save . original ) < 0 ) {
perror ( " dup2(~SavedFileDescriptors) " ) ;
continue ;
}
}
}
2020-10-01 14:43:01 +00:00
}