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