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 <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}