| /* -*- Mode: C -*- |
| * open_init_pty.c --- |
| * Author : Manoj Srivastava ( srivasta@glaurung.internal.golden-gryphon.com ) |
| * Created On : Fri Jan 14 10:48:28 2005 |
| * Created On Node : glaurung.internal.golden-gryphon.com |
| * Last Modified By : Manoj Srivastava |
| * Last Modified On : Thu Sep 15 00:57:00 2005 |
| * Last Machine Used: glaurung.internal.golden-gryphon.com |
| * Update Count : 92 |
| * Status : Unknown, Use with caution! |
| * HISTORY : |
| * Description : |
| * |
| * Distributed under the terms of the GNU General Public License v2 |
| * |
| * open_init_pty |
| * |
| * SYNOPSIS: |
| * |
| * This program allows a systems administrator to execute daemons |
| * which need to work in the initrc domain, and which need to have |
| * pty's as system_u:system_r:initrc_t |
| * |
| * USAGE: |
| * |
| * * arch-tag: a5583d39-72b9-4cdf-ba1b-5678ea4cbe20 |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <signal.h> |
| #include <errno.h> |
| |
| #include <sysexits.h> |
| |
| #include <pty.h> /* for forkpty */ |
| #include <termios.h> |
| #include <fcntl.h> |
| |
| #include <sys/select.h> |
| #include <sys/wait.h> |
| |
| |
| #define MAXRETR 3 /* The max number of IO retries on a fd */ |
| #define BUFSIZE 2048 /* The ring buffer size */ |
| |
| static struct termios saved_termios; |
| static int saved_fd = -1; |
| static enum { RESET, RAW, CBREAK } tty_state = RESET; |
| |
| static int tty_semi_raw(int fd) |
| { |
| struct termios buf; |
| |
| if (tty_state == RESET) { |
| if (tcgetattr(fd, &saved_termios) < 0) { |
| return -1; |
| } |
| } |
| |
| buf = saved_termios; |
| /* |
| * echo off, canonical mode off, extended input processing off, |
| * signal chars off |
| */ |
| buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); |
| /* |
| * no SIGINT on break, CR-to-NL off, input parity check off, do not |
| * strip 8th bit on input,output flow control off |
| */ |
| buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); |
| /* Clear size bits, parity checking off */ |
| buf.c_cflag &= ~(CSIZE | PARENB); |
| /* set 8 bits/char */ |
| buf.c_cflag |= CS8; |
| /* Output processing off |
| buf.c_oflag &= ~(OPOST); */ |
| |
| buf.c_cc[VMIN] = 1; /* one byte at a time, no timer */ |
| buf.c_cc[VTIME] = 0; |
| if (tcsetattr(fd, TCSANOW, &buf) < 0) { |
| return -1; |
| } /* end of if(tcsetattr(fileno(stdin), TCSANOW, &buf) < 0) */ |
| tty_state = RAW; |
| saved_fd = fd; |
| return 0; |
| } |
| |
| static void tty_atexit(void) |
| { |
| if (tty_state != CBREAK && tty_state != RAW) { |
| return; |
| } |
| |
| if (tcsetattr(saved_fd, TCSANOW, &saved_termios) < 0) { |
| return; |
| } |
| tty_state = RESET; |
| return; |
| } |
| |
| |
| /* The simple ring buffer */ |
| struct ring_buffer { |
| char *buf; /* pointer to buffer memory */ |
| char *wptr; |
| char *rptr; |
| size_t size; /* the number of bytes allocated for buf */ |
| size_t count; |
| }; |
| |
| static void rb_init(struct ring_buffer *b, char *buf, size_t size) |
| { |
| b->buf = b->wptr = b->rptr = buf; |
| b->size = size; |
| b->count = 0; |
| } |
| |
| static int rb_isempty(struct ring_buffer *b) |
| { |
| return b->count == 0; |
| } |
| |
| /* return the unused space size in the buffer */ |
| static size_t rb_space(struct ring_buffer *b) |
| { |
| if (b->rptr > b->wptr) |
| return b->rptr - b->wptr; |
| |
| if (b->rptr < b->wptr || b->count == 0) |
| return b->buf + b->size - b->wptr; |
| |
| return 0; /* should not hit this */ |
| } |
| |
| /* return the used space in the buffer */ |
| static size_t rb_chunk_size(struct ring_buffer *b) |
| { |
| if (b->rptr < b->wptr) |
| return b->wptr - b->rptr; |
| |
| if (b->rptr > b->wptr || b->count > 0) |
| return b->buf + b->size - b->rptr; |
| |
| return 0; /* should not hit this */ |
| } |
| |
| /* read from fd and write to buffer memory */ |
| static ssize_t rb_read(struct ring_buffer *b, int fd) |
| { |
| ssize_t n = read(fd, b->wptr, rb_space(b)); |
| if (n <= 0) |
| return n; |
| |
| b->wptr += n; |
| b->count += n; |
| if (b->buf + b->size <= b->wptr) |
| b->wptr = b->buf; |
| |
| return n; |
| } |
| |
| static ssize_t rb_write(struct ring_buffer *b, int fd) |
| { |
| ssize_t n = write(fd, b->rptr, rb_chunk_size(b)); |
| if (n <= 0) |
| return n; |
| |
| b->rptr += n; |
| b->count -= n; |
| if (b->buf + b->size <= b->rptr) |
| b->rptr = b->buf; |
| |
| return n; |
| } |
| |
| static void setfd_nonblock(int fd) |
| { |
| int fsflags = fcntl(fd, F_GETFL); |
| |
| if (fsflags < 0) { |
| fprintf(stderr, "fcntl(%d, F_GETFL): %s\n", fd, strerror(errno)); |
| exit(EX_IOERR); |
| } |
| |
| if (fcntl(fd, F_SETFL, fsflags | O_NONBLOCK) < 0) { |
| fprintf(stderr, "fcntl(%d, F_SETFL, ... | O_NONBLOCK): %s\n", fd, strerror(errno)); |
| exit(EX_IOERR); |
| } |
| } |
| |
| static void sigchld_handler(int asig __attribute__ ((unused))) |
| { |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| pid_t child_pid; |
| int child_exit_status; |
| struct termios tty_attr; |
| struct winsize window_size; |
| int pty_master; |
| |
| /* for select */ |
| fd_set readfds; |
| fd_set writefds; |
| |
| unsigned err_n_rpty = 0; |
| unsigned err_n_wpty = 0; |
| unsigned err_n_stdin = 0; |
| unsigned err_n_stdout = 0; |
| |
| int done = 0; |
| |
| /* the ring buffers */ |
| char inbuf_mem[BUFSIZE]; |
| char outbuf_mem[BUFSIZE]; |
| struct ring_buffer inbuf; |
| struct ring_buffer outbuf; |
| rb_init(&inbuf, inbuf_mem, sizeof(inbuf_mem)); |
| rb_init(&outbuf, outbuf_mem, sizeof(outbuf_mem)); |
| |
| if (argc == 1) { |
| printf("usage: %s PROGRAM [ARGS]...\n", argv[0]); |
| exit(1); |
| } |
| |
| /* We need I/O calls to fail with EINTR on SIGCHLD... */ |
| if (signal(SIGCHLD, sigchld_handler) == SIG_ERR) { |
| perror("signal(SIGCHLD,...)"); |
| exit(EX_OSERR); |
| } |
| |
| if (isatty(STDIN_FILENO)) { |
| /* get terminal parameters associated with stdout */ |
| if (tcgetattr(STDOUT_FILENO, &tty_attr) < 0) { |
| perror("tcgetattr(stdout,...)"); |
| exit(EX_OSERR); |
| } |
| |
| /* get window size */ |
| if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &window_size) < 0) { |
| perror("ioctl(stdout,...)"); |
| exit(1); |
| } |
| |
| child_pid = forkpty(&pty_master, NULL, &tty_attr, &window_size); |
| } else { /* not interactive */ |
| child_pid = forkpty(&pty_master, NULL, NULL, NULL); |
| } |
| |
| if (child_pid < 0) { |
| perror("forkpty()"); |
| exit(EX_OSERR); |
| } |
| if (child_pid == 0) { /* in the child */ |
| struct termios s_tty_attr; |
| if (tcgetattr(STDIN_FILENO, &s_tty_attr)) { |
| perror("tcgetattr(stdin,...)"); |
| exit(EXIT_FAILURE); |
| } |
| /* Turn off echo */ |
| s_tty_attr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); |
| /* Also turn of NL to CR?LF on output */ |
| s_tty_attr.c_oflag &= ~(ONLCR); |
| if (tcsetattr(STDIN_FILENO, TCSANOW, &s_tty_attr)) { |
| perror("tcsetattr(stdin,...)"); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (execvp(argv[1], argv + 1)) { |
| perror("execvp()"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| /* Non blocking mode for all file descriptors. */ |
| setfd_nonblock(pty_master); |
| setfd_nonblock(STDIN_FILENO); |
| setfd_nonblock(STDOUT_FILENO); |
| |
| if (isatty(STDIN_FILENO)) { |
| if (tty_semi_raw(STDIN_FILENO) < 0) { |
| perror("tty_semi_raw(stdin)"); |
| } |
| if (atexit(tty_atexit) < 0) { |
| perror("atexit()"); |
| } |
| } |
| |
| do { |
| /* Accept events only on fds, that we can handle now. */ |
| int do_select = 0; |
| FD_ZERO(&readfds); |
| FD_ZERO(&writefds); |
| |
| if (rb_space(&outbuf) > 0 && err_n_rpty < MAXRETR) { |
| FD_SET(pty_master, &readfds); |
| do_select = 1; |
| } |
| |
| if (!rb_isempty(&inbuf) && err_n_wpty < MAXRETR) { |
| FD_SET(pty_master, &writefds); |
| do_select = 1; |
| } |
| |
| if (rb_space(&inbuf) > 0 && err_n_stdin < MAXRETR) { |
| FD_SET(STDIN_FILENO, &readfds); |
| do_select = 1; |
| } |
| |
| if (!rb_isempty(&outbuf) && err_n_stdout < MAXRETR) { |
| FD_SET(STDOUT_FILENO, &writefds); |
| do_select = 1; |
| } |
| |
| if (!do_select) { |
| #ifdef DEBUG |
| fprintf(stderr, "No I/O job for us, calling waitpid()...\n"); |
| #endif |
| while (waitpid(child_pid, &child_exit_status, 0) < 0) |
| { |
| /* nothing */ |
| } |
| break; |
| } |
| |
| int select_rc = select(pty_master + 1, &readfds, &writefds, NULL, NULL); |
| if (select_rc < 0) { |
| perror("select()"); |
| exit(EX_IOERR); |
| } |
| #ifdef DEBUG |
| fprintf(stderr, "select() returned %d\n", select_rc); |
| #endif |
| |
| if (FD_ISSET(STDOUT_FILENO, &writefds)) { |
| #ifdef DEBUG |
| fprintf(stderr, "stdout can be written\n"); |
| #endif |
| ssize_t n = rb_write(&outbuf, STDOUT_FILENO); |
| if (n <= 0 && n != EINTR && n != EAGAIN) |
| err_n_stdout++; |
| #ifdef DEBUG |
| if (n >= 0) |
| fprintf(stderr, "%d bytes written into stdout\n", n); |
| else |
| perror("write(stdout,...)"); |
| #endif |
| } |
| |
| if (FD_ISSET(pty_master, &writefds)) { |
| #ifdef DEBUG |
| fprintf(stderr, "pty_master can be written\n"); |
| #endif |
| ssize_t n = rb_write(&inbuf, pty_master); |
| if (n <= 0 && n != EINTR && n != EAGAIN) |
| err_n_wpty++; |
| #ifdef DEBUG |
| if (n >= 0) |
| fprintf(stderr, "%d bytes written into pty_master\n", n); |
| else |
| perror("write(pty_master,...)"); |
| #endif |
| } |
| |
| if (FD_ISSET(STDIN_FILENO, &readfds)) { |
| #ifdef DEBUG |
| fprintf(stderr, "stdin can be read\n"); |
| #endif |
| ssize_t n = rb_read(&inbuf, STDIN_FILENO); |
| if (n <= 0 && n != EINTR && n != EAGAIN) |
| err_n_stdin++; |
| #ifdef DEBUG |
| if (n >= 0) |
| fprintf(stderr, "%d bytes read from stdin\n", n); |
| else |
| perror("read(stdin,...)"); |
| #endif |
| } |
| |
| if (FD_ISSET(pty_master, &readfds)) { |
| #ifdef DEBUG |
| fprintf(stderr, "pty_master can be read\n"); |
| #endif |
| ssize_t n = rb_read(&outbuf, pty_master); |
| if (n <= 0 && n != EINTR && n != EAGAIN) |
| err_n_rpty++; |
| #ifdef DEBUG |
| if (n >= 0) |
| fprintf(stderr, "%d bytes read from pty_master\n", n); |
| else |
| perror("read(pty_master,...)"); |
| #endif |
| } |
| |
| if (!done && waitpid(child_pid, &child_exit_status, WNOHANG) > 0) |
| done = 1; |
| |
| } while (!done |
| || !(rb_isempty(&inbuf) || err_n_wpty >= MAXRETR) |
| || !(rb_isempty(&outbuf) || err_n_stdout >= MAXRETR)); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "inbuf: %u bytes left, outbuf: %u bytes left\n", inbuf.count, outbuf.count); |
| fprintf(stderr, "err_n_rpty=%u, err_n_wpty=%u, err_n_stdin=%u, err_n_stdout=%u\n", |
| err_n_rpty, err_n_wpty, err_n_stdin, err_n_stdout); |
| #endif |
| |
| if (WIFEXITED(child_exit_status)) |
| exit(WEXITSTATUS(child_exit_status)); |
| else if (WIFSIGNALED(child_exit_status)) |
| exit(128 + WTERMSIG(child_exit_status)); |
| |
| exit(EXIT_FAILURE); |
| } |