Serenity Operating System
at hosted 185 lines 5.7 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 <string.h> 39#include <sys/ioctl.h> 40#include <sys/types.h> 41#include <sys/wait.h> 42#include <unistd.h> 43 44void TerminalWrapper::run_command(const String& command) 45{ 46 if (m_pid != -1) { 47 GUI::MessageBox::show( 48 "A command is already running in this TerminalWrapper", 49 "Can't run command", 50 GUI::MessageBox::Type::Error, 51 GUI::MessageBox::InputType::OK, 52 window()); 53 return; 54 } 55 56 int ptm_fd = posix_openpt(O_RDWR | O_CLOEXEC); 57 if (ptm_fd < 0) { 58 perror("posix_openpt"); 59 ASSERT_NOT_REACHED(); 60 } 61 if (grantpt(ptm_fd) < 0) { 62 perror("grantpt"); 63 ASSERT_NOT_REACHED(); 64 } 65 if (unlockpt(ptm_fd) < 0) { 66 perror("unlockpt"); 67 ASSERT_NOT_REACHED(); 68 } 69 70 m_terminal_widget->set_pty_master_fd(ptm_fd); 71 m_terminal_widget->on_command_exit = [this] { 72 int wstatus; 73 int rc = waitpid(m_pid, &wstatus, 0); 74 if (rc < 0) { 75 perror("waitpid"); 76 ASSERT_NOT_REACHED(); 77 } 78 if (WIFEXITED(wstatus)) { 79 m_terminal_widget->inject_string(String::format("\033[%d;1m(Command exited with code %d)\033[0m\n", wstatus == 0 ? 32 : 31, WEXITSTATUS(wstatus))); 80 } else if (WIFSTOPPED(wstatus)) { 81 m_terminal_widget->inject_string(String::format("\033[34;1m(Command stopped!)\033[0m\n")); 82 } else if (WIFSIGNALED(wstatus)) { 83 m_terminal_widget->inject_string(String::format("\033[34;1m(Command signaled with %s!)\033[0m\n", strsignal(WTERMSIG(wstatus)))); 84 } 85 m_process_state_widget->set_tty_fd(-1); 86 m_pid = -1; 87 88 if (on_command_exit) 89 on_command_exit(); 90 }; 91 92 m_pid = fork(); 93 if (m_pid == 0) { 94 // Create a new process group. 95 setsid(); 96 97 const char* tty_name = ptsname(ptm_fd); 98 if (!tty_name) { 99 perror("ptsname"); 100 exit(1); 101 } 102 close(ptm_fd); 103 int pts_fd = open(tty_name, O_RDWR); 104 if (pts_fd < 0) { 105 perror("open"); 106 exit(1); 107 } 108 109 // NOTE: It's okay if this fails. 110 (void)ioctl(0, TIOCNOTTY); 111 112 close(0); 113 close(1); 114 close(2); 115 116 int rc = dup2(pts_fd, 0); 117 if (rc < 0) { 118 perror("dup2"); 119 exit(1); 120 } 121 rc = dup2(pts_fd, 1); 122 if (rc < 0) { 123 perror("dup2"); 124 exit(1); 125 } 126 rc = dup2(pts_fd, 2); 127 if (rc < 0) { 128 perror("dup2"); 129 exit(1); 130 } 131 rc = close(pts_fd); 132 if (rc < 0) { 133 perror("close"); 134 exit(1); 135 } 136 rc = ioctl(0, TIOCSCTTY); 137 if (rc < 0) { 138 perror("ioctl(TIOCSCTTY)"); 139 exit(1); 140 } 141 142 setenv("TERM", "xterm", true); 143 144 auto parts = command.split(' '); 145 ASSERT(!parts.is_empty()); 146 const char** args = (const char**)calloc(parts.size() + 1, sizeof(const char*)); 147 for (size_t i = 0; i < parts.size(); i++) { 148 args[i] = parts[i].characters(); 149 } 150 rc = execvp(args[0], const_cast<char**>(args)); 151 if (rc < 0) { 152 perror("execve"); 153 exit(1); 154 } 155 ASSERT_NOT_REACHED(); 156 } 157 158 // Parent process, cont'd. 159 m_process_state_widget->set_tty_fd(ptm_fd); 160} 161 162void TerminalWrapper::kill_running_command() 163{ 164 ASSERT(m_pid != -1); 165 166 // Kill our child process and its whole process group. 167 (void)killpg(m_pid, SIGTERM); 168} 169 170TerminalWrapper::TerminalWrapper(bool user_spawned) 171 : m_user_spawned(user_spawned) 172{ 173 set_layout<GUI::VerticalBoxLayout>(); 174 175 RefPtr<Core::ConfigFile> config = Core::ConfigFile::get_for_app("Terminal"); 176 m_terminal_widget = add<TerminalWidget>(-1, false, config); 177 m_process_state_widget = add<ProcessStateWidget>(); 178 179 if (user_spawned) 180 run_command("Shell"); 181} 182 183TerminalWrapper::~TerminalWrapper() 184{ 185}