text: /*
   * process.cc
   *
   * Written 2003-2021 by Autumn Lamonte ⚧ Trans Liberation Now
   *
   * To the extent possible under law, the author(s) have dedicated all
   * copyright and related and neighboring rights to this software to the
   * public domain worldwide. This software is distributed without any
   * warranty.
   *
   * You should have received a copy of the CC0 Public Domain Dedication along
   * with this software. If not, see
   * <http://creativecommons.org/publicdomain/zero/1.0/>.
   */
  
  #include <string.h>	// memcpy()
  #include "process.hh"
  
  using namespace qodem;
  
  // Process -------------------------------------------------------------------
  
  // Constructor
  Process::Process() : inbuf_n(0), outbuf_n(0) {}
  
  // Destructor
  Process::~Process() {}
  
  // Append bytes to outbuf
  int Process::appendBytes(const char * data, const int data_n) {
      int n = sizeof(outbuf) - outbuf_n;
      if (n > data_n) {
          n = data_n;
      }
      memcpy(outbuf + outbuf_n, data, n);
      outbuf_n += n;
      return n;
  }
  
  // Discard bytes from beginning of inbuf
  void Process::discardBytes(const size_t count) {
      if (count > 0) {
          memmove(inbuf, inbuf + count, count);
      }
      inbuf_n -= count;
  }
  
  // UNIXProcess ---------------------------------------------------------------
  
  #include <signal.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <sys/wait.h>
  #include <poll.h>
  #include <ctype.h>
  #include <string.h>
  #include <stdlib.h>
  #include <stdio.h>
  #include "memory.hh"
  
  // Use forkpty()
  #ifdef __APPLE__
  #include <util.h>
  #else
  #include <pty.h>
  #endif
  
  #ifdef __linux
  #include <sys/ioctl.h>
  #endif /* __linux */
  
  /*
   * Make the file/socket non-blocking
   */
  static bool set_nonblock(const int fd) {
      int flags;
  
      flags = fcntl(fd, F_GETFL);
      if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
          /* Error */
          return false;
      }
  
      /* All OK */
      return true;
  } /* ---------------------------------------------------------------------- */
  
  /*
   * tokenize_command - Separate command into arguments.
   */
  static char ** tokenize_command(const char * argv) {
      int i, j;
      int argv_start;
      char ** new_argv;
      char * old_argv;
      int n;
  
      old_argv = Xstrdup(argv, __FILE__, __LINE__);
      n = strlen(argv);
  
      /* Trim beginning whitespace */
      argv_start = 0;
      while (isspace(argv[argv_start])) {
          argv_start++;
      }
  
      for (i=0, j=argv_start; j<n; i++) {
          while (!isspace(argv[j]) && (j<n)) {
              j++;
          }
          while (isspace(argv[j]) && (j<n)) {
              j++;
          }
      }
  
      /* i is the # of tokens */
      new_argv = (char **)Xmalloc(sizeof(char *) * (i+1), __FILE__, __LINE__);
      for (i=0, j=argv_start; j<n; i++) {
          new_argv[i] = old_argv + j;
          while (!isspace(old_argv[j]) && (j<n)) {
              j++;
          }
          while (isspace(old_argv[j]) && (j<n)) {
              old_argv[j] = 0;
              j++;
          }
      }
  
      /*
       * Why can't I free this?  Because new_argv is pointers
       * into old_argv.  Freeing it kills the child process.
       */
      /* Xfree(old_argv, __FILE__, __LINE__); */
  
      new_argv[i] = NULL;
      return new_argv;
  } /* ---------------------------------------------------------------------- */
  
  
  // Spawn the child process
  void UNIXProcess::spawnChild(const char * commandLine, unsigned lines, unsigned columns) {
      // Fork and put the child on a new tty
      char ttyname_buffer[128];
      pid_t child_pid = forkpty(&q_child_tty_fd, ttyname_buffer, NULL, NULL);
      q_child_ttyname = Xstrdup(ttyname_buffer, __FILE__, __LINE__);
  
      if (child_pid == 0) {
          /*
           * Child process, will become the shell
           */
  
          // This just has to be long enough for LINES=blah and COLUMNS=blah
          char buffer[32];
  
          // Restore signal handlers
          signal(SIGPIPE, SIG_DFL);
  
          // Set terminal for bash
          // putenv("TERM=vt102");
          putenv((char *) "TERM=linux");
  
          // Set LINES and COLUMNS
          memset(buffer, 0, sizeof(buffer));
          snprintf(buffer, sizeof(buffer), "LINES=%u", lines);
          putenv(strdup(buffer));
          memset(buffer, 0, sizeof(buffer));
          snprintf(buffer, sizeof(buffer), "COLUMNS=%u", columns);
          putenv(strdup(buffer));
  
  #ifdef __linux
  
          /*
           * Some programs won't forward the environment variables anyway, so
           * let's tell the kernel to reset cols and rows and maybe that will
           * make it across.
           *
           * We use perror() here because it will make its way back to the
           * parent (I hope).  We don't have control of the terminal anymore.
           */
          struct winsize console_size;
          if (ioctl(STDIN_FILENO, TIOCGWINSZ, &console_size) < 0) {
              perror("ioctl(TIOCGWINSZ)");
          } else {
              console_size.ws_row = lines;
              console_size.ws_col = columns;
              if (ioctl(STDIN_FILENO, TIOCSWINSZ, &console_size) < 0) {
                  perror("ioctl(TIOCSWINSZ)");
              }
          }
  
  #endif // __linux
  
          /* Separate target into arguments */
          char ** target_argv = tokenize_command(commandLine);
          execvp(target_argv[0], target_argv);
      }
  
      /*
       * Parent process
       */
      q_child_pid = child_pid;
  
      /* Don't block the port */
      set_nonblock(q_child_tty_fd);
  
  }
  
  // Constructor
  UNIXProcess::UNIXProcess() : Process() {}
  
  // Destructor
  UNIXProcess::~UNIXProcess() {
      // Close child if it is still open
      closeChild();
  }
  
  // Close the child process
  void UNIXProcess::closeChild() {
      if (q_child_pid != -1) {
          /* Close pty */
          int status;
          close(q_child_tty_fd);
          q_child_tty_fd = -1;
          Xfree(q_child_ttyname, __FILE__, __LINE__);
          wait4(q_child_pid, &status, WNOHANG, NULL);
          q_child_pid = -1;
      }
  }
  
  
  // Process bytes between inbuf and outbuf
  int UNIXProcess::readBytes() {
  
      if (q_child_pid == -1) {
          return 0;
      }
  
      // Poll the child first
      struct pollfd pfd;
  
      pfd.fd = q_child_tty_fd;
      pfd.events = POLLIN;
  
      // Poll
      int rc = poll(&pfd, 1, 10);
      if (rc < 0) {
          /* Error */
      } else if (rc > 0) {
          // Read data from shell process into input side
          int n = sizeof(inbuf) - inbuf_n;
  
          if (n > 0) {
  
              /* Clear errno */
              errno = 0;
  
              int rc = ::read(q_child_tty_fd, inbuf + inbuf_n, n);
              if (rc < 0) {
                  if (errno == EIO) {
                      /* This is EOF */
                      rc = 0;
                  } else {
                      // TODO
                  }
              }
              if (rc == 0) {
                  closeChild();
                  return 0;
              }
              if (rc > 0) {
                  inbuf_n += rc;
              }
  
          } /* if (n > 0) */
      } // if (rc > 0)
  
      return rc;
  }
  
  // Write outbound bytes to the child process
  int UNIXProcess::writeBytes() {
      // Write to the output side
      int rc = 0;
      if (outbuf_n > 0) {
          rc = ::write(q_child_tty_fd, outbuf, outbuf_n);
          if (rc > 0) {
              memmove(outbuf, outbuf + rc, outbuf_n - rc);
              outbuf_n -= rc;
          }
      }
      return rc;
  }
  
  // Notify the child process of a window change event
  void UNIXProcess::sigwinch(unsigned lines, unsigned columns) {
  #ifdef __linux
      struct winsize console_size;
      if (ioctl(q_child_tty_fd, TIOCGWINSZ, &console_size) < 0) {
          perror("ioctl(TIOCGWINSZ)");
      } else {
          console_size.ws_row = lines;
          console_size.ws_col = columns;
          if (ioctl(q_child_tty_fd, TIOCSWINSZ, &console_size) < 0) {
              perror("ioctl(TIOCSWINSZ)");
          }
      }
      kill(q_child_pid, SIGWINCH);
  #endif // __linux
  }
  