rjc@athena:/tmp$ gcc open_init_pty.c -lutil
rjc@athena:/tmp$ ./a.out bash
rjc@athena:/tmp$   PID TTY          TIME CMD
 4250 pts/6    00:00:00 bash
 4253 pts/6    00:00:00 ps
rjc@athena:/tmp$   PID TTY          TIME CMD
 4250 pts/6    00:00:00 bash
 4254 pts/6    00:00:00 ps

The latest version of that code doesn't work correctly, it doesn't handle 
local echo.  From the above you can see the result of running bash and typing 
"ps" at the command-line.

Also I've attached a C++ version of the code in question that uses a class for 
the ring buffer.  It makes the code more readable and maintainable.

In regard to sending the code upstream, this has always been my thing for 
Debian.  As Manoj has disappeared that makes me the real upstream for this 
program.  I expect that the people who release the upstream policycoreutils 
archives will be happy to take whatever I think is good as they aren't really 
interested in it.

-- 
My Main Blog         http://etbe.coker.com.au/
My Documents Blog    http://doc.coker.com.au/
/*                               -*- Mode: C -*- 
 * open_init_pty.c --- 
 * Author           : Manoj Srivastava ( [email protected] ) 
 * 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 openpty and forkpty */
#include <utmp.h>    /* for login_tty */
#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;
  }
  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 */
class ring_buffer
{
public:
  ring_buffer(char *buf, size_t size)
  {
    m_buf = m_wptr = m_rptr = buf;
    m_size = size;
    m_count = 0;
  }

  size_t get_count() { return m_count; }
  int isempty() { return m_count == 0; }

  // return the unused space size in the buffer
  size_t space()
  {
    if(m_rptr > m_wptr)
      return m_rptr - m_wptr;
    if(m_rptr < m_wptr || m_count == 0)
      return m_buf + m_size - m_wptr;
    return 0; // should not hit this
  }

  // return the used space in the buffer
  size_t chunk_size()
  {
    if(m_rptr < m_wptr)
      return m_wptr - m_rptr;
    if(m_rptr > m_wptr || m_count > 0)
      return m_buf + m_size - m_rptr;
    return 0; // should not hit this
  }

  // read from fd and write to buffer memory
  ssize_t rb_read(int fd)
  {
    ssize_t n = read(fd, m_wptr, space());
    if(n <= 0)
      return n;
    m_wptr += n;
    m_count += n;
    if(m_buf + m_size <= m_wptr)
      m_wptr = m_buf;
    return n;
  }

  ssize_t rb_write(int fd)
  {
    ssize_t n = write(fd, m_rptr, chunk_size());
    if(n <= 0)
      return n;
    m_rptr += n;
    m_count -= n;
    if(m_buf + m_size <= m_rptr)
      m_rptr = m_buf;
    return n;
  }

private:
    char *m_buf; /* pointer to buffer memory */
    char *m_wptr;
    char *m_rptr;
    size_t m_size; /* the number of bytes allocated for buf */
    size_t m_count;
};

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(STDIN_FILENO, 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;
  char inbuf_mem[BUFSIZE];
  char outbuf_mem[BUFSIZE];
  ring_buffer inbuf(inbuf_mem, sizeof(inbuf_mem));
  ring_buffer outbuf(outbuf_mem, sizeof(outbuf_mem));

  if (argc == 1) {
    printf("usage: %s PROGRAM [ARGS]...\n", argv[0]);
    exit(1);
  }

  /* Wee 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);
    }

    /* end of if(tcsetattr(&tty_attr)) */
    /* 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);
  } /* end of if(isatty(STDIN_FILENO)) */
  else {      /* not interactive */
    child_pid = forkpty(&pty_master, NULL, NULL, NULL);
  }

  if (child_pid < 0) {
    perror("forkpty()");
    fflush(stdout);
    fflush(stderr);
    exit(EX_OSERR);
  }      /* end of if(child_pid < 0) */
  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()");
    }
  }

  /* for select()... */
  fd_set readfds;
  fd_set writefds;
  fd_set exceptfds;
  FD_ZERO(&readfds);
  FD_ZERO(&writefds);
  FD_ZERO(&exceptfds);

  unsigned err_n_rpty = 0;
  unsigned err_n_wpty = 0;
  unsigned err_n_stdin = 0;
  unsigned err_n_stdout = 0;

  int done = 0;

  do {
    /* Accept events only on fds, that we can handle now. */
    int do_select = 0;

    if ( outbuf.space() > 0  &&  err_n_rpty < MAXRETR ) {
      FD_SET(pty_master, &readfds);
      do_select = 1;
    } else
      FD_CLR(pty_master, &readfds);

    if ( ! inbuf.isempty()  &&  err_n_wpty < MAXRETR ) {
      FD_SET(pty_master, &writefds);
      do_select = 1;
    } else
      FD_CLR(pty_master, &writefds);
        
    if ( inbuf.space() > 0  &&  err_n_stdin < MAXRETR ) {
      FD_SET(STDIN_FILENO, &readfds);
      do_select = 1;
    } else
      FD_CLR(STDIN_FILENO, &readfds);
        
    if ( ! outbuf.isempty()  &&  err_n_stdout < MAXRETR ) {
      FD_SET(STDOUT_FILENO, &writefds);
      do_select = 1;
    } else
      FD_CLR(STDOUT_FILENO, &writefds);

    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 )
        ;
      break;
    }

    int select_rc = select(pty_master + 1,
        &readfds, &writefds, &exceptfds, 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 = outbuf.rb_write(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 = inbuf.rb_write(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 = inbuf.rb_read(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 = outbuf.rb_read(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 )
        if ( waitpid(child_pid, &child_exit_status, WNOHANG) > 0 )
        done = 1;

  } while ( !done
      || !(inbuf.isempty() || err_n_wpty >= MAXRETR)
        || !(outbuf.isempty() || err_n_stdout >= MAXRETR) );

#ifdef DEBUG
  fprintf(stderr, "inbuf: %u bytes left, outbuf: %u bytes left\n",
      inbuf.get_count(), outbuf.get_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));
  exit(EXIT_FAILURE);
}        /* end of main() */

/*
 * vim:ts=4:
 */

Reply via email to