2 * Copyright 2013, Tan Chee Eng (@tan-ce)
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 * Manages the pseudo-terminal driver on Linux/Android and provides some
21 * helper functions to handle raw input mode and terminal window resizing
38 // Ensures all the data is written out
39 static int write_blocking(int fd, char *buf, size_t bufsz) {
44 ret = write(fd, buf + written, bufsz - written);
45 if (ret == -1) return -1;
47 } while (written < (ssize_t)bufsz);
53 * Pump data from input FD to output FD. If close_output is
54 * true, then close the output FD when we're done.
56 static void pump_ex(int input, int output, int close_output) {
59 while ((len = read(input, buf, 4096)) > 0) {
60 if (write_blocking(output, buf, len) == -1) break;
63 if (close_output) close(output);
67 * Pump data from input FD to output FD. Will close the
68 * output FD when done.
70 static void pump(int input, int output) {
71 pump_ex(input, output, 1);
74 static void* pump_thread(void* data) {
75 int* files = (int*)data;
77 int output = files[1];
83 static void pump_async(int input, int output) {
85 int* files = (int*)malloc(sizeof(int) * 2);
91 pthread_create(&writer, NULL, pump_thread, files);
98 * Opens a pts device and returns the name of the slave tty device.
101 * slave_name the name of the slave device
102 * slave_name_size the size of the buffer passed via slave_name
105 * on failure either -2 or -1 (errno set) is returned.
106 * on success, the file descriptor of the master device is returned.
108 int pts_open(char *slave_name, size_t slave_name_size) {
110 char sn_tmp[slave_name_size];
112 // Open master ptmx device
113 fdm = open("/dev/ptmx", O_RDWR);
114 if (fdm == -1) return -1;
116 // Get the slave name
117 if (ptsname_r(fdm, sn_tmp, slave_name_size) != 0) {
122 if (strlcpy(slave_name, sn_tmp, slave_name_size) >= slave_name_size) {
126 // Grant, then unlock
127 if (grantpt(fdm) == -1) {
131 if (unlockpt(fdm) == -1) {
139 // Stores the previous termios of stdin
140 static struct termios old_stdin;
141 static int stdin_is_raw = 0;
146 * Changes stdin to raw unbuffered mode, disables echo,
147 * auto carriage return, etc.
150 * on failure -1, and errno is set
153 int set_stdin_raw(void) {
154 struct termios new_termios;
156 // Save the current stdin termios
157 if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
161 // Start from the current settings
162 new_termios = old_stdin;
164 // Make the terminal like an SSH or telnet client
165 new_termios.c_iflag |= IGNPAR;
166 new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
167 new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
168 new_termios.c_oflag &= ~OPOST;
169 new_termios.c_cc[VMIN] = 1;
170 new_termios.c_cc[VTIME] = 0;
172 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
184 * Restore termios on stdin to the state it was before
185 * set_stdin_raw() was called. If set_stdin_raw() was
186 * never called, does nothing and doesn't return an error.
188 * This function is async-safe.
191 * on failure, -1 and errno is set
194 int restore_stdin(void) {
195 if (!stdin_is_raw) return 0;
197 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
206 // Flag indicating whether the sigwinch watcher should terminate.
207 static volatile int closing_time = 0;
210 * Thread process. Wait for a SIGWINCH to be received, then update
213 static void *watch_sigwinch(void *data) {
216 int master = ((int *)data)[0];
217 int slave = ((int *)data)[1];
220 sigaddset(&winch, SIGWINCH);
223 // Wait for a SIGWINCH
224 sigwait(&winch, &sig);
226 if (closing_time) break;
228 // Get the new terminal size
230 if (ioctl(master, TIOCGWINSZ, &w) == -1) {
234 // Set the new terminal size
235 ioctl(slave, TIOCSWINSZ, &w);
244 * watch_sigwinch_async
246 * After calling this function, if the application receives
247 * SIGWINCH, the terminal window size will be read from
248 * "input" and set on "output".
250 * NOTE: This function blocks SIGWINCH and spawns a thread.
251 * NOTE 2: This function must be called before any of the
255 * master A file descriptor of the TTY window size to follow
256 * slave A file descriptor of the TTY window size which is
257 * to be set on SIGWINCH
260 * on failure, -1 and errno will be set. In this case, no
261 * thread has been spawned and SIGWINCH will not be
265 int watch_sigwinch_async(int master, int slave) {
267 int *files = (int *) malloc(sizeof(int) * 2);
272 // Block SIGWINCH so sigwait can later receive it
275 sigaddset(&winch, SIGWINCH);
276 if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
281 // Initialize some variables, then start the thread
285 int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
292 // Set the initial terminal size
298 * watch_sigwinch_cleanup
300 * Cause the SIGWINCH watcher thread to terminate
302 void watch_sigwinch_cleanup(void) {
310 * Forward data from STDIN to the given FD
311 * in a seperate thread
313 void pump_stdin_async(int outfd) {
314 // Put stdin into raw mode
317 // Pump data from stdin to the PTY
318 pump_async(STDIN_FILENO, outfd);
322 * pump_stdout_blocking
324 * Forward data from the FD to STDOUT.
325 * Returns when the remote end of the FD closes.
327 * Before returning, restores stdin settings.
329 void pump_stdout_blocking(int infd) {
330 // Pump data from stdout to PTY
331 pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */);
335 watch_sigwinch_cleanup();