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 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