TerminalWrapper.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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/String.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(String const& command, Optional<String> 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",
  29. "Can't run command",
  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. if (wait_for_exit == WaitForExit::Yes) {
  39. GUI::Application::the()->event_loop().spin_until([this]() {
  40. return m_child_exited;
  41. });
  42. VERIFY(m_child_exit_status.has_value());
  43. if (m_child_exit_status.value() != 0)
  44. return Error::from_string_literal(failure_message.value_or("Command execution failed"sv));
  45. }
  46. return {};
  47. }
  48. if (working_directory.has_value())
  49. TRY(Core::System::chdir(working_directory->view()));
  50. TRY(setup_slave_pseudoterminal(ptm_fd));
  51. auto args = command.split_view(' ');
  52. VERIFY(!args.is_empty());
  53. TRY(Core::System::exec(args[0], args, Core::System::SearchInPath::Yes));
  54. VERIFY_NOT_REACHED();
  55. }
  56. ErrorOr<int> TerminalWrapper::setup_master_pseudoterminal(WaitForChildOnExit wait_for_child)
  57. {
  58. int ptm_fd = TRY(Core::System::posix_openpt(O_RDWR | O_CLOEXEC));
  59. bool error_happened = true;
  60. ScopeGuard close_ptm { [&]() {
  61. if (error_happened) {
  62. if (auto result = Core::System::close(ptm_fd); result.is_error())
  63. warnln("{}", result.release_error());
  64. }
  65. } };
  66. TRY(Core::System::grantpt(ptm_fd));
  67. TRY(Core::System::unlockpt(ptm_fd));
  68. m_terminal_widget->set_pty_master_fd(ptm_fd);
  69. m_terminal_widget->on_command_exit = [this, wait_for_child] {
  70. if (wait_for_child == WaitForChildOnExit::Yes) {
  71. auto result = Core::System::waitpid(m_pid, 0);
  72. if (result.is_error()) {
  73. warnln("{}", result.error());
  74. VERIFY_NOT_REACHED();
  75. }
  76. int wstatus = result.release_value().status;
  77. if (WIFEXITED(wstatus)) {
  78. m_terminal_widget->inject_string(String::formatted("\033[{};1m(Command exited with code {})\033[0m\r\n", wstatus == 0 ? 32 : 31, WEXITSTATUS(wstatus)));
  79. } else if (WIFSTOPPED(wstatus)) {
  80. m_terminal_widget->inject_string("\033[34;1m(Command stopped!)\033[0m\r\n");
  81. } else if (WIFSIGNALED(wstatus)) {
  82. m_terminal_widget->inject_string(String::formatted("\033[34;1m(Command signaled with {}!)\033[0m\r\n", strsignal(WTERMSIG(wstatus))));
  83. }
  84. m_child_exit_status = WEXITSTATUS(wstatus);
  85. m_child_exited = true;
  86. }
  87. m_pid = -1;
  88. if (on_command_exit)
  89. on_command_exit();
  90. };
  91. terminal().scroll_to_bottom();
  92. error_happened = false;
  93. return ptm_fd;
  94. }
  95. ErrorOr<void> TerminalWrapper::setup_slave_pseudoterminal(int master_fd)
  96. {
  97. setsid();
  98. auto tty_name = TRY(Core::System::ptsname(master_fd));
  99. close(master_fd);
  100. int pts_fd = TRY(Core::System::open(tty_name, O_RDWR));
  101. tcsetpgrp(pts_fd, getpid());
  102. // NOTE: It's okay if this fails.
  103. ioctl(0, TIOCNOTTY);
  104. close(0);
  105. close(1);
  106. close(2);
  107. TRY(Core::System::dup2(pts_fd, 0));
  108. TRY(Core::System::dup2(pts_fd, 1));
  109. TRY(Core::System::dup2(pts_fd, 2));
  110. TRY(Core::System::close(pts_fd));
  111. TRY(Core::System::ioctl(0, TIOCSCTTY));
  112. setenv("TERM", "xterm", true);
  113. return {};
  114. }
  115. ErrorOr<void> TerminalWrapper::kill_running_command()
  116. {
  117. VERIFY(m_pid != -1);
  118. // Kill our child process and its whole process group.
  119. TRY(Core::System::killpg(m_pid, SIGTERM));
  120. return {};
  121. }
  122. void TerminalWrapper::clear_including_history()
  123. {
  124. m_terminal_widget->clear_including_history();
  125. }
  126. TerminalWrapper::TerminalWrapper(bool user_spawned)
  127. : m_user_spawned(user_spawned)
  128. {
  129. set_layout<GUI::VerticalBoxLayout>();
  130. m_terminal_widget = add<VT::TerminalWidget>(-1, false);
  131. if (user_spawned) {
  132. auto maybe_error = run_command("Shell");
  133. if (maybe_error.is_error())
  134. warnln("{}", maybe_error.release_error());
  135. }
  136. }
  137. int TerminalWrapper::child_exit_status() const
  138. {
  139. VERIFY(m_child_exit_status.has_value());
  140. return m_child_exit_status.value();
  141. }
  142. }