• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
setfd_block(int fd)194 static void setfd_block(int fd)
195 {
196 	int fsflags = fcntl(fd, F_GETFL);
197 
198 	if (fsflags < 0) {
199 		fprintf(stderr, "fcntl(%d, F_GETFL): %s\n", fd, strerror(errno));
200 		exit(EX_IOERR);
201 	}
202 
203 	if (fcntl(fd, F_SETFL, fsflags & ~O_NONBLOCK) < 0) {
204 		fprintf(stderr, "fcntl(%d, F_SETFL, ... & ~O_NONBLOCK): %s\n", fd, strerror(errno));
205 		exit(EX_IOERR);
206 	}
207 }
208 
setfd_atexit(void)209 static void setfd_atexit(void)
210 {
211 	setfd_block(STDIN_FILENO);
212 	setfd_block(STDOUT_FILENO);
213 	return;
214 }
215 
sigchld_handler(int asig)216 static void sigchld_handler(int asig __attribute__ ((unused)))
217 {
218 }
219 
main(int argc,char * argv[])220 int main(int argc, char *argv[])
221 {
222 	pid_t child_pid;
223 	int child_exit_status;
224 	struct termios tty_attr;
225 	struct winsize window_size;
226 	int pty_master;
227 
228 	/* for select */
229 	fd_set readfds;
230 	fd_set writefds;
231 
232 	unsigned err_n_rpty = 0;
233 	unsigned err_n_wpty = 0;
234 	unsigned err_n_stdin = 0;
235 	unsigned err_n_stdout = 0;
236 
237 	int done = 0;
238 
239 	/* the ring buffers */
240 	char inbuf_mem[BUFSIZE];
241 	char outbuf_mem[BUFSIZE];
242 	struct ring_buffer inbuf;
243 	struct ring_buffer outbuf;
244 	rb_init(&inbuf, inbuf_mem, sizeof(inbuf_mem));
245 	rb_init(&outbuf, outbuf_mem, sizeof(outbuf_mem));
246 
247 	if (argc == 1) {
248 		printf("usage: %s PROGRAM [ARGS]...\n", argv[0]);
249 		exit(1);
250 	}
251 
252 	/* We need I/O calls to fail with EINTR on SIGCHLD... */
253 	if (signal(SIGCHLD, sigchld_handler) == SIG_ERR) {
254 		perror("signal(SIGCHLD,...)");
255 		exit(EX_OSERR);
256 	}
257 
258 	if (isatty(STDIN_FILENO)) {
259 		/* get terminal parameters associated with stdout */
260 		if (tcgetattr(STDOUT_FILENO, &tty_attr) < 0) {
261 			perror("tcgetattr(stdout,...)");
262 			exit(EX_OSERR);
263 		}
264 
265 		/* get window size */
266 		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window_size) < 0) {
267 			perror("ioctl(stdout,...)");
268 			exit(1);
269 		}
270 
271 		child_pid = forkpty(&pty_master, NULL, &tty_attr, &window_size);
272 	} else { /* not interactive */
273 		child_pid = forkpty(&pty_master, NULL, NULL, NULL);
274 	}
275 
276 	if (child_pid < 0) {
277 		perror("forkpty()");
278 		exit(EX_OSERR);
279 	}
280 	if (child_pid == 0) { /* in the child */
281 		struct termios s_tty_attr;
282 		if (tcgetattr(STDIN_FILENO, &s_tty_attr)) {
283 			perror("tcgetattr(stdin,...)");
284 			exit(EXIT_FAILURE);
285 		}
286 		/* Turn off echo */
287 		s_tty_attr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
288 		/* Also turn of NL to CR?LF on output */
289 		s_tty_attr.c_oflag &= ~(ONLCR);
290 		if (tcsetattr(STDIN_FILENO, TCSANOW, &s_tty_attr)) {
291 			perror("tcsetattr(stdin,...)");
292 			exit(EXIT_FAILURE);
293 		}
294 
295 		if (execvp(argv[1], argv + 1)) {
296 			perror("execvp()");
297 			exit(EXIT_FAILURE);
298 		}
299 	}
300 
301 	/* Non blocking mode for all file descriptors. */
302 	setfd_nonblock(pty_master);
303 	setfd_nonblock(STDIN_FILENO);
304 	setfd_nonblock(STDOUT_FILENO);
305 	if (atexit(setfd_atexit) < 0) {
306 		perror("atexit()");
307 		exit(EXIT_FAILURE);
308 	}
309 
310 	if (isatty(STDIN_FILENO)) {
311 		if (tty_semi_raw(STDIN_FILENO) < 0) {
312 			perror("tty_semi_raw(stdin)");
313 		}
314 		if (atexit(tty_atexit) < 0) {
315 			perror("atexit()");
316 		}
317 	}
318 
319 	do {
320 		/* Accept events only on fds, that we can handle now. */
321 		int do_select = 0;
322 		FD_ZERO(&readfds);
323 		FD_ZERO(&writefds);
324 
325 		if (rb_space(&outbuf) > 0 && err_n_rpty < MAXRETR) {
326 			FD_SET(pty_master, &readfds);
327 			do_select = 1;
328 		}
329 
330 		if (!rb_isempty(&inbuf) && err_n_wpty < MAXRETR) {
331 			FD_SET(pty_master, &writefds);
332 			do_select = 1;
333 		}
334 
335 		if (rb_space(&inbuf) > 0 && err_n_stdin < MAXRETR) {
336 			FD_SET(STDIN_FILENO, &readfds);
337 			do_select = 1;
338 		}
339 
340 		if (!rb_isempty(&outbuf) && err_n_stdout < MAXRETR) {
341 			FD_SET(STDOUT_FILENO, &writefds);
342 			do_select = 1;
343 		}
344 
345 		if (!do_select) {
346 #ifdef DEBUG
347 			fprintf(stderr, "No I/O job for us, calling waitpid()...\n");
348 #endif
349 			while (waitpid(child_pid, &child_exit_status, 0) < 0)
350 			{
351 				/* nothing */
352 			}
353 			break;
354 		}
355 
356 		errno = 0;
357 		int select_rc = select(pty_master + 1, &readfds, &writefds, NULL, NULL);
358 		if (select_rc < 0 && errno != EINTR) {
359 			perror("select()");
360 			exit(EX_IOERR);
361 		}
362 #ifdef DEBUG
363 		fprintf(stderr, "select() returned %d\n", select_rc);
364 #endif
365 
366 		if (FD_ISSET(STDOUT_FILENO, &writefds)) {
367 #ifdef DEBUG
368 			fprintf(stderr, "stdout can be written\n");
369 #endif
370 			ssize_t n = rb_write(&outbuf, STDOUT_FILENO);
371 			if (n <= 0 && n != EINTR && n != EAGAIN)
372 				err_n_stdout++;
373 #ifdef DEBUG
374 			if (n >= 0)
375 				fprintf(stderr, "%d bytes written into stdout\n", n);
376 			else
377 				perror("write(stdout,...)");
378 #endif
379 		}
380 
381 		if (FD_ISSET(pty_master, &writefds)) {
382 #ifdef DEBUG
383 			fprintf(stderr, "pty_master can be written\n");
384 #endif
385 			ssize_t n = rb_write(&inbuf, pty_master);
386 			if (n <= 0 && n != EINTR && n != EAGAIN)
387 				err_n_wpty++;
388 #ifdef DEBUG
389 			if (n >= 0)
390 				fprintf(stderr, "%d bytes written into pty_master\n", n);
391 			else
392 				perror("write(pty_master,...)");
393 #endif
394 		}
395 
396 		if (FD_ISSET(STDIN_FILENO, &readfds)) {
397 #ifdef DEBUG
398 			fprintf(stderr, "stdin can be read\n");
399 #endif
400 			ssize_t n = rb_read(&inbuf, STDIN_FILENO);
401 			if (n <= 0 && n != EINTR && n != EAGAIN)
402 				err_n_stdin++;
403 #ifdef DEBUG
404 			if (n >= 0)
405 				fprintf(stderr, "%d bytes read from stdin\n", n);
406 			else
407 				perror("read(stdin,...)");
408 #endif
409 		}
410 
411 		if (FD_ISSET(pty_master, &readfds)) {
412 #ifdef DEBUG
413 			fprintf(stderr, "pty_master can be read\n");
414 #endif
415 			ssize_t n = rb_read(&outbuf, pty_master);
416 			if (n <= 0 && n != EINTR && n != EAGAIN)
417 				err_n_rpty++;
418 #ifdef DEBUG
419 			if (n >= 0)
420 				fprintf(stderr, "%d bytes read from pty_master\n", n);
421 			else
422 				perror("read(pty_master,...)");
423 #endif
424 		}
425 
426 		if (!done && waitpid(child_pid, &child_exit_status, WNOHANG) > 0)
427 			done = 1;
428 
429 	} while (!done
430 		|| !(rb_isempty(&inbuf) || err_n_wpty >= MAXRETR)
431 		|| !(rb_isempty(&outbuf) || err_n_stdout >= MAXRETR));
432 
433 #ifdef DEBUG
434 	fprintf(stderr, "inbuf: %u bytes left, outbuf: %u bytes left\n", inbuf.count, outbuf.count);
435 	fprintf(stderr, "err_n_rpty=%u, err_n_wpty=%u, err_n_stdin=%u, err_n_stdout=%u\n",
436 		err_n_rpty, err_n_wpty, err_n_stdin, err_n_stdout);
437 #endif
438 
439 	if (WIFEXITED(child_exit_status))
440 		exit(WEXITSTATUS(child_exit_status));
441 	else if (WIFSIGNALED(child_exit_status))
442 		exit(128 + WTERMSIG(child_exit_status));
443 
444 	exit(EXIT_FAILURE);
445 }
446