2020-01-18 05:38:21 -03:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2022-02-15 17:28:01 -03:00
* Copyright ( c ) 2022 , the SerenityOS developers .
2020-01-18 05:38:21 -03:00
*
2021-04-22 05:24:48 -03:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 05:38:21 -03:00
*/
2019-10-21 15:17:32 -03:00
# include "TerminalWrapper.h"
2022-12-04 15:02:33 -03:00
# include <AK/DeprecatedString.h>
2022-03-02 11:16:53 -03:00
# include <LibCore/System.h>
2022-01-07 11:28:56 -03:00
# include <LibGUI/Application.h>
2020-02-06 16:33:02 -03:00
# include <LibGUI/BoxLayout.h>
# include <LibGUI/MessageBox.h>
2019-10-21 15:17:32 -03:00
# include <LibVT/TerminalWidget.h>
# include <fcntl.h>
2019-11-14 13:21:13 -03:00
# include <signal.h>
2019-10-21 15:17:32 -03:00
# include <stdio.h>
# include <stdlib.h>
2020-03-08 08:05:14 -03:00
# include <string.h>
2019-10-21 15:17:32 -03:00
# include <sys/ioctl.h>
2019-11-14 13:21:13 -03:00
# include <sys/types.h>
2019-10-22 17:15:43 -03:00
# include <sys/wait.h>
2019-10-21 15:17:32 -03:00
# include <unistd.h>
2020-08-17 10:22:30 -03:00
namespace HackStudio {
2022-12-04 15:02:33 -03:00
ErrorOr < void > TerminalWrapper : : run_command ( DeprecatedString const & command , Optional < DeprecatedString > working_directory , WaitForExit wait_for_exit , Optional < StringView > failure_message )
2019-10-21 15:17:32 -03:00
{
2019-10-22 17:15:43 -03:00
if ( m_pid ! = - 1 ) {
2020-07-15 23:45:11 -03:00
GUI : : MessageBox : : show ( window ( ) ,
2022-07-11 14:32:29 -03:00
" A command is already running in this TerminalWrapper " sv ,
" Can't run command " sv ,
2020-07-15 23:45:11 -03:00
GUI : : MessageBox : : Type : : Error ) ;
2022-03-02 11:16:53 -03:00
return { } ;
2019-10-22 17:15:43 -03:00
}
2022-03-02 11:16:53 -03:00
auto ptm_fd = TRY ( setup_master_pseudoterminal ( ) ) ;
2021-12-20 17:32:21 -03:00
2022-01-07 11:28:56 -03:00
m_child_exited = false ;
m_child_exit_status . clear ( ) ;
2021-12-20 17:32:21 -03:00
2022-03-02 11:16:53 -03:00
m_pid = TRY ( Core : : System : : fork ( ) ) ;
2021-12-20 17:32:21 -03:00
2022-01-07 11:28:56 -03:00
if ( m_pid > 0 ) {
if ( wait_for_exit = = WaitForExit : : Yes ) {
GUI : : Application : : the ( ) - > event_loop ( ) . spin_until ( [ this ] ( ) {
return m_child_exited ;
} ) ;
2022-03-02 11:16:53 -03:00
VERIFY ( m_child_exit_status . has_value ( ) ) ;
if ( m_child_exit_status . value ( ) ! = 0 )
2022-07-11 14:57:32 -03:00
return Error : : from_string_view ( failure_message . value_or ( " Command execution failed " sv ) ) ;
2022-01-07 11:28:56 -03:00
}
2021-12-20 17:32:21 -03:00
2022-03-02 11:16:53 -03:00
return { } ;
2021-12-20 17:32:21 -03:00
}
2022-03-02 11:16:53 -03:00
if ( working_directory . has_value ( ) )
TRY ( Core : : System : : chdir ( working_directory - > view ( ) ) ) ;
TRY ( setup_slave_pseudoterminal ( ptm_fd ) ) ;
2022-04-02 09:49:58 -03:00
auto args = command . split_view ( ' ' ) ;
VERIFY ( ! args . is_empty ( ) ) ;
TRY ( Core : : System : : exec ( args [ 0 ] , args , Core : : System : : SearchInPath : : Yes ) ) ;
2021-12-20 17:32:21 -03:00
VERIFY_NOT_REACHED ( ) ;
}
ErrorOr < int > TerminalWrapper : : setup_master_pseudoterminal ( WaitForChildOnExit wait_for_child )
{
2022-03-02 11:16:53 -03:00
int ptm_fd = TRY ( Core : : System : : posix_openpt ( O_RDWR | O_CLOEXEC ) ) ;
bool error_happened = true ;
ScopeGuard close_ptm { [ & ] ( ) {
if ( error_happened ) {
if ( auto result = Core : : System : : close ( ptm_fd ) ; result . is_error ( ) )
warnln ( " {} " , result . release_error ( ) ) ;
}
} } ;
TRY ( Core : : System : : grantpt ( ptm_fd ) ) ;
TRY ( Core : : System : : unlockpt ( ptm_fd ) ) ;
2019-10-22 17:15:43 -03:00
m_terminal_widget - > set_pty_master_fd ( ptm_fd ) ;
2021-12-20 17:32:21 -03:00
m_terminal_widget - > on_command_exit = [ this , wait_for_child ] {
if ( wait_for_child = = WaitForChildOnExit : : Yes ) {
2022-03-02 11:16:53 -03:00
auto result = Core : : System : : waitpid ( m_pid , 0 ) ;
if ( result . is_error ( ) ) {
warnln ( " {} " , result . error ( ) ) ;
2021-12-20 17:32:21 -03:00
VERIFY_NOT_REACHED ( ) ;
}
2022-03-02 11:16:53 -03:00
int wstatus = result . release_value ( ) . status ;
2022-01-07 11:28:56 -03:00
2021-12-20 17:32:21 -03:00
if ( WIFEXITED ( wstatus ) ) {
2022-12-04 15:02:33 -03:00
m_terminal_widget - > inject_string ( DeprecatedString : : formatted ( " \033 [{};1m(Command exited with code {}) \033 [0m \r \n " , wstatus = = 0 ? 32 : 31 , WEXITSTATUS ( wstatus ) ) ) ;
2021-12-20 17:32:21 -03:00
} else if ( WIFSTOPPED ( wstatus ) ) {
2022-07-11 14:32:29 -03:00
m_terminal_widget - > inject_string ( " \033 [34;1m(Command stopped!) \033 [0m \r \n " sv ) ;
2021-12-20 17:32:21 -03:00
} else if ( WIFSIGNALED ( wstatus ) ) {
2022-12-04 15:02:33 -03:00
m_terminal_widget - > inject_string ( DeprecatedString : : formatted ( " \033 [34;1m(Command signaled with {}!) \033 [0m \r \n " , strsignal ( WTERMSIG ( wstatus ) ) ) ) ;
2021-12-20 17:32:21 -03:00
}
2022-01-07 11:28:56 -03:00
m_child_exit_status = WEXITSTATUS ( wstatus ) ;
m_child_exited = true ;
2019-10-22 17:15:43 -03:00
}
m_pid = - 1 ;
2019-11-14 13:19:57 -03:00
if ( on_command_exit )
on_command_exit ( ) ;
2019-10-22 17:15:43 -03:00
} ;
2021-12-20 17:32:21 -03:00
terminal ( ) . scroll_to_bottom ( ) ;
2021-01-23 04:39:59 -03:00
2022-03-02 11:16:53 -03:00
error_happened = false ;
2021-12-20 17:32:21 -03:00
return ptm_fd ;
}
2019-11-14 13:21:13 -03:00
2021-12-20 17:32:21 -03:00
ErrorOr < void > TerminalWrapper : : setup_slave_pseudoterminal ( int master_fd )
{
setsid ( ) ;
2019-10-21 15:17:32 -03:00
2022-03-02 11:16:53 -03:00
auto tty_name = TRY ( Core : : System : : ptsname ( master_fd ) ) ;
2020-08-09 06:17:09 -03:00
2021-12-20 17:32:21 -03:00
close ( master_fd ) ;
2022-03-02 11:16:53 -03:00
int pts_fd = TRY ( Core : : System : : open ( tty_name , O_RDWR ) ) ;
2019-10-21 15:17:32 -03:00
2021-12-20 17:32:21 -03:00
tcsetpgrp ( pts_fd , getpid ( ) ) ;
2019-10-21 15:17:32 -03:00
2021-12-20 17:32:21 -03:00
// NOTE: It's okay if this fails.
2022-03-02 11:16:53 -03:00
ioctl ( 0 , TIOCNOTTY ) ;
2019-11-14 13:22:09 -03:00
2021-12-20 17:32:21 -03:00
close ( 0 ) ;
close ( 1 ) ;
close ( 2 ) ;
2019-11-14 13:22:09 -03:00
2022-03-02 11:16:53 -03:00
TRY ( Core : : System : : dup2 ( pts_fd , 0 ) ) ;
TRY ( Core : : System : : dup2 ( pts_fd , 1 ) ) ;
TRY ( Core : : System : : dup2 ( pts_fd , 2 ) ) ;
2020-11-10 07:47:51 -03:00
2022-03-02 11:16:53 -03:00
TRY ( Core : : System : : close ( pts_fd ) ) ;
2021-12-20 17:32:21 -03:00
2022-03-02 11:16:53 -03:00
TRY ( Core : : System : : ioctl ( 0 , TIOCSCTTY ) ) ;
2021-12-20 17:32:21 -03:00
setenv ( " TERM " , " xterm " , true ) ;
return { } ;
2019-10-21 15:17:32 -03:00
}
2022-03-02 11:16:53 -03:00
ErrorOr < void > TerminalWrapper : : kill_running_command ( )
2019-11-14 13:21:13 -03:00
{
2021-02-23 16:42:32 -03:00
VERIFY ( m_pid ! = - 1 ) ;
2019-11-14 13:21:13 -03:00
// Kill our child process and its whole process group.
2022-03-02 11:16:53 -03:00
TRY ( Core : : System : : killpg ( m_pid , SIGTERM ) ) ;
return { } ;
2019-11-14 13:21:13 -03:00
}
2021-08-01 17:40:05 -03:00
void TerminalWrapper : : clear_including_history ( )
{
m_terminal_widget - > clear_including_history ( ) ;
}
2020-04-05 19:03:15 -03:00
TerminalWrapper : : TerminalWrapper ( bool user_spawned )
: m_user_spawned ( user_spawned )
2019-10-21 15:17:32 -03:00
{
2020-03-04 05:43:54 -03:00
set_layout < GUI : : VerticalBoxLayout > ( ) ;
2019-10-21 15:17:32 -03:00
2021-08-25 14:39:57 -03:00
m_terminal_widget = add < VT : : TerminalWidget > ( - 1 , false ) ;
2022-03-02 11:16:53 -03:00
if ( user_spawned ) {
auto maybe_error = run_command ( " Shell " ) ;
if ( maybe_error . is_error ( ) )
warnln ( " {} " , maybe_error . release_error ( ) ) ;
}
2019-10-21 15:17:32 -03:00
}
2022-01-07 11:28:56 -03:00
int TerminalWrapper : : child_exit_status ( ) const
{
VERIFY ( m_child_exit_status . has_value ( ) ) ;
return m_child_exit_status . value ( ) ;
}
2020-08-17 10:22:30 -03:00
}