TerminalWrapper.cpp 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. /*
  2. * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
  3. * Copyright (c) 2022, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "TerminalWrapper.h"
  8. #include <AK/ByteString.h>
  9. #include <LibCore/System.h>
  10. #include <LibGUI/Application.h>
  11. #include <LibGUI/BoxLayout.h>
  12. #include <LibGUI/MessageBox.h>
  13. #include <LibVT/TerminalWidget.h>
  14. #include <fcntl.h>
  15. #include <signal.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <sys/ioctl.h>
  20. #include <sys/types.h>
  21. #include <sys/wait.h>
  22. #include <unistd.h>
  23. namespace HackStudio {
  24. ErrorOr<void> TerminalWrapper::run_command(ByteString const& command, Optional<ByteString> working_directory, WaitForExit wait_for_exit, Optional<StringView> failure_message)
  25. {
  26. if (m_pid != -1) {
  27. GUI::MessageBox::show(window(),
  28. "A command is already running in this TerminalWrapper"sv,
  29. "Can't run command"sv,
  30. GUI::MessageBox::Type::Error);
  31. return {};
  32. }
  33. auto ptm_fd = TRY(setup_master_pseudoterminal());
  34. m_child_exited = false;
  35. m_child_exit_status.clear();
  36. m_pid = TRY(Core::System::fork());
  37. if (m_pid > 0) {
  38. m_terminal_widget->set_startup_process_id(m_pid);
  39. if (wait_for_exit == WaitForExit::Yes) {
  40. GUI::Application::the()->event_loop().spin_until([this]() {
  41. return m_child_exited;
  42. });
  43. VERIFY(m_child_exit_status.has_value());
  44. if (m_child_exit_status.value() != 0)
  45. return Error::from_string_view(failure_message.value_or("Command execution failed"sv));
  46. }
  47. return {};
  48. }
  49. if (working_directory.has_value())
  50. TRY(Core::System::chdir(working_directory->view()));
  51. TRY(setup_slave_pseudoterminal(ptm_fd));
  52. auto args = command.split_view(' ');
  53. VERIFY(!args.is_empty());
  54. TRY(Core::System::exec(args[0], args, Core::System::SearchInPath::Yes));
  55. VERIFY_NOT_REACHED();
  56. }
  57. ErrorOr<int> TerminalWrapper::setup_master_pseudoterminal(WaitForChildOnExit wait_for_child)
  58. {
  59. int ptm_fd = TRY(Core::System::posix_openpt(O_RDWR | O_CLOEXEC));
  60. bool error_happened = true;
  61. ScopeGuard close_ptm { [&]() {
  62. if (error_happened) {
  63. if (auto result = Core::System::close(ptm_fd); result.is_error())
  64. warnln("{}", result.release_error());
  65. }
  66. } };
  67. TRY(Core::System::grantpt(ptm_fd));
  68. TRY(Core::System::unlockpt(ptm_fd));
  69. m_terminal_widget->set_pty_master_fd(ptm_fd);
  70. m_terminal_widget->on_command_exit = [this, wait_for_child] {
  71. if (wait_for_child == WaitForChildOnExit::Yes) {
  72. auto result = Core::System::waitpid(m_pid, 0);
  73. if (result.is_error()) {
  74. warnln("{}", result.error());
  75. VERIFY_NOT_REACHED();
  76. }
  77. int wstatus = result.release_value().status;
  78. if (WIFEXITED(wstatus)) {
  79. m_terminal_widget->inject_string(ByteString::formatted("\033[{};1m(Command exited with code {})\033[0m\r\n", wstatus == 0 ? 32 : 31, WEXITSTATUS(wstatus)));
  80. } else if (WIFSTOPPED(wstatus)) {
  81. m_terminal_widget->inject_string("\033[34;1m(Command stopped!)\033[0m\r\n"sv);
  82. } else if (WIFSIGNALED(wstatus)) {
  83. m_terminal_widget->inject_string(ByteString::formatted("\033[34;1m(Command signaled with {}!)\033[0m\r\n", strsignal(WTERMSIG(wstatus))));
  84. }
  85. m_child_exit_status = WEXITSTATUS(wstatus);
  86. m_child_exited = true;
  87. }
  88. m_pid = -1;
  89. if (on_command_exit)
  90. on_command_exit();
  91. };
  92. terminal().scroll_to_bottom();
  93. error_happened = false;
  94. return ptm_fd;
  95. }
  96. ErrorOr<void> TerminalWrapper::setup_slave_pseudoterminal(int master_fd)
  97. {
  98. setsid();
  99. auto tty_name = TRY(Core::System::ptsname(master_fd));
  100. close(master_fd);
  101. int pts_fd = TRY(Core::System::open(tty_name, O_RDWR));
  102. tcsetpgrp(pts_fd, getpid());
  103. // NOTE: It's okay if this fails.
  104. ioctl(0, TIOCNOTTY);
  105. close(0);
  106. close(1);
  107. close(2);
  108. TRY(Core::System::dup2(pts_fd, 0));
  109. TRY(Core::System::dup2(pts_fd, 1));
  110. TRY(Core::System::dup2(pts_fd, 2));
  111. TRY(Core::System::close(pts_fd));
  112. TRY(Core::System::ioctl(0, TIOCSCTTY));
  113. setenv("TERM", "xterm", true);
  114. return {};
  115. }
  116. ErrorOr<void> TerminalWrapper::kill_running_command()
  117. {
  118. VERIFY(m_pid != -1);
  119. // Kill our child process and its whole process group.
  120. TRY(Core::System::killpg(m_pid, SIGTERM));
  121. return {};
  122. }
  123. void TerminalWrapper::clear_including_history()
  124. {
  125. m_terminal_widget->clear_including_history();
  126. }
  127. TerminalWrapper::TerminalWrapper(bool user_spawned)
  128. : m_user_spawned(user_spawned)
  129. {
  130. set_layout<GUI::VerticalBoxLayout>();
  131. m_terminal_widget = add<VT::TerminalWidget>(-1, false);
  132. if (user_spawned) {
  133. auto maybe_error = run_command("Shell");
  134. if (maybe_error.is_error())
  135. warnln("{}", maybe_error.release_error());
  136. }
  137. }
  138. int TerminalWrapper::child_exit_status() const
  139. {
  140. VERIFY(m_child_exit_status.has_value());
  141. return m_child_exit_status.value();
  142. }
  143. }