Home | History | Annotate | Download | only in run_init
      1 /*                               -*- Mode: C -*-
      2  * open_init_pty.c ---
      3  * Author           : Manoj Srivastava ( srivasta (at) 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 
     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 
     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 
    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 
    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 */
    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 */
    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 */
    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 
    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 
    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 
    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 
    209 static void setfd_atexit(void)
    210 {
    211 	setfd_block(STDIN_FILENO);
    212 	setfd_block(STDOUT_FILENO);
    213 	return;
    214 }
    215 
    216 static void sigchld_handler(int asig __attribute__ ((unused)))
    217 {
    218 }
    219 
    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