OSDN Git Service

ext4_utils: Yet another MMC discard pain in the ass
[android-x86/system-extras.git] / su / pts.c
1 /*
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
17  /*
18  * 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
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <signal.h>
29 #include <termios.h>
30 #include <errno.h>
31 #include <pthread.h>
32
33 #include "pts.h"
34
35 /**
36  * Helper functions
37  */
38 // Ensures all the data is written out
39 static int write_blocking(int fd, char *buf, size_t bufsz) {
40     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;
47     } while (written < (ssize_t)bufsz);
48
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  */
56 static 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  */
70 static void pump(int input, int output) {
71     pump_ex(input, output, 1);
72 }
73
74 static 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
83 static 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
94
95 /**
96  * pts_open
97  *
98  * Opens a pts device and returns the name of the slave tty device.
99  *
100  * Arguments
101  * slave_name       the name of the slave device
102  * slave_name_size  the size of the buffer passed via slave_name
103  *
104  * Return Values
105  * on failure either -2 or -1 (errno set) is returned.
106  * on success, the file descriptor of the master device is returned.
107  */
108 int pts_open(char *slave_name, size_t slave_name_size) {
109     int fdm;
110     char sn_tmp[slave_name_size];
111
112     // Open master ptmx device
113     fdm = open("/dev/ptmx", O_RDWR);
114     if (fdm == -1) return -1;
115
116     // Get the slave name
117     if (ptsname_r(fdm, sn_tmp, slave_name_size) != 0) {
118         close(fdm);
119         return -2;
120     }
121
122     if (strlcpy(slave_name, sn_tmp, slave_name_size) >= slave_name_size) {
123         return -1;
124     }
125
126     // Grant, then unlock
127     if (grantpt(fdm) == -1) {
128         close(fdm);
129         return -1;
130     }
131     if (unlockpt(fdm) == -1) {
132         close(fdm);
133         return -1;
134     }
135
136     return fdm;
137 }
138
139 // Stores the previous termios of stdin
140 static struct termios old_stdin;
141 static int stdin_is_raw = 0;
142
143 /**
144  * set_stdin_raw
145  *
146  * Changes stdin to raw unbuffered mode, disables echo, 
147  * auto carriage return, etc.
148  *
149  * Return Value
150  * on failure -1, and errno is set
151  * on success 0
152  */
153 int set_stdin_raw(void) {
154     struct termios new_termios;
155
156     // Save the current stdin termios
157     if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
158         return -1;
159     }
160
161     // Start from the current settings
162     new_termios = old_stdin;
163
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;
171
172     if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
173         return -1;
174     }
175
176     stdin_is_raw = 1;
177
178     return 0;
179 }
180
181 /**
182  * restore_stdin
183  *
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.
187  *
188  * This function is async-safe.
189  *
190  * Return Value
191  * on failure, -1 and errno is set
192  * on success, 0
193  */
194 int restore_stdin(void) {
195     if (!stdin_is_raw) return 0;
196
197     if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
198         return -1;
199     }
200
201     stdin_is_raw = 0;
202
203     return 0;
204 }
205
206 // Flag indicating whether the sigwinch watcher should terminate.
207 static volatile int closing_time = 0;
208
209 /**
210  * Thread process. Wait for a SIGWINCH to be received, then update 
211  * the terminal size.
212  */
213 static void *watch_sigwinch(void *data) {
214     sigset_t winch;
215     int sig;
216     int master = ((int *)data)[0];
217     int slave = ((int *)data)[1];
218
219     sigemptyset(&winch);
220     sigaddset(&winch, SIGWINCH);
221
222     do {
223         // Wait for a SIGWINCH
224         sigwait(&winch, &sig);
225
226         if (closing_time) break;
227
228         // Get the new terminal size
229         struct winsize w;
230         if (ioctl(master, TIOCGWINSZ, &w) == -1) {
231             continue;
232         }
233
234         // Set the new terminal size
235         ioctl(slave, TIOCSWINSZ, &w);
236
237     } while (1);
238
239     free(data);
240     return NULL;
241 }
242
243 /**
244  * watch_sigwinch_async
245  *
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".
249  *
250  * NOTE: This function blocks SIGWINCH and spawns a thread.
251  * NOTE 2: This function must be called before any of the
252  *         pump functions.
253  *
254  * Arguments
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
258  *
259  * Return Value
260  * on failure, -1 and errno will be set. In this case, no
261  *      thread has been spawned and SIGWINCH will not be 
262  *      blocked.
263  * on success, 0
264  */
265 int watch_sigwinch_async(int master, int slave) {
266     pthread_t watcher;
267     int *files = (int *) malloc(sizeof(int) * 2);
268     if (files == NULL) {
269         return -1;
270     }
271
272     // Block SIGWINCH so sigwait can later receive it
273     sigset_t winch;
274     sigemptyset(&winch);
275     sigaddset(&winch, SIGWINCH);
276     if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
277         free(files);
278         return -1;
279     }
280
281     // Initialize some variables, then start the thread
282     closing_time = 0;
283     files[0] = master;
284     files[1] = slave;
285     int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
286     if (ret != 0) {
287         free(files);
288         errno = ret;
289         return -1;
290     }
291
292     // Set the initial terminal size
293     raise(SIGWINCH);
294     return 0;
295 }
296
297 /**
298  * watch_sigwinch_cleanup
299  *
300  * Cause the SIGWINCH watcher thread to terminate
301  */
302 void watch_sigwinch_cleanup(void) {
303     closing_time = 1;
304     raise(SIGWINCH);
305 }
306
307 /**
308  * pump_stdin_async
309  *
310  * Forward data from STDIN to the given FD
311  * in a seperate thread
312  */
313 void pump_stdin_async(int outfd) {
314     // Put stdin into raw mode
315     set_stdin_raw();
316
317     // Pump data from stdin to the PTY
318     pump_async(STDIN_FILENO, outfd);
319 }
320
321 /**
322  * pump_stdout_blocking
323  *
324  * Forward data from the FD to STDOUT.
325  * Returns when the remote end of the FD closes.
326  *
327  * Before returning, restores stdin settings.
328  */
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 */);
332
333     // Cleanup
334     restore_stdin();
335     watch_sigwinch_cleanup();
336 }