Serenity Operating System
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}