Serenity Operating System
at portability 180 lines 5.6 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "TerminalWrapper.h" 28#include "ProcessStateWidget.h" 29#include <AK/String.h> 30#include <LibCore/ConfigFile.h> 31#include <LibGUI/BoxLayout.h> 32#include <LibGUI/MessageBox.h> 33#include <LibVT/TerminalWidget.h> 34#include <fcntl.h> 35#include <signal.h> 36#include <stdio.h> 37#include <stdlib.h> 38#include <sys/ioctl.h> 39#include <sys/types.h> 40#include <sys/wait.h> 41#include <unistd.h> 42 43void TerminalWrapper::run_command(const String& command) 44{ 45 if (m_pid != -1) { 46 GUI::MessageBox::show( 47 "A command is already running in this TerminalWrapper", 48 "Can't run command", 49 GUI::MessageBox::Type::Error, 50 GUI::MessageBox::InputType::OK, 51 window()); 52 return; 53 } 54 55 int ptm_fd = posix_openpt(O_RDWR | O_CLOEXEC); 56 if (ptm_fd < 0) { 57 perror("posix_openpt"); 58 ASSERT_NOT_REACHED(); 59 } 60 if (grantpt(ptm_fd) < 0) { 61 perror("grantpt"); 62 ASSERT_NOT_REACHED(); 63 } 64 if (unlockpt(ptm_fd) < 0) { 65 perror("unlockpt"); 66 ASSERT_NOT_REACHED(); 67 } 68 69 m_terminal_widget->set_pty_master_fd(ptm_fd); 70 m_terminal_widget->on_command_exit = [this] { 71 int wstatus; 72 int rc = waitpid(m_pid, &wstatus, 0); 73 if (rc < 0) { 74 perror("waitpid"); 75 ASSERT_NOT_REACHED(); 76 } 77 if (WIFEXITED(wstatus)) { 78 m_terminal_widget->inject_string(String::format("\033[%d;1m(Command exited with code %d)\033[0m\n", wstatus == 0 ? 32 : 31, WEXITSTATUS(wstatus))); 79 } else if (WIFSTOPPED(wstatus)) { 80 m_terminal_widget->inject_string(String::format("\033[34;1m(Command stopped!)\033[0m\n")); 81 } else if (WIFSIGNALED(wstatus)) { 82 m_terminal_widget->inject_string(String::format("\033[34;1m(Command signaled with %s!)\033[0m\n", strsignal(WTERMSIG(wstatus)))); 83 } 84 m_process_state_widget->set_tty_fd(-1); 85 m_pid = -1; 86 87 if (on_command_exit) 88 on_command_exit(); 89 }; 90 91 m_pid = fork(); 92 if (m_pid == 0) { 93 // Create a new process group. 94 setsid(); 95 96 const char* tty_name = ptsname(ptm_fd); 97 if (!tty_name) { 98 perror("ptsname"); 99 exit(1); 100 } 101 close(ptm_fd); 102 int pts_fd = open(tty_name, O_RDWR); 103 if (pts_fd < 0) { 104 perror("open"); 105 exit(1); 106 } 107 108 // NOTE: It's okay if this fails. 109 (void)ioctl(0, TIOCNOTTY); 110 111 close(0); 112 close(1); 113 close(2); 114 115 int rc = dup2(pts_fd, 0); 116 if (rc < 0) { 117 perror("dup2"); 118 exit(1); 119 } 120 rc = dup2(pts_fd, 1); 121 if (rc < 0) { 122 perror("dup2"); 123 exit(1); 124 } 125 rc = dup2(pts_fd, 2); 126 if (rc < 0) { 127 perror("dup2"); 128 exit(1); 129 } 130 rc = close(pts_fd); 131 if (rc < 0) { 132 perror("close"); 133 exit(1); 134 } 135 rc = ioctl(0, TIOCSCTTY); 136 if (rc < 0) { 137 perror("ioctl(TIOCSCTTY)"); 138 exit(1); 139 } 140 141 setenv("TERM", "xterm", true); 142 143 auto parts = command.split(' '); 144 ASSERT(!parts.is_empty()); 145 const char** args = (const char**) calloc(parts.size() + 1, sizeof(const char*)); 146 for (size_t i = 0; i < parts.size(); i++) { 147 args[i] = parts[i].characters(); 148 } 149 rc = execvp(args[0], const_cast<char**>(args)); 150 if (rc < 0) { 151 perror("execve"); 152 exit(1); 153 } 154 ASSERT_NOT_REACHED(); 155 } 156 157 // Parent process, cont'd. 158 m_process_state_widget->set_tty_fd(ptm_fd); 159} 160 161void TerminalWrapper::kill_running_command() 162{ 163 ASSERT(m_pid != -1); 164 165 // Kill our child process and its whole process group. 166 (void)killpg(m_pid, SIGTERM); 167} 168 169TerminalWrapper::TerminalWrapper() 170{ 171 set_layout(make<GUI::VerticalBoxLayout>()); 172 173 RefPtr<Core::ConfigFile> config = Core::ConfigFile::get_for_app("Terminal"); 174 m_terminal_widget = add<TerminalWidget>(-1, false, config); 175 m_process_state_widget = add<ProcessStateWidget>(); 176} 177 178TerminalWrapper::~TerminalWrapper() 179{ 180}