text: /*
   * Copyright (c) 2017 Cisco and/or its affiliates.
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at:
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  #include <sys/socket.h>
  #include <sys/un.h>
  #include <sys/epoll.h>
  #include <sys/ioctl.h>
  #include <signal.h>
  #include <termios.h>
  #include <unistd.h>
  #include <string.h>
  
  #define DEBUG 0
  
  #if DEBUG
  #define TELCMDS
  #define TELOPTS
  #endif
  
  #include <arpa/telnet.h>
  
  #include <vppinfra/mem.h>
  #include <vppinfra/format.h>
  #include <vppinfra/socket.h>
  
  #define SOCKET_FILE "/run/vpp/cli.sock"
  
  volatile int window_resized = 0;
  struct termios orig_tio;
  
  static void
  send_ttype (clib_socket_t * s, int is_interactive)
  {
    char *term;
  
    term = is_interactive ? getenv ("TERM") : "vppctl";
    if (term == NULL)
      term = "dumb";
  
    clib_socket_tx_add_formatted (s, "%c%c%c" "%c%s" "%c%c",
  				IAC, SB, TELOPT_TTYPE, 0, term, IAC, SE);
    clib_socket_tx (s);
  }
  
  static void
  send_naws (clib_socket_t * s)
  {
    struct winsize ws;
  
    if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
      {
        clib_unix_warning ("ioctl(TIOCGWINSZ)");
        return;
      }
  
    clib_socket_tx_add_formatted (s, "%c%c%c" "%c%c%c%c" "%c%c",
  				IAC, SB, TELOPT_NAWS,
  				ws.ws_col >> 8, ws.ws_col & 0xff,
  				ws.ws_row >> 8, ws.ws_row & 0xff, IAC, SE);
    clib_socket_tx (s);
  }
  
  static void
  signal_handler_winch (int signum)
  {
    window_resized = 1;
  }
  
  static void
  signal_handler_term (int signum)
  {
    tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
  }
  
  static u8 *
  process_input (u8 * str, clib_socket_t * s, int is_interactive,
  	       int *sent_ttype)
  {
    int i = 0;
  
    while (i < vec_len (s->rx_buffer))
      {
        if (s->rx_buffer[i] == IAC)
  	{
  	  if (s->rx_buffer[i + 1] == SB)
  	    {
  	      u8 *sb = 0;
  	      char opt = s->rx_buffer[i + 2];
  	      i += 3;
  	      while (s->rx_buffer[i] != IAC)
  		vec_add1 (sb, s->rx_buffer[i++]);
  
  #if DEBUG
  	      clib_warning ("SB %s\n  %U", TELOPT (opt),
  			    format_hexdump, sb, vec_len (sb));
  #endif
  	      vec_free (sb);
  	      i += 2;
  	      if (opt == TELOPT_TTYPE)
  		{
  		  send_ttype (s, is_interactive);
  		  *sent_ttype = 1;
  		}
  	      else if (is_interactive && opt == TELOPT_NAWS)
  		send_naws (s);
  	    }
  	  else
  	    {
  #if DEBUG
  	      clib_warning ("IAC at %d, IAC %s %s", i,
  			    TELCMD (s->rx_buffer[i + 1]),
  			    TELOPT (s->rx_buffer[i + 2]));
  #endif
  	      i += 3;
  	    }
  	}
        else
  	vec_add1 (str, s->rx_buffer[i++]);
      }
    vec_reset_length (s->rx_buffer);
    return str;
  }
  
  
  int
  main (int argc, char *argv[])
  {
    clib_socket_t _s = { 0 }, *s = &_s;
    clib_error_t *error = 0;
    struct epoll_event event;
    struct sigaction sa;
    struct termios tio;
    int efd = -1;
    u8 *str = 0;
    u8 *cmd = 0;
    int do_quit = 0;
    int is_interactive = 0;
    int acked = 1;		/* counts messages from VPP; starts at 1 */
    int sent_ttype = 0;
  
  
    clib_mem_init (0, 64ULL << 10);
  
    /* process command line */
    argc--;
    argv++;
  
    if (argc > 1 && strcmp (argv[0], "-s") == 0)
      {
        s->config = argv[1];
        argc -= 2;
        argv += 2;
      }
    else
      s->config = SOCKET_FILE;
  
    while (argc--)
      cmd = format (cmd, "%s%c", (argv++)[0], argc ? ' ' : 0);
  
    s->flags = CLIB_SOCKET_F_IS_CLIENT;
  
    error = clib_socket_init (s);
    if (error)
      goto done;
  
    is_interactive = isatty (STDIN_FILENO) && cmd == 0;
  
    if (is_interactive)
      {
        /* Capture terminal resize events */
        memset (&sa, 0, sizeof (struct sigaction));
        sa.sa_handler = signal_handler_winch;
        if (sigaction (SIGWINCH, &sa, 0) < 0)
  	{
  	  error = clib_error_return_unix (0, "sigaction");
  	  goto done;
  	}
  
        /* Capture SIGTERM to reset tty settings */
        sa.sa_handler = signal_handler_term;
        if (sigaction (SIGTERM, &sa, 0) < 0)
  	{
  	  error = clib_error_return_unix (0, "sigaction");
  	  goto done;
  	}
  
        /* Save the original tty state so we can restore it later */
        if (tcgetattr (STDIN_FILENO, &orig_tio) < 0)
  	{
  	  error = clib_error_return_unix (0, "tcgetattr");
  	  goto done;
  	}
  
        /* Tweak the tty settings */
        tio = orig_tio;
        /* echo off, canonical mode off, ext'd input processing off */
        tio.c_lflag &= ~(ECHO | ICANON | IEXTEN);
        tio.c_cc[VMIN] = 1;	/* 1 byte at a time */
        tio.c_cc[VTIME] = 0;	/* no timer */
  
        if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &tio) < 0)
  	{
  	  error = clib_error_return_unix (0, "tcsetattr");
  	  goto done;
  	}
      }
  
    efd = epoll_create1 (0);
  
    /* register STDIN */
    event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
    event.data.fd = STDIN_FILENO;
    if (epoll_ctl (efd, EPOLL_CTL_ADD, STDIN_FILENO, &event) != 0)
      {
        /* ignore EPERM; it means stdin is something like /dev/null */
        if (errno != EPERM)
  	{
  	  error = clib_error_return_unix (0, "epoll_ctl[%d]", STDIN_FILENO);
  	  goto done;
  	}
      }
  
    /* register socket */
    event.events = EPOLLIN | EPOLLPRI | EPOLLERR;
    event.data.fd = s->fd;
    if (epoll_ctl (efd, EPOLL_CTL_ADD, s->fd, &event) != 0)
      {
        error = clib_error_return_unix (0, "epoll_ctl[%d]", s->fd);
        goto done;
      }
  
    while (1)
      {
        int n;
  
        if (window_resized)
  	{
  	  window_resized = 0;
  	  send_naws (s);
  	}
  
        if ((n = epoll_wait (efd, &event, 1, -1)) < 0)
  	{
  	  /* maybe we received signal */
  	  if (errno == EINTR)
  	    continue;
  
  	  error = clib_error_return_unix (0, "epoll_wait");
  	  goto done;
  	}
  
        if (n == 0)
  	continue;
  
        if (event.data.fd == STDIN_FILENO)
  	{
  	  int n;
  	  char c[100];
  
  	  if (!sent_ttype)
  	    continue;		/* not ready for this yet */
  
  	  n = read (STDIN_FILENO, c, sizeof (c));
  	  if (n > 0)
  	    {
  	      memcpy (clib_socket_tx_add (s, n), c, n);
  	      error = clib_socket_tx (s);
  	      if (error)
  		goto done;
  	    }
  	  else if (n < 0)
  	    clib_warning ("read rv=%d", n);
  	  else			/* EOF */
  	    do_quit = 1;
  	}
        else if (event.data.fd == s->fd)
  	{
  	  error = clib_socket_rx (s, 100);
  	  if (error)
  	    break;
  
  	  if (clib_socket_rx_end_of_file (s))
  	    break;
  
  	  str = process_input (str, s, is_interactive, &sent_ttype);
  
  	  if (vec_len (str) > 0)
  	    {
  	      int len = vec_len (str);
  	      u8 *p = str, *q = str;
  
  	      while (len)
  		{
  		  /* Search for and skip NUL bytes */
  		  while (q < (p + len) && *q)
  		    q++;
  
  		  n = write (STDOUT_FILENO, p, q - p);
  		  if (n < 0)
  		    {
  		      error = clib_error_return_unix (0, "write");
  		      goto done;
  		    }
  
  		  while (q < (p + len) && !*q)
  		    {
  		      q++;
  		      acked++;	/* every NUL is an acknowledgement */
  		    }
  		  len -= q - p;
  		  p = q;
  		}
  
  	      vec_reset_length (str);
  	    }
  
  	  if (do_quit && do_quit < acked)
  	    {
  	      /* Ask the other end to close the connection */
  	      clib_socket_tx_add_formatted (s, "quit\n");
  	      clib_socket_tx (s);
  	      do_quit = 0;
  	    }
  	  if (cmd && sent_ttype)
  	    {
  	      /* We wait until after the TELNET TTYPE option has been sent.
  	       * That is to make sure the session at the VPP end has switched
  	       * to line-by-line mode, and thus avoid prompts and echoing.
  	       * Note that it does also disable further TELNET option processing.
  	       */
  	      clib_socket_tx_add_formatted (s, "%s\n", cmd);
  	      clib_socket_tx (s);
  	      vec_free (cmd);
  	      do_quit = acked;	/* quit after the next response */
  	    }
  	}
        else
  	{
  	  error = clib_error_return (0, "unknown fd");
  	  goto done;
  	}
      }
  
    error = clib_socket_close (s);
  
  done:
    vec_free (cmd);
    vec_free (str);
    if (efd > -1)
      close (efd);
  
    if (is_interactive)
      tcsetattr (STDIN_FILENO, TCSAFLUSH, &orig_tio);
  
    if (error)
      {
        clib_error_report (error);
        return 1;
      }
  
    return 0;
  }
  
  /* *INDENT-ON* */
  
  /*
   * fd.io coding-style-patch-verification: ON
   *
   * Local Variables:
   * eval: (c-set-style "gnu")
   * End:
   */
  