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