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 sigchld_handler(int asig __attribute__ ((unused)))
    195 {
    196 }
    197 
    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 		errno = 0;
    331 		int select_rc = select(pty_master + 1, &readfds, &writefds, NULL, NULL);
    332 		if (select_rc < 0 && errno != EINTR) {
    333 			perror("select()");
    334 			exit(EX_IOERR);
    335 		}
    336 #ifdef DEBUG
    337 		fprintf(stderr, "select() returned %d\n", select_rc);
    338 #endif
    339 
    340 		if (FD_ISSET(STDOUT_FILENO, &writefds)) {
    341 #ifdef DEBUG
    342 			fprintf(stderr, "stdout can be written\n");
    343 #endif
    344 			ssize_t n = rb_write(&outbuf, STDOUT_FILENO);
    345 			if (n <= 0 && n != EINTR && n != EAGAIN)
    346 				err_n_stdout++;
    347 #ifdef DEBUG
    348 			if (n >= 0)
    349 				fprintf(stderr, "%d bytes written into stdout\n", n);
    350 			else
    351 				perror("write(stdout,...)");
    352 #endif
    353 		}
    354 
    355 		if (FD_ISSET(pty_master, &writefds)) {
    356 #ifdef DEBUG
    357 			fprintf(stderr, "pty_master can be written\n");
    358 #endif
    359 			ssize_t n = rb_write(&inbuf, pty_master);
    360 			if (n <= 0 && n != EINTR && n != EAGAIN)
    361 				err_n_wpty++;
    362 #ifdef DEBUG
    363 			if (n >= 0)
    364 				fprintf(stderr, "%d bytes written into pty_master\n", n);
    365 			else
    366 				perror("write(pty_master,...)");
    367 #endif
    368 		}
    369 
    370 		if (FD_ISSET(STDIN_FILENO, &readfds)) {
    371 #ifdef DEBUG
    372 			fprintf(stderr, "stdin can be read\n");
    373 #endif
    374 			ssize_t n = rb_read(&inbuf, STDIN_FILENO);
    375 			if (n <= 0 && n != EINTR && n != EAGAIN)
    376 				err_n_stdin++;
    377 #ifdef DEBUG
    378 			if (n >= 0)
    379 				fprintf(stderr, "%d bytes read from stdin\n", n);
    380 			else
    381 				perror("read(stdin,...)");
    382 #endif
    383 		}
    384 
    385 		if (FD_ISSET(pty_master, &readfds)) {
    386 #ifdef DEBUG
    387 			fprintf(stderr, "pty_master can be read\n");
    388 #endif
    389 			ssize_t n = rb_read(&outbuf, pty_master);
    390 			if (n <= 0 && n != EINTR && n != EAGAIN)
    391 				err_n_rpty++;
    392 #ifdef DEBUG
    393 			if (n >= 0)
    394 				fprintf(stderr, "%d bytes read from pty_master\n", n);
    395 			else
    396 				perror("read(pty_master,...)");
    397 #endif
    398 		}
    399 
    400 		if (!done && waitpid(child_pid, &child_exit_status, WNOHANG) > 0)
    401 			done = 1;
    402 
    403 	} while (!done
    404 		|| !(rb_isempty(&inbuf) || err_n_wpty >= MAXRETR)
    405 		|| !(rb_isempty(&outbuf) || err_n_stdout >= MAXRETR));
    406 
    407 #ifdef DEBUG
    408 	fprintf(stderr, "inbuf: %u bytes left, outbuf: %u bytes left\n", inbuf.count, outbuf.count);
    409 	fprintf(stderr, "err_n_rpty=%u, err_n_wpty=%u, err_n_stdin=%u, err_n_stdout=%u\n",
    410 		err_n_rpty, err_n_wpty, err_n_stdin, err_n_stdout);
    411 #endif
    412 
    413 	if (WIFEXITED(child_exit_status))
    414 		exit(WEXITSTATUS(child_exit_status));
    415 	else if (WIFSIGNALED(child_exit_status))
    416 		exit(128 + WTERMSIG(child_exit_status));
    417 
    418 	exit(EXIT_FAILURE);
    419 }
    420