blob: 12a47c38866abf38ae825b5adb1b0771ccd464da [file] [log] [blame]
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +08001/*
2 * Copyright 2013, Tan Chee Eng (@tan-ce)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Luca Stefani5eb52ea2018-08-26 11:35:00 +020017/*
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +080018 * pts.c
19 *
20 * Manages the pseudo-terminal driver on Linux/Android and provides some
21 * helper functions to handle raw input mode and terminal window resizing
22 */
23
Luca Stefani5eb52ea2018-08-26 11:35:00 +020024#include <errno.h>
25#include <fcntl.h>
26#include <pthread.h>
27#include <signal.h>
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +080028#include <stdlib.h>
Brandon McAnsh8a03bcf2015-10-08 10:43:37 -040029#include <string.h>
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +080030#include <termios.h>
Luca Stefani5eb52ea2018-08-26 11:35:00 +020031#include <unistd.h>
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +080032
33#include "pts.h"
34
35/**
Tan Chee Eng8881f702013-09-08 21:08:44 +080036 * Helper functions
37 */
38// Ensures all the data is written out
Luca Stefani5eb52ea2018-08-26 11:35:00 +020039static int write_blocking(int fd, char* buf, size_t bufsz) {
Tan Chee Eng8881f702013-09-08 21:08:44 +080040 ssize_t ret, written;
41
42 written = 0;
43 do {
44 ret = write(fd, buf + written, bufsz - written);
45 if (ret == -1) return -1;
46 written += ret;
Marcos Marado2b782d12014-12-11 20:41:24 +000047 } while (written < (ssize_t)bufsz);
Tan Chee Eng8881f702013-09-08 21:08:44 +080048
49 return 0;
50}
51
52/**
53 * Pump data from input FD to output FD. If close_output is
54 * true, then close the output FD when we're done.
55 */
56static void pump_ex(int input, int output, int close_output) {
57 char buf[4096];
58 int len;
59 while ((len = read(input, buf, 4096)) > 0) {
60 if (write_blocking(output, buf, len) == -1) break;
61 }
62 close(input);
63 if (close_output) close(output);
64}
65
66/**
67 * Pump data from input FD to output FD. Will close the
68 * output FD when done.
69 */
70static void pump(int input, int output) {
71 pump_ex(input, output, 1);
72}
73
74static void* pump_thread(void* data) {
75 int* files = (int*)data;
76 int input = files[0];
77 int output = files[1];
78 pump(input, output);
79 free(data);
80 return NULL;
81}
82
83static void pump_async(int input, int output) {
84 pthread_t writer;
85 int* files = (int*)malloc(sizeof(int) * 2);
86 if (files == NULL) {
87 exit(-1);
88 }
89 files[0] = input;
90 files[1] = output;
91 pthread_create(&writer, NULL, pump_thread, files);
92}
93
Tan Chee Eng8881f702013-09-08 21:08:44 +080094/**
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +080095 * pts_open
96 *
97 * Opens a pts device and returns the name of the slave tty device.
98 *
99 * Arguments
100 * slave_name the name of the slave device
101 * slave_name_size the size of the buffer passed via slave_name
102 *
103 * Return Values
104 * on failure either -2 or -1 (errno set) is returned.
105 * on success, the file descriptor of the master device is returned.
106 */
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200107int pts_open(char* slave_name, size_t slave_name_size) {
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800108 int fdm;
Marcos Marado2b782d12014-12-11 20:41:24 +0000109 char sn_tmp[slave_name_size];
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800110
111 // Open master ptmx device
112 fdm = open("/dev/ptmx", O_RDWR);
113 if (fdm == -1) return -1;
114
115 // Get the slave name
Marcos Marado2b782d12014-12-11 20:41:24 +0000116 if (ptsname_r(fdm, sn_tmp, slave_name_size) != 0) {
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800117 close(fdm);
118 return -2;
119 }
120
Daniel Micayc9d292e2015-11-08 16:52:17 -0500121 if (strlcpy(slave_name, sn_tmp, slave_name_size) >= slave_name_size) {
122 return -1;
123 }
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800124
125 // Grant, then unlock
126 if (grantpt(fdm) == -1) {
127 close(fdm);
128 return -1;
129 }
130 if (unlockpt(fdm) == -1) {
131 close(fdm);
132 return -1;
133 }
134
135 return fdm;
136}
137
138// Stores the previous termios of stdin
139static struct termios old_stdin;
140static int stdin_is_raw = 0;
141
142/**
143 * set_stdin_raw
144 *
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200145 * Changes stdin to raw unbuffered mode, disables echo,
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800146 * auto carriage return, etc.
147 *
148 * Return Value
149 * on failure -1, and errno is set
150 * on success 0
151 */
152int set_stdin_raw(void) {
153 struct termios new_termios;
154
155 // Save the current stdin termios
156 if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
157 return -1;
158 }
159
160 // Start from the current settings
161 new_termios = old_stdin;
162
163 // Make the terminal like an SSH or telnet client
164 new_termios.c_iflag |= IGNPAR;
165 new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
166 new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
167 new_termios.c_oflag &= ~OPOST;
168 new_termios.c_cc[VMIN] = 1;
169 new_termios.c_cc[VTIME] = 0;
170
171 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
172 return -1;
173 }
174
175 stdin_is_raw = 1;
176
177 return 0;
178}
179
180/**
181 * restore_stdin
182 *
183 * Restore termios on stdin to the state it was before
184 * set_stdin_raw() was called. If set_stdin_raw() was
185 * never called, does nothing and doesn't return an error.
186 *
187 * This function is async-safe.
188 *
189 * Return Value
190 * on failure, -1 and errno is set
191 * on success, 0
192 */
193int restore_stdin(void) {
194 if (!stdin_is_raw) return 0;
195
196 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
197 return -1;
198 }
199
200 stdin_is_raw = 0;
201
202 return 0;
203}
204
205// Flag indicating whether the sigwinch watcher should terminate.
Marcos Marado2b782d12014-12-11 20:41:24 +0000206static volatile int closing_time = 0;
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800207
208/**
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200209 * Thread process. Wait for a SIGWINCH to be received, then update
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800210 * the terminal size.
211 */
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200212static void* watch_sigwinch(void* data) {
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800213 sigset_t winch;
214 int sig;
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200215 int master = ((int*)data)[0];
216 int slave = ((int*)data)[1];
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800217
218 sigemptyset(&winch);
219 sigaddset(&winch, SIGWINCH);
220
221 do {
222 // Wait for a SIGWINCH
223 sigwait(&winch, &sig);
224
225 if (closing_time) break;
226
227 // Get the new terminal size
228 struct winsize w;
229 if (ioctl(master, TIOCGWINSZ, &w) == -1) {
230 continue;
231 }
232
233 // Set the new terminal size
234 ioctl(slave, TIOCSWINSZ, &w);
235
236 } while (1);
237
238 free(data);
239 return NULL;
240}
241
242/**
243 * watch_sigwinch_async
244 *
245 * After calling this function, if the application receives
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200246 * SIGWINCH, the terminal window size will be read from
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800247 * "input" and set on "output".
248 *
249 * NOTE: This function blocks SIGWINCH and spawns a thread.
Tan Chee Eng8881f702013-09-08 21:08:44 +0800250 * NOTE 2: This function must be called before any of the
251 * pump functions.
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800252 *
253 * Arguments
254 * master A file descriptor of the TTY window size to follow
255 * slave A file descriptor of the TTY window size which is
256 * to be set on SIGWINCH
257 *
258 * Return Value
259 * on failure, -1 and errno will be set. In this case, no
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200260 * thread has been spawned and SIGWINCH will not be
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800261 * blocked.
262 * on success, 0
263 */
264int watch_sigwinch_async(int master, int slave) {
265 pthread_t watcher;
Luca Stefani5eb52ea2018-08-26 11:35:00 +0200266 int* files = (int*)malloc(sizeof(int) * 2);
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800267 if (files == NULL) {
268 return -1;
269 }
270
271 // Block SIGWINCH so sigwait can later receive it
272 sigset_t winch;
273 sigemptyset(&winch);
274 sigaddset(&winch, SIGWINCH);
275 if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
276 free(files);
277 return -1;
278 }
279
280 // Initialize some variables, then start the thread
281 closing_time = 0;
282 files[0] = master;
283 files[1] = slave;
284 int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
285 if (ret != 0) {
286 free(files);
287 errno = ret;
288 return -1;
289 }
290
291 // Set the initial terminal size
292 raise(SIGWINCH);
Ricardo Cerqueira0272c3b2014-01-12 22:38:23 +0000293 return 0;
Tan Chee Eng1adfb9e2013-08-30 16:09:21 +0800294}
295
296/**
297 * watch_sigwinch_cleanup
298 *
299 * Cause the SIGWINCH watcher thread to terminate
300 */
301void watch_sigwinch_cleanup(void) {
302 closing_time = 1;
303 raise(SIGWINCH);
304}
Tan Chee Eng8881f702013-09-08 21:08:44 +0800305
306/**
307 * pump_stdin_async
308 *
309 * Forward data from STDIN to the given FD
310 * in a seperate thread
311 */
312void pump_stdin_async(int outfd) {
313 // Put stdin into raw mode
314 set_stdin_raw();
315
316 // Pump data from stdin to the PTY
317 pump_async(STDIN_FILENO, outfd);
318}
319
320/**
321 * pump_stdout_blocking
322 *
323 * Forward data from the FD to STDOUT.
324 * Returns when the remote end of the FD closes.
325 *
326 * Before returning, restores stdin settings.
327 */
328void pump_stdout_blocking(int infd) {
329 // Pump data from stdout to PTY
330 pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */);
331
332 // Cleanup
333 restore_stdin();
334 watch_sigwinch_cleanup();
335}