Branch data Line data Source code
1 : : #ifdef RUN_TESTS
2 : : # include <gtest/gtest.h>
3 : : extern "C" {
4 : : # include <gcov.h>
5 : : }
6 : : # include <utmp.h>
7 : : #endif
8 : : /** @file tty/forkpty.cc
9 : : * @brief Physical frontend to the underlying process (shell).
10 : : */
11 : :
12 : : #include <cstdlib>
13 : : #include <cerrno>
14 : :
15 : : #include <unistd.h>
16 : : #include <sys/wait.h>
17 : : #include <sys/fcntl.h>
18 : : #include <sys/ioctl.h>
19 : :
20 : : #ifdef __APPLE__
21 : : # include <util.h>
22 : : #else
23 : : # include <pty.h>
24 : : #endif
25 : :
26 : : #include "forkpty.hh"
27 : :
28 : 4 : bool ForkPTY::Active() const
29 : : {
30 : 4 : return fd >= 0;
31 : : }
32 : :
33 : 3 : int ForkPTY::getfd() const
34 : : {
35 : 3 : return fd;
36 : : }
37 : :
38 : 4 : ForkPTY::~ForkPTY()
39 : : {
40 [ - + ]: 4 : if(Active())
41 : : {
42 : 0 : Close();
43 : : }
44 : 4 : }
45 : :
46 : 4 : void ForkPTY::Open(std::size_t width, std::size_t height)
47 : : {
48 : 4 : struct winsize ws = {};
49 : 4 : ws.ws_col = width;
50 : 4 : ws.ws_row = height;
51 : : #if 0
52 : : pid = -1;
53 : : #elif defined RUN_TESTS_no
54 : : // gcov duplicates every counter on fork. Because of that, implement forkpty
55 : : // manually, and do NOT call fork() (which gets aliased to __gcov_fork)
56 : : int fd2 = -1;
57 : : openpty(&fd, &fd2, nullptr, nullptr, &ws);
58 : : pid = syscall(__NR_fork);
59 : : if(pid)
60 : : close(fd2);
61 : : else
62 : : {
63 : : __gcov_reset();
64 : : close(fd);
65 : : login_tty(fd2);
66 : : }
67 : : #else
68 : 4 : pid = forkpty(&fd, nullptr, nullptr, &ws);
69 : : #endif
70 [ - + ]: 4 : if(!pid)
71 : : {
72 : 0 : static char termstr[] = "TERM=xterm";
73 : 0 : putenv(termstr);
74 : : #ifdef RUN_TESTS_no
75 : : __gcov_dump();
76 : : #endif
77 : 0 : execl(std::getenv("SHELL"), std::getenv("SHELL"), "-l", "-i", nullptr); // TODO: check return values
78 : : // Note: getenv() is in C++ standard, but putenv() is not.
79 : 0 : _exit(0);
80 : : }
81 : :
82 : : // Change the virtual terminal handle (file descriptor)
83 : : // into non-blocking mode.
84 : 4 : fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
85 : 4 : }
86 : :
87 : 4 : void ForkPTY::Close()
88 : : {
89 : 4 : Kill(SIGTERM);
90 : 4 : close(fd);
91 : 4 : waitpid(pid, nullptr, 0);
92 : 4 : fd = -1;
93 : 4 : }
94 : :
95 : 4 : int ForkPTY::Send(std::string_view buffer)
96 : : {
97 : 4 : return write(fd, buffer.data(), buffer.size());
98 : : }
99 : :
100 : 115 : std::pair<std::string,int> ForkPTY::Recv()
101 : : {
102 : : //char buffer[16]; usleep(400);
103 : 115 : char buffer[512];
104 [ + - ]: 115 : std::pair<std::string,int> result;
105 [ + - ]: 115 : result.second = read(fd, buffer, sizeof(buffer));
106 [ + + ]: 115 : if(result.second > 0)
107 [ + - ]: 7 : result.first.assign(buffer, buffer+result.second);
108 : 115 : return result;
109 : 0 : }
110 : :
111 : 5 : void ForkPTY::Kill(int signal)
112 : : {
113 [ + - ]: 5 : if(pid >= 0) kill(pid, signal);
114 : 5 : }
115 : :
116 : 1 : void ForkPTY::Resize(unsigned xsize, unsigned ysize)
117 : : {
118 : 1 : struct winsize ws = {};
119 : 1 : ws.ws_col = xsize;
120 : 1 : ws.ws_row = ysize;
121 : 1 : ioctl(fd, TIOCSWINSZ, &ws);
122 : 1 : }
123 : :
124 : : #ifdef RUN_TESTS
125 : : // For some strange reason, every call here seems to be treated as a branch by gcov.
126 : : // This seriously skews the statistics.
127 : :
128 : : #include <chrono>
129 : 3 : TEST(forkpty, opening_a_shell_works) // Test that opening a shell works
130 : : {
131 : 1 : ForkPTY pty(80,25);
132 : 1 : pty.Active();
133 : 1 : EXPECT_NE(pty.getfd(), -1);
134 [ + - ]: 1 : pty.Close();
135 : 1 : pty.Active();
136 : 1 : }
137 : 3 : TEST(forkpty, killing_a_shell_works) // Test that killing a shell works
138 : : {
139 : 1 : ForkPTY pty(80,25);
140 : : // Disable non-blocking mode
141 [ + - + - ]: 1 : fcntl(pty.getfd(), F_SETFL, fcntl(pty.getfd(), F_GETFL) & ~O_NONBLOCK);
142 : :
143 : 2 : EXPECT_NE(pty.Recv().second, -1);
144 : 1 : pty.Kill(SIGHUP);
145 : 1 : pty.Active();
146 : 2 : EXPECT_EQ(pty.Recv().second, -1);
147 [ + - ]: 1 : pty.Close();
148 : 1 : pty.Active();
149 : 1 : }
150 : : // Test that running commands in shell works
151 : : // This test depends on the command `stat` existing.
152 : 3 : TEST(forkpty, running_a_command_in_shell_works)
153 : : {
154 : 1 : ForkPTY pty(80,25);
155 : 58 : auto t = []{return std::chrono::system_clock::now(); };
156 : : // Wait until we receive something
157 : 1 : auto start = t();
158 [ + - ]: 1 : pty.Send("\r");
159 : 1 : std::string in;
160 [ + + ]: 3 : while(in.empty())
161 : : {
162 [ + - ]: 2 : auto k = pty.Recv();
163 [ + - ]: 4 : in += k.first;
164 : 2 : ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
165 : 2 : usleep(10000);
166 : 2 : }
167 : : // Send a command
168 [ + - ]: 1 : pty.Send("stat /tmp\r");
169 : : // Wait until we receive something
170 : 55 : for(;;)
171 : : {
172 [ + - ]: 55 : auto k = pty.Recv();
173 [ + - ]: 110 : in += k.first;
174 : 55 : ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
175 : 55 : usleep(10000);
176 [ + + ]: 55 : if(in.find("Device:") != in.npos) break;
177 : 54 : }
178 [ + - ]: 1 : pty.Close();
179 : 1 : }
180 : : // Test that the shell reacts to resizes.
181 : 3 : TEST(forkpty, resizing_works)
182 : : {
183 : 1 : ForkPTY pty(80,25);
184 : 57 : auto t = []{return std::chrono::system_clock::now(); };
185 : : // Wait until we receive something
186 : 1 : auto start = t();
187 [ + - ]: 1 : pty.Send("\r");
188 : 1 : pty.Active();
189 : 1 : pty.Resize(40,30);
190 : 1 : std::string in;
191 [ + + ]: 2 : while(in.empty())
192 : : {
193 [ + - ]: 1 : auto k = pty.Recv();
194 [ + - ]: 2 : in += k.first;
195 : 1 : ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
196 : 1 : usleep(10000);
197 : 1 : }
198 : : // Send a command
199 [ + - ]: 1 : pty.Send("echo $LINES $COLUMNS\r");
200 : : // Wait until we receive something
201 : 55 : for(;;)
202 : : {
203 [ + - ]: 55 : auto k = pty.Recv();
204 [ + - ]: 110 : in += k.first;
205 : 55 : ASSERT_LE(std::chrono::duration<double>(t()-start).count(), 2);
206 : 55 : usleep(10000);
207 [ + + ]: 55 : if(in.find("30 40") != in.npos) break;
208 : 54 : }
209 [ + - ]: 1 : pty.Close();
210 : 1 : pty.Active();
211 : 1 : }
212 : : #endif
|