1157184Sache/* A front-end using readline to "cook" input lines.
2157184Sache *
3157184Sache * Copyright (C) 2004, 1999  Per Bothner
4157184Sache *
5157184Sache * This front-end program is free software; you can redistribute it and/or
6157184Sache * modify it under the terms of the GNU General Public License as published
7157184Sache * by the Free Software Foundation; either version 2, or (at your option)
8157184Sache * any later version.
9157184Sache *
10157184Sache * Some code from Johnson & Troan: "Linux Application Development"
11157184Sache * (Addison-Wesley, 1998) was used directly or for inspiration.
12157184Sache *
13157184Sache * 2003-11-07 Wolfgang Taeuber <wolfgang_taeuber@agilent.com>
14157184Sache * Specify a history file and the size of the history file with command
15157184Sache * line options; use EDITOR/VISUAL to set vi/emacs preference.
16157184Sache */
17157184Sache
18157184Sache/* PROBLEMS/TODO:
19157184Sache *
20157184Sache * Only tested under GNU/Linux and Mac OS 10.x;  needs to be ported.
21157184Sache *
22157184Sache * Switching between line-editing-mode vs raw-char-mode depending on
23157184Sache * what tcgetattr returns is inherently not robust, plus it doesn't
24157184Sache * work when ssh/telnetting in.  A better solution is possible if the
25157184Sache * tty system can send in-line escape sequences indicating the current
26157184Sache * mode, echo'd input, etc.  That would also allow a user preference
27157184Sache * to set different colors for prompt, input, stdout, and stderr.
28157184Sache *
29157184Sache * When running mc -c under the Linux console, mc does not recognize
30157184Sache * mouse clicks, which mc does when not running under rlfe.
31157184Sache *
32157184Sache * Pasting selected text containing tabs is like hitting the tab character,
33157184Sache * which invokes readline completion.  We don't want this.  I don't know
34157184Sache * if this is fixable without integrating rlfe into a terminal emulator.
35157184Sache *
36157184Sache * Echo suppression is a kludge, but can only be avoided with better kernel
37157184Sache * support: We need a tty mode to disable "real" echoing, while still
38157184Sache * letting the inferior think its tty driver to doing echoing.
39157184Sache * Stevens's book claims SCR$ and BSD4.3+ have TIOCREMOTE.
40157184Sache *
41157184Sache * The latest readline may have some hooks we can use to avoid having
42157184Sache * to back up the prompt. (See HAVE_ALREADY_PROMPTED.)
43157184Sache *
44157184Sache * Desirable readline feature:  When in cooked no-echo mode (e.g. password),
45157184Sache * echo characters are they are types with '*', but remove them when done.
46157184Sache *
47157184Sache * Asynchronous output while we're editing an input line should be
48157184Sache * inserted in the output view *before* the input line, so that the
49157184Sache * lines being edited (with the prompt) float at the end of the input.
50157184Sache *
51157184Sache * A "page mode" option to emulate more/less behavior:  At each page of
52157184Sache * output, pause for a user command.  This required parsing the output
53157184Sache * to keep track of line lengths.  It also requires remembering the
54157184Sache * output, if we want an option to scroll back, which suggests that
55157184Sache * this should be integrated with a terminal emulator like xterm.
56157184Sache */
57157184Sache
58157184Sache#include <stdio.h>
59157184Sache#include <fcntl.h>
60157184Sache#include <sys/types.h>
61157184Sache#include <sys/socket.h>
62157184Sache#include <netinet/in.h>
63157184Sache#include <arpa/inet.h>
64157184Sache#include <signal.h>
65157184Sache#include <netdb.h>
66157184Sache#include <stdlib.h>
67157184Sache#include <errno.h>
68157184Sache#include <grp.h>
69157184Sache#include <string.h>
70157184Sache#include <sys/stat.h>
71157184Sache#include <unistd.h>
72157184Sache#include <sys/ioctl.h>
73157184Sache#include <termios.h>
74157184Sache
75157184Sache#include "config.h"
76157184Sache
77157184Sache#ifdef READLINE_LIBRARY
78157184Sache#  include "readline.h"
79157184Sache#  include "history.h"
80157184Sache#else
81157184Sache#  include <readline/readline.h>
82157184Sache#  include <readline/history.h>
83157184Sache#endif
84157184Sache
85157184Sache#ifndef COMMAND
86157184Sache#define COMMAND "/bin/bash"
87157184Sache#endif
88157184Sache#ifndef COMMAND_ARGS
89157184Sache#define COMMAND_ARGS COMMAND
90157184Sache#endif
91157184Sache
92157184Sache#ifndef ALT_COMMAND
93157184Sache#define ALT_COMMAND "/bin/sh"
94157184Sache#endif
95157184Sache#ifndef ALT_COMMAND_ARGS
96157184Sache#define ALT_COMMAND_ARGS ALT_COMMAND
97157184Sache#endif
98157184Sache
99157184Sache#ifndef HAVE_MEMMOVE
100157184Sache#  if __GNUC__ > 1
101157184Sache#    define memmove(d, s, n)	__builtin_memcpy(d, s, n)
102157184Sache#  else
103157184Sache#    define memmove(d, s, n)	memcpy(d, s, n)
104157184Sache#  endif
105157184Sache#else
106157184Sache#  define memmove(d, s, n)	memcpy(d, s, n)
107157184Sache#endif
108157184Sache
109157184Sache#define APPLICATION_NAME "rlfe"
110157184Sache
111157184Sachestatic int in_from_inferior_fd;
112157184Sachestatic int out_to_inferior_fd;
113157184Sachestatic void set_edit_mode ();
114157184Sachestatic void usage_exit ();
115157184Sachestatic char *hist_file = 0;
116157184Sachestatic int  hist_size = 0;
117157184Sache
118157184Sache/* Unfortunately, we cannot safely display echo from the inferior process.
119157184Sache   The reason is that the echo bit in the pty is "owned" by the inferior,
120157184Sache   and if we try to turn it off, we could confuse the inferior.
121157184Sache   Thus, when echoing, we get echo twice:  First readline echoes while
122157184Sache   we're actually editing. Then we send the line to the inferior, and the
123157184Sache   terminal driver send back an extra echo.
124157184Sache   The work-around is to remember the input lines, and when we see that
125157184Sache   line come back, we supress the output.
126157184Sache   A better solution (supposedly available on SVR4) would be a smarter
127157184Sache   terminal driver, with more flags ... */
128157184Sache#define ECHO_SUPPRESS_MAX 1024
129157184Sachechar echo_suppress_buffer[ECHO_SUPPRESS_MAX];
130157184Sacheint echo_suppress_start = 0;
131157184Sacheint echo_suppress_limit = 0;
132157184Sache
133157184Sache/*#define DEBUG*/
134157184Sache
135157184Sache#ifdef DEBUG
136157184SacheFILE *logfile = NULL;
137157184Sache#define DPRINT0(FMT) (fprintf(logfile, FMT), fflush(logfile))
138157184Sache#define DPRINT1(FMT, V1) (fprintf(logfile, FMT, V1), fflush(logfile))
139157184Sache#define DPRINT2(FMT, V1, V2) (fprintf(logfile, FMT, V1, V2), fflush(logfile))
140157184Sache#else
141157184Sache#define DPRINT0(FMT) ((void) 0) /* Do nothing */
142157184Sache#define DPRINT1(FMT, V1) ((void) 0) /* Do nothing */
143157184Sache#define DPRINT2(FMT, V1, V2) ((void) 0) /* Do nothing */
144157184Sache#endif
145157184Sache
146157184Sachestruct termios orig_term;
147157184Sache
148157184Sache/* Pid of child process. */
149157184Sachestatic pid_t child = -1;
150157184Sache
151157184Sachestatic void
152157184Sachesig_child (int signo)
153157184Sache{
154157184Sache  int status;
155157184Sache  wait (&status);
156157184Sache  if (hist_file != 0)
157157184Sache    {
158157184Sache      write_history (hist_file);
159157184Sache      if (hist_size)
160157184Sache	history_truncate_file (hist_file, hist_size);
161157184Sache    }
162157184Sache  DPRINT0 ("(Child process died.)\n");
163157184Sache  tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
164157184Sache  exit (0);
165157184Sache}
166157184Sache
167157184Sachevolatile int propagate_sigwinch = 0;
168157184Sache
169157184Sache/* sigwinch_handler
170157184Sache * propagate window size changes from input file descriptor to
171157184Sache * master side of pty.
172157184Sache */
173157184Sachevoid sigwinch_handler(int signal) {
174157184Sache   propagate_sigwinch = 1;
175157184Sache}
176157184Sache
177157184Sache
178157184Sache/* get_slave_pty() returns an integer file descriptor.
179157184Sache * If it returns < 0, an error has occurred.
180157184Sache * Otherwise, it has returned the slave file descriptor.
181157184Sache */
182157184Sache
183157184Sacheint get_slave_pty(char *name) {
184157184Sache   struct group *gptr;
185157184Sache   gid_t gid;
186157184Sache   int slave = -1;
187157184Sache
188157184Sache   /* chown/chmod the corresponding pty, if possible.
189157184Sache    * This will only work if the process has root permissions.
190157184Sache    * Alternatively, write and exec a small setuid program that
191157184Sache    * does just this.
192157184Sache    */
193157184Sache   if ((gptr = getgrnam("tty")) != 0) {
194157184Sache      gid = gptr->gr_gid;
195157184Sache   } else {
196157184Sache      /* if the tty group does not exist, don't change the
197157184Sache       * group on the slave pty, only the owner
198157184Sache       */
199157184Sache      gid = -1;
200157184Sache   }
201157184Sache
202157184Sache   /* Note that we do not check for errors here.  If this is code
203157184Sache    * where these actions are critical, check for errors!
204157184Sache    */
205157184Sache   chown(name, getuid(), gid);
206157184Sache   /* This code only makes the slave read/writeable for the user.
207157184Sache    * If this is for an interactive shell that will want to
208157184Sache    * receive "write" and "wall" messages, OR S_IWGRP into the
209157184Sache    * second argument below.
210157184Sache    */
211157184Sache   chmod(name, S_IRUSR|S_IWUSR);
212157184Sache
213157184Sache   /* open the corresponding slave pty */
214157184Sache   slave = open(name, O_RDWR);
215157184Sache   return (slave);
216157184Sache}
217157184Sache
218157184Sache/* Certain special characters, such as ctrl/C, we want to pass directly
219157184Sache   to the inferior, rather than letting readline handle them. */
220157184Sache
221157184Sachestatic char special_chars[20];
222157184Sachestatic int special_chars_count;
223157184Sache
224157184Sachestatic void
225157184Sacheadd_special_char(int ch)
226157184Sache{
227157184Sache  if (ch != 0)
228157184Sache    special_chars[special_chars_count++] = ch;
229157184Sache}
230157184Sache
231157184Sachestatic int eof_char;
232157184Sache
233157184Sachestatic int
234157184Sacheis_special_char(int ch)
235157184Sache{
236157184Sache  int i;
237157184Sache#if 0
238157184Sache  if (ch == eof_char && rl_point == rl_end)
239157184Sache    return 1;
240157184Sache#endif
241157184Sache  for (i = special_chars_count;  --i >= 0; )
242157184Sache    if (special_chars[i] == ch)
243157184Sache      return 1;
244157184Sache  return 0;
245157184Sache}
246157184Sache
247157184Sachestatic char buf[1024];
248157184Sache/* buf[0 .. buf_count-1] is the what has been emitted on the current line.
249157184Sache   It is used as the readline prompt. */
250157184Sachestatic int buf_count = 0;
251157184Sache
252157184Sacheint do_emphasize_input = 1;
253157184Sacheint current_emphasize_input;
254157184Sache
255157184Sachechar *start_input_mode = "\033[1m";
256157184Sachechar *end_input_mode = "\033[0m";
257157184Sache
258157184Sacheint num_keys = 0;
259157184Sache
260157184Sachestatic void maybe_emphasize_input (int on)
261157184Sache{
262157184Sache  if (on == current_emphasize_input
263157184Sache      || (on && ! do_emphasize_input))
264157184Sache    return;
265157184Sache  fprintf (rl_outstream, on ? start_input_mode : end_input_mode);
266157184Sache  fflush (rl_outstream);
267157184Sache  current_emphasize_input = on;
268157184Sache}
269157184Sache
270157184Sachestatic void
271157184Sachenull_prep_terminal (int meta)
272157184Sache{
273157184Sache}
274157184Sache
275157184Sachestatic void
276157184Sachenull_deprep_terminal ()
277157184Sache{
278157184Sache  maybe_emphasize_input (0);
279157184Sache}
280157184Sache
281157184Sachestatic int
282157184Sachepre_input_change_mode (void)
283157184Sache{
284157184Sache  return 0;
285157184Sache}
286157184Sache
287157184Sachechar pending_special_char;
288157184Sache
289157184Sachestatic void
290157184Sacheline_handler (char *line)
291157184Sache{
292157184Sache  if (line == NULL)
293157184Sache    {
294157184Sache      char buf[1];
295157184Sache      DPRINT0("saw eof!\n");
296157184Sache      buf[0] = '\004'; /* ctrl/d */
297157184Sache      write (out_to_inferior_fd, buf, 1);
298157184Sache    }
299157184Sache  else
300157184Sache    {
301157184Sache      static char enter[] = "\r";
302157184Sache      /*  Send line to inferior: */
303157184Sache      int length = strlen (line);
304157184Sache      if (length > ECHO_SUPPRESS_MAX-2)
305157184Sache	{
306157184Sache	  echo_suppress_start = 0;
307157184Sache	  echo_suppress_limit = 0;
308157184Sache	}
309157184Sache      else
310157184Sache	{
311157184Sache	  if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
312157184Sache	    {
313157184Sache	      if (echo_suppress_limit - echo_suppress_start + length
314157184Sache		  <= ECHO_SUPPRESS_MAX - 2)
315157184Sache		{
316157184Sache		  memmove (echo_suppress_buffer,
317157184Sache			   echo_suppress_buffer + echo_suppress_start,
318157184Sache			   echo_suppress_limit - echo_suppress_start);
319157184Sache		  echo_suppress_limit -= echo_suppress_start;
320157184Sache		  echo_suppress_start = 0;
321157184Sache		}
322157184Sache	      else
323157184Sache		{
324157184Sache		  echo_suppress_limit = 0;
325157184Sache		}
326157184Sache	      echo_suppress_start = 0;
327157184Sache	    }
328157184Sache	  memcpy (echo_suppress_buffer + echo_suppress_limit,
329157184Sache		  line, length);
330157184Sache	  echo_suppress_limit += length;
331157184Sache	  echo_suppress_buffer[echo_suppress_limit++] = '\r';
332157184Sache	  echo_suppress_buffer[echo_suppress_limit++] = '\n';
333157184Sache	}
334157184Sache      write (out_to_inferior_fd, line, length);
335157184Sache      if (pending_special_char == 0)
336157184Sache        {
337157184Sache          write (out_to_inferior_fd, enter, sizeof(enter)-1);
338157184Sache          if (*line)
339157184Sache            add_history (line);
340157184Sache        }
341157184Sache      free (line);
342157184Sache    }
343157184Sache  rl_callback_handler_remove ();
344157184Sache  buf_count = 0;
345157184Sache  num_keys = 0;
346157184Sache  if (pending_special_char != 0)
347157184Sache    {
348157184Sache      write (out_to_inferior_fd, &pending_special_char, 1);
349157184Sache      pending_special_char = 0;
350157184Sache    }
351157184Sache}
352157184Sache
353157184Sache/* Value of rl_getc_function.
354157184Sache   Use this because readline should read from stdin, not rl_instream,
355157184Sache   points to the pty (so readline has monitor its terminal modes). */
356157184Sache
357157184Sacheint
358157184Sachemy_rl_getc (FILE *dummy)
359157184Sache{
360157184Sache  int ch = rl_getc (stdin);
361157184Sache  if (is_special_char (ch))
362157184Sache    {
363157184Sache      pending_special_char = ch;
364157184Sache      return '\r';
365157184Sache    }
366157184Sache  return ch;
367157184Sache}
368157184Sache
369157184Sacheint
370157184Sachemain(int argc, char** argv)
371157184Sache{
372157184Sache  char *path;
373157184Sache  int i;
374157184Sache  int master;
375157184Sache  char *name;
376157184Sache  int in_from_tty_fd;
377157184Sache  struct sigaction act;
378157184Sache  struct winsize ws;
379157184Sache  struct termios t;
380157184Sache  int maxfd;
381157184Sache  fd_set in_set;
382157184Sache  static char empty_string[1] = "";
383157184Sache  char *prompt = empty_string;
384157184Sache  int ioctl_err = 0;
385157184Sache  int arg_base = 1;
386157184Sache
387157184Sache#ifdef DEBUG
388157184Sache  logfile = fopen("/tmp/rlfe.log", "w");
389157184Sache#endif
390157184Sache
391157184Sache  while (arg_base<argc)
392157184Sache    {
393157184Sache      if (argv[arg_base][0] != '-')
394157184Sache	break;
395157184Sache      if (arg_base+1 >= argc )
396157184Sache	usage_exit();
397157184Sache      switch(argv[arg_base][1])
398157184Sache	{
399157184Sache	case 'h':
400157184Sache	  arg_base++;
401157184Sache	  hist_file = argv[arg_base];
402157184Sache	  break;
403157184Sache	case 's':
404157184Sache	  arg_base++;
405157184Sache	  hist_size = atoi(argv[arg_base]);
406157184Sache	  if (hist_size<0)
407157184Sache	    usage_exit();
408157184Sache	  break;
409157184Sache	default:
410157184Sache	  usage_exit();
411157184Sache	}
412157184Sache      arg_base++;
413157184Sache    }
414157184Sache  if (hist_file)
415157184Sache    read_history (hist_file);
416157184Sache
417157184Sache  set_edit_mode ();
418157184Sache
419157184Sache  rl_readline_name = APPLICATION_NAME;
420157184Sache
421157184Sache  if ((master = OpenPTY (&name)) < 0)
422157184Sache    {
423157184Sache      perror("ptypair: could not open master pty");
424157184Sache      exit(1);
425157184Sache    }
426157184Sache
427157184Sache  DPRINT1("pty name: '%s'\n", name);
428157184Sache
429157184Sache  /* set up SIGWINCH handler */
430157184Sache  act.sa_handler = sigwinch_handler;
431157184Sache  sigemptyset(&(act.sa_mask));
432157184Sache  act.sa_flags = 0;
433157184Sache  if (sigaction(SIGWINCH, &act, NULL) < 0)
434157184Sache    {
435157184Sache      perror("ptypair: could not handle SIGWINCH ");
436157184Sache      exit(1);
437157184Sache    }
438157184Sache
439157184Sache  if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
440157184Sache    {
441157184Sache      perror("ptypair: could not get window size");
442157184Sache      exit(1);
443157184Sache    }
444157184Sache
445157184Sache  if ((child = fork()) < 0)
446157184Sache    {
447157184Sache      perror("cannot fork");
448157184Sache      exit(1);
449157184Sache    }
450157184Sache
451157184Sache  if (child == 0)
452157184Sache    {
453157184Sache      int slave;  /* file descriptor for slave pty */
454157184Sache
455157184Sache      /* We are in the child process */
456157184Sache      close(master);
457157184Sache
458157184Sache#ifdef TIOCSCTTY
459157184Sache      if ((slave = get_slave_pty(name)) < 0)
460157184Sache	{
461157184Sache	  perror("ptypair: could not open slave pty");
462157184Sache	  exit(1);
463157184Sache	}
464157184Sache#endif
465157184Sache
466157184Sache      /* We need to make this process a session group leader, because
467157184Sache       * it is on a new PTY, and things like job control simply will
468157184Sache       * not work correctly unless there is a session group leader
469157184Sache       * and process group leader (which a session group leader
470157184Sache       * automatically is). This also disassociates us from our old
471157184Sache       * controlling tty.
472157184Sache       */
473157184Sache      if (setsid() < 0)
474157184Sache	{
475157184Sache	  perror("could not set session leader");
476157184Sache	}
477157184Sache
478157184Sache      /* Tie us to our new controlling tty. */
479157184Sache#ifdef TIOCSCTTY
480157184Sache      if (ioctl(slave, TIOCSCTTY, NULL))
481157184Sache	{
482157184Sache	  perror("could not set new controlling tty");
483157184Sache	}
484157184Sache#else
485157184Sache      if ((slave = get_slave_pty(name)) < 0)
486157184Sache	{
487157184Sache	  perror("ptypair: could not open slave pty");
488157184Sache	  exit(1);
489157184Sache	}
490157184Sache#endif
491157184Sache
492157184Sache      /* make slave pty be standard in, out, and error */
493157184Sache      dup2(slave, STDIN_FILENO);
494157184Sache      dup2(slave, STDOUT_FILENO);
495157184Sache      dup2(slave, STDERR_FILENO);
496157184Sache
497157184Sache      /* at this point the slave pty should be standard input */
498157184Sache      if (slave > 2)
499157184Sache	{
500157184Sache	  close(slave);
501157184Sache	}
502157184Sache
503157184Sache      /* Try to restore window size; failure isn't critical */
504157184Sache      if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
505157184Sache	{
506157184Sache	  perror("could not restore window size");
507157184Sache	}
508157184Sache
509157184Sache      /* now start the shell */
510157184Sache      {
511157184Sache	static char* command_args[] = { COMMAND_ARGS, NULL };
512157184Sache	static char* alt_command_args[] = { ALT_COMMAND_ARGS, NULL };
513157184Sache	if (argc <= 1)
514157184Sache	  {
515157184Sache	    execvp (COMMAND, command_args);
516157184Sache	    execvp (ALT_COMMAND, alt_command_args);
517157184Sache	  }
518157184Sache	else
519157184Sache	  execvp (argv[arg_base], &argv[arg_base]);
520157184Sache      }
521157184Sache
522157184Sache      /* should never be reached */
523157184Sache      exit(1);
524157184Sache    }
525157184Sache
526157184Sache  /* parent */
527157184Sache  signal (SIGCHLD, sig_child);
528157184Sache
529157184Sache  /* Note that we only set termios settings for standard input;
530157184Sache   * the master side of a pty is NOT a tty.
531157184Sache   */
532157184Sache  tcgetattr(STDIN_FILENO, &orig_term);
533157184Sache
534157184Sache  t = orig_term;
535157184Sache  eof_char = t.c_cc[VEOF];
536157184Sache  /*  add_special_char(t.c_cc[VEOF]);*/
537157184Sache  add_special_char(t.c_cc[VINTR]);
538157184Sache  add_special_char(t.c_cc[VQUIT]);
539157184Sache  add_special_char(t.c_cc[VSUSP]);
540157184Sache#if defined (VDISCARD)
541157184Sache  add_special_char(t.c_cc[VDISCARD]);
542157184Sache#endif
543157184Sache
544157184Sache  t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
545157184Sache		 ECHOK | ECHOKE | ECHONL | ECHOPRT );
546157184Sache  t.c_iflag &= ~ICRNL;
547157184Sache  t.c_iflag |= IGNBRK;
548157184Sache  t.c_cc[VMIN] = 1;
549157184Sache  t.c_cc[VTIME] = 0;
550157184Sache  tcsetattr(STDIN_FILENO, TCSANOW, &t);
551157184Sache  in_from_inferior_fd = master;
552157184Sache  out_to_inferior_fd = master;
553157184Sache  rl_instream = fdopen (master, "r");
554157184Sache  rl_getc_function = my_rl_getc;
555157184Sache
556157184Sache  rl_prep_term_function = null_prep_terminal;
557157184Sache  rl_deprep_term_function = null_deprep_terminal;
558157184Sache  rl_pre_input_hook = pre_input_change_mode;
559157184Sache  rl_callback_handler_install (prompt, line_handler);
560157184Sache
561157184Sache  in_from_tty_fd = STDIN_FILENO;
562157184Sache  FD_ZERO (&in_set);
563157184Sache  maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
564157184Sache    : in_from_tty_fd;
565157184Sache  for (;;)
566157184Sache    {
567157184Sache      int num;
568157184Sache      FD_SET (in_from_inferior_fd, &in_set);
569157184Sache      FD_SET (in_from_tty_fd, &in_set);
570157184Sache
571157184Sache      num = select(maxfd+1, &in_set, NULL, NULL, NULL);
572157184Sache
573157184Sache      if (propagate_sigwinch)
574157184Sache	{
575157184Sache	  struct winsize ws;
576157184Sache	  if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
577157184Sache	    {
578157184Sache	      ioctl (master, TIOCSWINSZ, &ws);
579157184Sache	    }
580157184Sache	  propagate_sigwinch = 0;
581157184Sache	  continue;
582157184Sache	}
583157184Sache
584157184Sache      if (num <= 0)
585157184Sache	{
586157184Sache	  perror ("select");
587157184Sache	  exit (-1);
588157184Sache	}
589157184Sache      if (FD_ISSET (in_from_tty_fd, &in_set))
590157184Sache	{
591157184Sache	  extern int readline_echoing_p;
592157184Sache	  struct termios term_master;
593157184Sache	  int do_canon = 1;
594157184Sache	  int do_icrnl = 1;
595157184Sache	  int ioctl_ret;
596157184Sache
597157184Sache	  DPRINT1("[tty avail num_keys:%d]\n", num_keys);
598157184Sache
599157184Sache	  /* If we can't get tty modes for the master side of the pty, we
600157184Sache	     can't handle non-canonical-mode programs.  Always assume the
601157184Sache	     master is in canonical echo mode if we can't tell. */
602157184Sache	  ioctl_ret = tcgetattr(master, &term_master);
603157184Sache
604157184Sache	  if (ioctl_ret >= 0)
605157184Sache	    {
606157184Sache	      do_canon = (term_master.c_lflag & ICANON) != 0;
607157184Sache	      do_icrnl = (term_master.c_lflag & ICRNL) != 0;
608157184Sache	      readline_echoing_p = (term_master.c_lflag & ECHO) != 0;
609157184Sache	      DPRINT1 ("echo,canon,crnl:%03d\n",
610157184Sache		       100 * readline_echoing_p
611157184Sache		       + 10 * do_canon
612157184Sache		       + 1 * do_icrnl);
613157184Sache	    }
614157184Sache	  else
615157184Sache	    {
616157184Sache	      if (ioctl_err == 0)
617157184Sache		DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
618157184Sache	      ioctl_err = 1;
619157184Sache	    }
620157184Sache
621157184Sache	  if (do_canon == 0 && num_keys == 0)
622157184Sache	    {
623157184Sache	      char ch[10];
624157184Sache	      int count = read (STDIN_FILENO, ch, sizeof(ch));
625157184Sache	      DPRINT1("[read %d chars from stdin: ", count);
626157184Sache	      DPRINT2(" \"%.*s\"]\n", count, ch);
627157184Sache	      if (do_icrnl)
628157184Sache		{
629157184Sache		  int i = count;
630157184Sache		  while (--i >= 0)
631157184Sache		    {
632157184Sache		      if (ch[i] == '\r')
633157184Sache			ch[i] = '\n';
634157184Sache		    }
635157184Sache		}
636157184Sache	      maybe_emphasize_input (1);
637157184Sache	      write (out_to_inferior_fd, ch, count);
638157184Sache	    }
639157184Sache	  else
640157184Sache	    {
641157184Sache	      if (num_keys == 0)
642157184Sache		{
643157184Sache		  int i;
644157184Sache		  /* Re-install callback handler for new prompt. */
645157184Sache		  if (prompt != empty_string)
646157184Sache		    free (prompt);
647157184Sache		  if (prompt == NULL)
648157184Sache		    {
649157184Sache		      DPRINT0("New empty prompt\n");
650157184Sache		      prompt = empty_string;
651157184Sache		    }
652157184Sache		  else
653157184Sache		    {
654157184Sache		      if (do_emphasize_input && buf_count > 0)
655157184Sache			{
656157184Sache			  prompt = malloc (buf_count + strlen (end_input_mode)
657157184Sache					   + strlen (start_input_mode) + 5);
658157184Sache			  sprintf (prompt, "\001%s\002%.*s\001%s\002",
659157184Sache				   end_input_mode,
660157184Sache				   buf_count, buf,
661157184Sache				   start_input_mode);
662157184Sache			}
663157184Sache		      else
664157184Sache			{
665157184Sache			  prompt = malloc (buf_count + 1);
666157184Sache			  memcpy (prompt, buf, buf_count);
667157184Sache			  prompt[buf_count] = '\0';
668157184Sache			}
669157184Sache		      DPRINT1("New prompt '%s'\n", prompt);
670157184Sache#if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED */
671157184Sache		      /* Doesn't quite work when do_emphasize_input is 1. */
672157184Sache		      rl_already_prompted = buf_count > 0;
673157184Sache#else
674157184Sache		      if (buf_count > 0)
675157184Sache			write (1, "\r", 1);
676157184Sache#endif
677157184Sache		    }
678157184Sache
679157184Sache		  rl_callback_handler_install (prompt, line_handler);
680157184Sache		}
681157184Sache	      num_keys++;
682157184Sache	      maybe_emphasize_input (1);
683157184Sache	      rl_callback_read_char ();
684157184Sache	    }
685157184Sache	}
686157184Sache      else /* output from inferior. */
687157184Sache	{
688157184Sache	  int i;
689157184Sache	  int count;
690157184Sache	  int old_count;
691157184Sache	  if (buf_count > (sizeof(buf) >> 2))
692157184Sache	    buf_count = 0;
693157184Sache	  count = read (in_from_inferior_fd, buf+buf_count,
694157184Sache			sizeof(buf) - buf_count);
695157184Sache          DPRINT2("read %d from inferior, buf_count=%d", count, buf_count);
696157184Sache	  DPRINT2(": \"%.*s\"", count, buf+buf_count);
697157184Sache	  maybe_emphasize_input (0);
698157184Sache	  if (count <= 0)
699157184Sache	    {
700157184Sache	      DPRINT0 ("(Connection closed by foreign host.)\n");
701157184Sache	      tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
702157184Sache	      exit (0);
703157184Sache	    }
704157184Sache	  old_count = buf_count;
705157184Sache
706157184Sache          /* Look for any pending echo that we need to suppress. */
707157184Sache	  while (echo_suppress_start < echo_suppress_limit
708157184Sache		 && count > 0
709157184Sache		 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
710157184Sache	    {
711157184Sache	      count--;
712157184Sache	      buf_count++;
713157184Sache	      echo_suppress_start++;
714157184Sache	    }
715157184Sache	  DPRINT1("suppressed %d characters of echo.\n", buf_count-old_count);
716157184Sache
717157184Sache          /* Write to the terminal anything that was not suppressed. */
718157184Sache          if (count > 0)
719157184Sache            write (1, buf + buf_count, count);
720157184Sache
721157184Sache          /* Finally, look for a prompt candidate.
722157184Sache           * When we get around to going input (from the keyboard),
723157184Sache           * we will consider the prompt to be anything since the last
724157184Sache           * line terminator.  So we need to save that text in the
725157184Sache           * initial part of buf.  However, anything before the
726157184Sache           * most recent end-of-line is not interesting. */
727157184Sache	  buf_count += count;
728157184Sache#if 1
729157184Sache	  for (i = buf_count;  --i >= old_count; )
730157184Sache#else
731157184Sache	  for (i = buf_count - 1;  i-- >= buf_count - count; )
732157184Sache#endif
733157184Sache	    {
734157184Sache	      if (buf[i] == '\n' || buf[i] == '\r')
735157184Sache		{
736157184Sache		  i++;
737157184Sache		  memmove (buf, buf+i, buf_count - i);
738157184Sache		  buf_count -= i;
739157184Sache		  break;
740157184Sache		}
741157184Sache	    }
742157184Sache	  DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
743157184Sache	}
744157184Sache    }
745157184Sache}
746157184Sache
747157184Sachestatic void set_edit_mode ()
748157184Sache{
749157184Sache  int vi = 0;
750157184Sache  char *shellopts;
751157184Sache
752157184Sache  shellopts = getenv ("SHELLOPTS");
753157184Sache  while (shellopts != 0)
754157184Sache    {
755157184Sache      if (strncmp ("vi", shellopts, 2) == 0)
756157184Sache	{
757157184Sache	  vi = 1;
758157184Sache	  break;
759157184Sache	}
760157184Sache      shellopts = index (shellopts + 1, ':');
761157184Sache    }
762157184Sache
763157184Sache  if (!vi)
764157184Sache    {
765157184Sache      if (getenv ("EDITOR") != 0)
766157184Sache	vi |= strcmp (getenv ("EDITOR"), "vi") == 0;
767157184Sache    }
768157184Sache
769157184Sache  if (vi)
770157184Sache    rl_variable_bind ("editing-mode", "vi");
771157184Sache  else
772157184Sache    rl_variable_bind ("editing-mode", "emacs");
773157184Sache}
774157184Sache
775157184Sache
776157184Sachestatic void usage_exit ()
777157184Sache{
778157184Sache  fprintf (stderr, "Usage: rlfe [-h histfile] [-s size] cmd [arg1] [arg2] ...\n\n");
779157184Sache  exit (1);
780157184Sache}
781