1 /* -*- Mode: C -*-
2 * open_init_pty.c ---
3 * Author : Manoj Srivastava ( srivasta@glaurung.internal.golden-gryphon.com )
4 * Created On : Fri Jan 14 10:48:28 2005
5 * Created On Node : glaurung.internal.golden-gryphon.com
6 * Last Modified By : Manoj Srivastava
7 * Last Modified On : Thu Sep 15 00:57:00 2005
8 * Last Machine Used: glaurung.internal.golden-gryphon.com
9 * Update Count : 92
10 * Status : Unknown, Use with caution!
11 * HISTORY :
12 * Description :
13 *
14 * Distributed under the terms of the GNU General Public License v2
15 *
16 * open_init_pty
17 *
18 * SYNOPSIS:
19 *
20 * This program allows a systems administrator to execute daemons
21 * which need to work in the initrc domain, and which need to have
22 * pty's as system_u:system_r:initrc_t
23 *
24 * USAGE:
25 *
26 * * arch-tag: a5583d39-72b9-4cdf-ba1b-5678ea4cbe20
27 */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <signal.h>
34 #include <errno.h>
35
36 #include <sysexits.h>
37
38 #include <pty.h> /* for forkpty */
39 #include <termios.h>
40 #include <fcntl.h>
41
42 #include <sys/select.h>
43 #include <sys/wait.h>
44
45
46 #define MAXRETR 3 /* The max number of IO retries on a fd */
47 #define BUFSIZE 2048 /* The ring buffer size */
48
49 static struct termios saved_termios;
50 static int saved_fd = -1;
51 static enum { RESET, RAW, CBREAK } tty_state = RESET;
52
tty_semi_raw(int fd)53 static int tty_semi_raw(int fd)
54 {
55 struct termios buf;
56
57 if (tty_state == RESET) {
58 if (tcgetattr(fd, &saved_termios) < 0) {
59 return -1;
60 }
61 }
62
63 buf = saved_termios;
64 /*
65 * echo off, canonical mode off, extended input processing off,
66 * signal chars off
67 */
68 buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
69 /*
70 * no SIGINT on break, CR-to-NL off, input parity check off, do not
71 * strip 8th bit on input,output flow control off
72 */
73 buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
74 /* Clear size bits, parity checking off */
75 buf.c_cflag &= ~(CSIZE | PARENB);
76 /* set 8 bits/char */
77 buf.c_cflag |= CS8;
78 /* Output processing off
79 buf.c_oflag &= ~(OPOST); */
80
81 buf.c_cc[VMIN] = 1; /* one byte at a time, no timer */
82 buf.c_cc[VTIME] = 0;
83 if (tcsetattr(fd, TCSANOW, &buf) < 0) {
84 return -1;
85 } /* end of if(tcsetattr(fileno(stdin), TCSANOW, &buf) < 0) */
86 tty_state = RAW;
87 saved_fd = fd;
88 return 0;
89 }
90
tty_atexit(void)91 static void tty_atexit(void)
92 {
93 if (tty_state != CBREAK && tty_state != RAW) {
94 return;
95 }
96
97 if (tcsetattr(saved_fd, TCSANOW, &saved_termios) < 0) {
98 return;
99 }
100 tty_state = RESET;
101 return;
102 }
103
104
105 /* The simple ring buffer */
106 struct ring_buffer {
107 char *buf; /* pointer to buffer memory */
108 char *wptr;
109 char *rptr;
110 size_t size; /* the number of bytes allocated for buf */
111 size_t count;
112 };
113
rb_init(struct ring_buffer * b,char * buf,size_t size)114 static void rb_init(struct ring_buffer *b, char *buf, size_t size)
115 {
116 b->buf = b->wptr = b->rptr = buf;
117 b->size = size;
118 b->count = 0;
119 }
120
rb_isempty(struct ring_buffer * b)121 static int rb_isempty(struct ring_buffer *b)
122 {
123 return b->count == 0;
124 }
125
126 /* return the unused space size in the buffer */
rb_space(struct ring_buffer * b)127 static size_t rb_space(struct ring_buffer *b)
128 {
129 if (b->rptr > b->wptr)
130 return b->rptr - b->wptr;
131
132 if (b->rptr < b->wptr || b->count == 0)
133 return b->buf + b->size - b->wptr;
134
135 return 0; /* should not hit this */
136 }
137
138 /* return the used space in the buffer */
rb_chunk_size(struct ring_buffer * b)139 static size_t rb_chunk_size(struct ring_buffer *b)
140 {
141 if (b->rptr < b->wptr)
142 return b->wptr - b->rptr;
143
144 if (b->rptr > b->wptr || b->count > 0)
145 return b->buf + b->size - b->rptr;
146
147 return 0; /* should not hit this */
148 }
149
150 /* read from fd and write to buffer memory */
rb_read(struct ring_buffer * b,int fd)151 static ssize_t rb_read(struct ring_buffer *b, int fd)
152 {
153 ssize_t n = read(fd, b->wptr, rb_space(b));
154 if (n <= 0)
155 return n;
156
157 b->wptr += n;
158 b->count += n;
159 if (b->buf + b->size <= b->wptr)
160 b->wptr = b->buf;
161
162 return n;
163 }
164
rb_write(struct ring_buffer * b,int fd)165 static ssize_t rb_write(struct ring_buffer *b, int fd)
166 {
167 ssize_t n = write(fd, b->rptr, rb_chunk_size(b));
168 if (n <= 0)
169 return n;
170
171 b->rptr += n;
172 b->count -= n;
173 if (b->buf + b->size <= b->rptr)
174 b->rptr = b->buf;
175
176 return n;
177 }
178
setfd_nonblock(int fd)179 static void setfd_nonblock(int fd)
180 {
181 int fsflags = fcntl(fd, F_GETFL);
182
183 if (fsflags < 0) {
184 fprintf(stderr, "fcntl(%d, F_GETFL): %s\n", fd, strerror(errno));
185 exit(EX_IOERR);
186 }
187
188 if (fcntl(fd, F_SETFL, fsflags | O_NONBLOCK) < 0) {
189 fprintf(stderr, "fcntl(%d, F_SETFL, ... | O_NONBLOCK): %s\n", fd, strerror(errno));
190 exit(EX_IOERR);
191 }
192 }
193
sigchld_handler(int asig)194 static void sigchld_handler(int asig __attribute__ ((unused)))
195 {
196 }
197
main(int argc,char * argv[])198 int main(int argc, char *argv[])
199 {
200 pid_t child_pid;
201 int child_exit_status;
202 struct termios tty_attr;
203 struct winsize window_size;
204 int pty_master;
205
206 /* for select */
207 fd_set readfds;
208 fd_set writefds;
209
210 unsigned err_n_rpty = 0;
211 unsigned err_n_wpty = 0;
212 unsigned err_n_stdin = 0;
213 unsigned err_n_stdout = 0;
214
215 int done = 0;
216
217 /* the ring buffers */
218 char inbuf_mem[BUFSIZE];
219 char outbuf_mem[BUFSIZE];
220 struct ring_buffer inbuf;
221 struct ring_buffer outbuf;
222 rb_init(&inbuf, inbuf_mem, sizeof(inbuf_mem));
223 rb_init(&outbuf, outbuf_mem, sizeof(outbuf_mem));
224
225 if (argc == 1) {
226 printf("usage: %s PROGRAM [ARGS]...\n", argv[0]);
227 exit(1);
228 }
229
230 /* We need I/O calls to fail with EINTR on SIGCHLD... */
231 if (signal(SIGCHLD, sigchld_handler) == SIG_ERR) {
232 perror("signal(SIGCHLD,...)");
233 exit(EX_OSERR);
234 }
235
236 if (isatty(STDIN_FILENO)) {
237 /* get terminal parameters associated with stdout */
238 if (tcgetattr(STDOUT_FILENO, &tty_attr) < 0) {
239 perror("tcgetattr(stdout,...)");
240 exit(EX_OSERR);
241 }
242
243 /* get window size */
244 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window_size) < 0) {
245 perror("ioctl(stdout,...)");
246 exit(1);
247 }
248
249 child_pid = forkpty(&pty_master, NULL, &tty_attr, &window_size);
250 } else { /* not interactive */
251 child_pid = forkpty(&pty_master, NULL, NULL, NULL);
252 }
253
254 if (child_pid < 0) {
255 perror("forkpty()");
256 exit(EX_OSERR);
257 }
258 if (child_pid == 0) { /* in the child */
259 struct termios s_tty_attr;
260 if (tcgetattr(STDIN_FILENO, &s_tty_attr)) {
261 perror("tcgetattr(stdin,...)");
262 exit(EXIT_FAILURE);
263 }
264 /* Turn off echo */
265 s_tty_attr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
266 /* Also turn of NL to CR?LF on output */
267 s_tty_attr.c_oflag &= ~(ONLCR);
268 if (tcsetattr(STDIN_FILENO, TCSANOW, &s_tty_attr)) {
269 perror("tcsetattr(stdin,...)");
270 exit(EXIT_FAILURE);
271 }
272
273 if (execvp(argv[1], argv + 1)) {
274 perror("execvp()");
275 exit(EXIT_FAILURE);
276 }
277 }
278
279 /* Non blocking mode for all file descriptors. */
280 setfd_nonblock(pty_master);
281 setfd_nonblock(STDIN_FILENO);
282 setfd_nonblock(STDOUT_FILENO);
283
284 if (isatty(STDIN_FILENO)) {
285 if (tty_semi_raw(STDIN_FILENO) < 0) {
286 perror("tty_semi_raw(stdin)");
287 }
288 if (atexit(tty_atexit) < 0) {
289 perror("atexit()");
290 }
291 }
292
293 do {
294 /* Accept events only on fds, that we can handle now. */
295 int do_select = 0;
296 FD_ZERO(&readfds);
297 FD_ZERO(&writefds);
298
299 if (rb_space(&outbuf) > 0 && err_n_rpty < MAXRETR) {
300 FD_SET(pty_master, &readfds);
301 do_select = 1;
302 }
303
304 if (!rb_isempty(&inbuf) && err_n_wpty < MAXRETR) {
305 FD_SET(pty_master, &writefds);
306 do_select = 1;
307 }
308
309 if (rb_space(&inbuf) > 0 && err_n_stdin < MAXRETR) {
310 FD_SET(STDIN_FILENO, &readfds);
311 do_select = 1;
312 }
313
314 if (!rb_isempty(&outbuf) && err_n_stdout < MAXRETR) {
315 FD_SET(STDOUT_FILENO, &writefds);
316 do_select = 1;
317 }
318
319 if (!do_select) {
320 #ifdef DEBUG
321 fprintf(stderr, "No I/O job for us, calling waitpid()...\n");
322 #endif
323 while (waitpid(child_pid, &child_exit_status, 0) < 0)
324 {
325 /* nothing */
326 }
327 break;
328 }
329
330 int select_rc = select(pty_master + 1, &readfds, &writefds, NULL, NULL);
331 if (select_rc < 0) {
332 perror("select()");
333 exit(EX_IOERR);
334 }
335 #ifdef DEBUG
336 fprintf(stderr, "select() returned %d\n", select_rc);
337 #endif
338
339 if (FD_ISSET(STDOUT_FILENO, &writefds)) {
340 #ifdef DEBUG
341 fprintf(stderr, "stdout can be written\n");
342 #endif
343 ssize_t n = rb_write(&outbuf, STDOUT_FILENO);
344 if (n <= 0 && n != EINTR && n != EAGAIN)
345 err_n_stdout++;
346 #ifdef DEBUG
347 if (n >= 0)
348 fprintf(stderr, "%d bytes written into stdout\n", n);
349 else
350 perror("write(stdout,...)");
351 #endif
352 }
353
354 if (FD_ISSET(pty_master, &writefds)) {
355 #ifdef DEBUG
356 fprintf(stderr, "pty_master can be written\n");
357 #endif
358 ssize_t n = rb_write(&inbuf, pty_master);
359 if (n <= 0 && n != EINTR && n != EAGAIN)
360 err_n_wpty++;
361 #ifdef DEBUG
362 if (n >= 0)
363 fprintf(stderr, "%d bytes written into pty_master\n", n);
364 else
365 perror("write(pty_master,...)");
366 #endif
367 }
368
369 if (FD_ISSET(STDIN_FILENO, &readfds)) {
370 #ifdef DEBUG
371 fprintf(stderr, "stdin can be read\n");
372 #endif
373 ssize_t n = rb_read(&inbuf, STDIN_FILENO);
374 if (n <= 0 && n != EINTR && n != EAGAIN)
375 err_n_stdin++;
376 #ifdef DEBUG
377 if (n >= 0)
378 fprintf(stderr, "%d bytes read from stdin\n", n);
379 else
380 perror("read(stdin,...)");
381 #endif
382 }
383
384 if (FD_ISSET(pty_master, &readfds)) {
385 #ifdef DEBUG
386 fprintf(stderr, "pty_master can be read\n");
387 #endif
388 ssize_t n = rb_read(&outbuf, pty_master);
389 if (n <= 0 && n != EINTR && n != EAGAIN)
390 err_n_rpty++;
391 #ifdef DEBUG
392 if (n >= 0)
393 fprintf(stderr, "%d bytes read from pty_master\n", n);
394 else
395 perror("read(pty_master,...)");
396 #endif
397 }
398
399 if (!done && waitpid(child_pid, &child_exit_status, WNOHANG) > 0)
400 done = 1;
401
402 } while (!done
403 || !(rb_isempty(&inbuf) || err_n_wpty >= MAXRETR)
404 || !(rb_isempty(&outbuf) || err_n_stdout >= MAXRETR));
405
406 #ifdef DEBUG
407 fprintf(stderr, "inbuf: %u bytes left, outbuf: %u bytes left\n", inbuf.count, outbuf.count);
408 fprintf(stderr, "err_n_rpty=%u, err_n_wpty=%u, err_n_stdin=%u, err_n_stdout=%u\n",
409 err_n_rpty, err_n_wpty, err_n_stdin, err_n_stdout);
410 #endif
411
412 if (WIFEXITED(child_exit_status))
413 exit(WEXITSTATUS(child_exit_status));
414 else if (WIFSIGNALED(child_exit_status))
415 exit(128 + WTERMSIG(child_exit_status));
416
417 exit(EXIT_FAILURE);
418 }
419