1 /* A front-end using readline to "cook" input lines.
2  *
3  * Copyright (C) 2004, 1999  Per Bothner
4  *
5  * This front-end program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as published
7  * by the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * Some code from Johnson & Troan: "Linux Application Development"
11  * (Addison-Wesley, 1998) was used directly or for inspiration.
12  *
13  * 2003-11-07 Wolfgang Taeuber <wolfgang_taeuber@agilent.com>
14  * Specify a history file and the size of the history file with command
15  * line options; use EDITOR/VISUAL to set vi/emacs preference.
16  */
17 
18 /* PROBLEMS/TODO:
19  *
20  * Only tested under GNU/Linux and Mac OS 10.x;  needs to be ported.
21  *
22  * Switching between line-editing-mode vs raw-char-mode depending on
23  * what tcgetattr returns is inherently not robust, plus it doesn't
24  * work when ssh/telnetting in.  A better solution is possible if the
25  * tty system can send in-line escape sequences indicating the current
26  * mode, echo'd input, etc.  That would also allow a user preference
27  * to set different colors for prompt, input, stdout, and stderr.
28  *
29  * When running mc -c under the Linux console, mc does not recognize
30  * mouse clicks, which mc does when not running under rlfe.
31  *
32  * Pasting selected text containing tabs is like hitting the tab character,
33  * which invokes readline completion.  We don't want this.  I don't know
34  * if this is fixable without integrating rlfe into a terminal emulator.
35  *
36  * Echo suppression is a kludge, but can only be avoided with better kernel
37  * support: We need a tty mode to disable "real" echoing, while still
38  * letting the inferior think its tty driver to doing echoing.
39  * Stevens's book claims SCR$ and BSD4.3+ have TIOCREMOTE.
40  *
41  * The latest readline may have some hooks we can use to avoid having
42  * to back up the prompt. (See HAVE_ALREADY_PROMPTED.)
43  *
44  * Desirable readline feature:  When in cooked no-echo mode (e.g. password),
45  * echo characters are they are types with '*', but remove them when done.
46  *
47  * Asynchronous output while we're editing an input line should be
48  * inserted in the output view *before* the input line, so that the
49  * lines being edited (with the prompt) float at the end of the input.
50  *
51  * A "page mode" option to emulate more/less behavior:  At each page of
52  * output, pause for a user command.  This required parsing the output
53  * to keep track of line lengths.  It also requires remembering the
54  * output, if we want an option to scroll back, which suggests that
55  * this should be integrated with a terminal emulator like xterm.
56  */
57 
58 #include <stdio.h>
59 #include <fcntl.h>
60 #include <sys/types.h>
61 #include <sys/socket.h>
62 #include <netinet/in.h>
63 #include <arpa/inet.h>
64 #include <signal.h>
65 #include <netdb.h>
66 #include <stdlib.h>
67 #include <errno.h>
68 #include <grp.h>
69 #include <string.h>
70 #include <sys/stat.h>
71 #include <unistd.h>
72 #include <sys/ioctl.h>
73 #include <termios.h>
74 
75 #include "config.h"
76 #include "extern.h"
77 
78 #if defined (HAVE_SYS_WAIT_H)
79 #  include <sys/wait.h>
80 #endif
81 
82 #ifdef READLINE_LIBRARY
83 #  include "readline.h"
84 #  include "history.h"
85 #else
86 #  include <readline/readline.h>
87 #  include <readline/history.h>
88 #endif
89 
90 #ifndef COMMAND
91 #define COMMAND "/bin/bash"
92 #endif
93 #ifndef COMMAND_ARGS
94 #define COMMAND_ARGS COMMAND
95 #endif
96 
97 #ifndef ALT_COMMAND
98 #define ALT_COMMAND "/bin/sh"
99 #endif
100 #ifndef ALT_COMMAND_ARGS
101 #define ALT_COMMAND_ARGS ALT_COMMAND
102 #endif
103 
104 #ifndef HAVE_MEMMOVE
105 #  if __GNUC__ > 1
106 #    define memmove(d, s, n)	__builtin_memcpy(d, s, n)
107 #  else
108 #    define memmove(d, s, n)	memcpy(d, s, n)
109 #  endif
110 #else
111 #  define memmove(d, s, n)	memcpy(d, s, n)
112 #endif
113 
114 #define APPLICATION_NAME "rlfe"
115 
116 static int in_from_inferior_fd;
117 static int out_to_inferior_fd;
118 static void set_edit_mode ();
119 static void usage_exit ();
120 static char *hist_file = 0;
121 static int  hist_size = 0;
122 
123 /* Unfortunately, we cannot safely display echo from the inferior process.
124    The reason is that the echo bit in the pty is "owned" by the inferior,
125    and if we try to turn it off, we could confuse the inferior.
126    Thus, when echoing, we get echo twice:  First readline echoes while
127    we're actually editing. Then we send the line to the inferior, and the
128    terminal driver send back an extra echo.
129    The work-around is to remember the input lines, and when we see that
130    line come back, we supress the output.
131    A better solution (supposedly available on SVR4) would be a smarter
132    terminal driver, with more flags ... */
133 #define ECHO_SUPPRESS_MAX 1024
134 char echo_suppress_buffer[ECHO_SUPPRESS_MAX];
135 int echo_suppress_start = 0;
136 int echo_suppress_limit = 0;
137 
138 /*#define DEBUG*/
139 
140 #ifdef DEBUG
141 FILE *logfile = NULL;
142 #define DPRINT0(FMT) (fprintf(logfile, FMT), fflush(logfile))
143 #define DPRINT1(FMT, V1) (fprintf(logfile, FMT, V1), fflush(logfile))
144 #define DPRINT2(FMT, V1, V2) (fprintf(logfile, FMT, V1, V2), fflush(logfile))
145 #else
146 #define DPRINT0(FMT) ((void) 0) /* Do nothing */
147 #define DPRINT1(FMT, V1) ((void) 0) /* Do nothing */
148 #define DPRINT2(FMT, V1, V2) ((void) 0) /* Do nothing */
149 #endif
150 
151 struct termios orig_term;
152 
153 /* Pid of child process. */
154 static pid_t child = -1;
155 
156 static void
sig_child(int signo)157 sig_child (int signo)
158 {
159   int status;
160   wait (&status);
161   if (hist_file != 0)
162     {
163       write_history (hist_file);
164       if (hist_size)
165 	history_truncate_file (hist_file, hist_size);
166     }
167   DPRINT0 ("(Child process died.)\n");
168   tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
169   exit (0);
170 }
171 
172 volatile int propagate_sigwinch = 0;
173 
174 /* sigwinch_handler
175  * propagate window size changes from input file descriptor to
176  * master side of pty.
177  */
sigwinch_handler(int signal)178 void sigwinch_handler(int signal) {
179    propagate_sigwinch = 1;
180 }
181 
182 
183 /* get_slave_pty() returns an integer file descriptor.
184  * If it returns < 0, an error has occurred.
185  * Otherwise, it has returned the slave file descriptor.
186  */
187 
get_slave_pty(char * name)188 int get_slave_pty(char *name) {
189    struct group *gptr;
190    gid_t gid;
191    int slave = -1;
192 
193    /* chown/chmod the corresponding pty, if possible.
194     * This will only work if the process has root permissions.
195     * Alternatively, write and exec a small setuid program that
196     * does just this.
197     */
198    if ((gptr = getgrnam("tty")) != 0) {
199       gid = gptr->gr_gid;
200    } else {
201       /* if the tty group does not exist, don't change the
202        * group on the slave pty, only the owner
203        */
204       gid = -1;
205    }
206 
207    /* Note that we do not check for errors here.  If this is code
208     * where these actions are critical, check for errors!
209     */
210    chown(name, getuid(), gid);
211    /* This code only makes the slave read/writeable for the user.
212     * If this is for an interactive shell that will want to
213     * receive "write" and "wall" messages, OR S_IWGRP into the
214     * second argument below.
215     */
216    chmod(name, S_IRUSR|S_IWUSR);
217 
218    /* open the corresponding slave pty */
219    slave = open(name, O_RDWR);
220    return (slave);
221 }
222 
223 /* Certain special characters, such as ctrl/C, we want to pass directly
224    to the inferior, rather than letting readline handle them. */
225 
226 static char special_chars[20];
227 static int special_chars_count;
228 
229 static void
add_special_char(int ch)230 add_special_char(int ch)
231 {
232   if (ch != 0)
233     special_chars[special_chars_count++] = ch;
234 }
235 
236 static int eof_char;
237 
238 static int
is_special_char(int ch)239 is_special_char(int ch)
240 {
241   int i;
242 #if 0
243   if (ch == eof_char && rl_point == rl_end)
244     return 1;
245 #endif
246   for (i = special_chars_count;  --i >= 0; )
247     if (special_chars[i] == ch)
248       return 1;
249   return 0;
250 }
251 
252 static char buf[1024];
253 /* buf[0 .. buf_count-1] is the what has been emitted on the current line.
254    It is used as the readline prompt. */
255 static int buf_count = 0;
256 
257 int do_emphasize_input = 1;
258 int current_emphasize_input;
259 
260 char *start_input_mode = "\033[1m";
261 char *end_input_mode = "\033[0m";
262 
263 int num_keys = 0;
264 
maybe_emphasize_input(int on)265 static void maybe_emphasize_input (int on)
266 {
267   if (on == current_emphasize_input
268       || (on && ! do_emphasize_input))
269     return;
270   fprintf (rl_outstream, on ? start_input_mode : end_input_mode);
271   fflush (rl_outstream);
272   current_emphasize_input = on;
273 }
274 
275 static void
null_prep_terminal(int meta)276 null_prep_terminal (int meta)
277 {
278 }
279 
280 static void
null_deprep_terminal()281 null_deprep_terminal ()
282 {
283   maybe_emphasize_input (0);
284 }
285 
286 static int
pre_input_change_mode(void)287 pre_input_change_mode (void)
288 {
289   return 0;
290 }
291 
292 char pending_special_char;
293 
294 static void
line_handler(char * line)295 line_handler (char *line)
296 {
297   if (line == NULL)
298     {
299       char buf[1];
300       DPRINT0("saw eof!\n");
301       buf[0] = '\004'; /* ctrl/d */
302       write (out_to_inferior_fd, buf, 1);
303     }
304   else
305     {
306       static char enter[] = "\r";
307       /*  Send line to inferior: */
308       int length = strlen (line);
309       if (length > ECHO_SUPPRESS_MAX-2)
310 	{
311 	  echo_suppress_start = 0;
312 	  echo_suppress_limit = 0;
313 	}
314       else
315 	{
316 	  if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
317 	    {
318 	      if (echo_suppress_limit - echo_suppress_start + length
319 		  <= ECHO_SUPPRESS_MAX - 2)
320 		{
321 		  memmove (echo_suppress_buffer,
322 			   echo_suppress_buffer + echo_suppress_start,
323 			   echo_suppress_limit - echo_suppress_start);
324 		  echo_suppress_limit -= echo_suppress_start;
325 		  echo_suppress_start = 0;
326 		}
327 	      else
328 		{
329 		  echo_suppress_limit = 0;
330 		}
331 	      echo_suppress_start = 0;
332 	    }
333 	  memcpy (echo_suppress_buffer + echo_suppress_limit,
334 		  line, length);
335 	  echo_suppress_limit += length;
336 	  echo_suppress_buffer[echo_suppress_limit++] = '\r';
337 	  echo_suppress_buffer[echo_suppress_limit++] = '\n';
338 	}
339       write (out_to_inferior_fd, line, length);
340       if (pending_special_char == 0)
341         {
342           write (out_to_inferior_fd, enter, sizeof(enter)-1);
343           if (*line)
344             add_history (line);
345         }
346       free (line);
347     }
348   rl_callback_handler_remove ();
349   buf_count = 0;
350   num_keys = 0;
351   if (pending_special_char != 0)
352     {
353       write (out_to_inferior_fd, &pending_special_char, 1);
354       pending_special_char = 0;
355     }
356 }
357 
358 /* Value of rl_getc_function.
359    Use this because readline should read from stdin, not rl_instream,
360    points to the pty (so readline has monitor its terminal modes). */
361 
362 int
my_rl_getc(FILE * dummy)363 my_rl_getc (FILE *dummy)
364 {
365   int ch = rl_getc (stdin);
366   if (is_special_char (ch))
367     {
368       pending_special_char = ch;
369       return '\r';
370     }
371   return ch;
372 }
373 
374 int
main(int argc,char ** argv)375 main(int argc, char** argv)
376 {
377   char *path;
378   int i;
379   int master;
380   char *name;
381   int in_from_tty_fd;
382   struct sigaction act;
383   struct winsize ws;
384   struct termios t;
385   int maxfd;
386   fd_set in_set;
387   static char empty_string[1] = "";
388   char *prompt = empty_string;
389   int ioctl_err = 0;
390   int arg_base = 1;
391 
392 #ifdef DEBUG
393   logfile = fopen("/tmp/rlfe.log", "w");
394 #endif
395 
396   while (arg_base<argc)
397     {
398       if (argv[arg_base][0] != '-')
399 	break;
400       if (arg_base+1 >= argc )
401 	usage_exit();
402       switch(argv[arg_base][1])
403 	{
404 	case 'h':
405 	  arg_base++;
406 	  hist_file = argv[arg_base];
407 	  break;
408 	case 's':
409 	  arg_base++;
410 	  hist_size = atoi(argv[arg_base]);
411 	  if (hist_size<0)
412 	    usage_exit();
413 	  break;
414 	default:
415 	  usage_exit();
416 	}
417       arg_base++;
418     }
419   if (hist_file)
420     read_history (hist_file);
421 
422   set_edit_mode ();
423 
424   rl_readline_name = APPLICATION_NAME;
425 
426   if ((master = OpenPTY (&name)) < 0)
427     {
428       perror("ptypair: could not open master pty");
429       exit(1);
430     }
431 
432   DPRINT1("pty name: '%s'\n", name);
433 
434   /* set up SIGWINCH handler */
435   act.sa_handler = sigwinch_handler;
436   sigemptyset(&(act.sa_mask));
437   act.sa_flags = 0;
438   if (sigaction(SIGWINCH, &act, NULL) < 0)
439     {
440       perror("ptypair: could not handle SIGWINCH ");
441       exit(1);
442     }
443 
444   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
445     {
446       perror("ptypair: could not get window size");
447       exit(1);
448     }
449 
450   if ((child = fork()) < 0)
451     {
452       perror("cannot fork");
453       exit(1);
454     }
455 
456   if (child == 0)
457     {
458       int slave;  /* file descriptor for slave pty */
459 
460       /* We are in the child process */
461       close(master);
462 
463 #ifdef TIOCSCTTY
464       if ((slave = get_slave_pty(name)) < 0)
465 	{
466 	  perror("ptypair: could not open slave pty");
467 	  exit(1);
468 	}
469 #endif
470 
471       /* We need to make this process a session group leader, because
472        * it is on a new PTY, and things like job control simply will
473        * not work correctly unless there is a session group leader
474        * and process group leader (which a session group leader
475        * automatically is). This also disassociates us from our old
476        * controlling tty.
477        */
478       if (setsid() < 0)
479 	{
480 	  perror("could not set session leader");
481 	}
482 
483       /* Tie us to our new controlling tty. */
484 #ifdef TIOCSCTTY
485       if (ioctl(slave, TIOCSCTTY, NULL))
486 	{
487 	  perror("could not set new controlling tty");
488 	}
489 #else
490       if ((slave = get_slave_pty(name)) < 0)
491 	{
492 	  perror("ptypair: could not open slave pty");
493 	  exit(1);
494 	}
495 #endif
496 
497       /* make slave pty be standard in, out, and error */
498       dup2(slave, STDIN_FILENO);
499       dup2(slave, STDOUT_FILENO);
500       dup2(slave, STDERR_FILENO);
501 
502       /* at this point the slave pty should be standard input */
503       if (slave > 2)
504 	{
505 	  close(slave);
506 	}
507 
508       /* Try to restore window size; failure isn't critical */
509       if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
510 	{
511 	  perror("could not restore window size");
512 	}
513 
514       /* now start the shell */
515       {
516 	static char* command_args[] = { COMMAND_ARGS, NULL };
517 	static char* alt_command_args[] = { ALT_COMMAND_ARGS, NULL };
518 	if (argc <= 1)
519 	  {
520 	    execvp (COMMAND, command_args);
521 	    execvp (ALT_COMMAND, alt_command_args);
522 	  }
523 	else
524 	  execvp (argv[arg_base], &argv[arg_base]);
525       }
526 
527       /* should never be reached */
528       exit(1);
529     }
530 
531   /* parent */
532   signal (SIGCHLD, sig_child);
533 
534   /* Note that we only set termios settings for standard input;
535    * the master side of a pty is NOT a tty.
536    */
537   tcgetattr(STDIN_FILENO, &orig_term);
538 
539   t = orig_term;
540   eof_char = t.c_cc[VEOF];
541   /*  add_special_char(t.c_cc[VEOF]);*/
542   add_special_char(t.c_cc[VINTR]);
543   add_special_char(t.c_cc[VQUIT]);
544   add_special_char(t.c_cc[VSUSP]);
545 #if defined (VDISCARD)
546   add_special_char(t.c_cc[VDISCARD]);
547 #endif
548 
549   t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
550 		 ECHOK | ECHONL
551 #if defined (ECHOKE)
552 		| ECHOKE
553 #endif
554 #if defined (ECHOPRT)
555 		| ECHOPRT
556 #endif
557 		);
558   t.c_iflag &= ~ICRNL;
559   t.c_iflag |= IGNBRK;
560   t.c_cc[VMIN] = 1;
561   t.c_cc[VTIME] = 0;
562   tcsetattr(STDIN_FILENO, TCSANOW, &t);
563   in_from_inferior_fd = master;
564   out_to_inferior_fd = master;
565   rl_instream = fdopen (master, "r");
566   rl_getc_function = my_rl_getc;
567 
568   rl_prep_term_function = null_prep_terminal;
569   rl_deprep_term_function = null_deprep_terminal;
570   rl_pre_input_hook = pre_input_change_mode;
571   rl_callback_handler_install (prompt, line_handler);
572 
573   in_from_tty_fd = STDIN_FILENO;
574   FD_ZERO (&in_set);
575   maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
576     : in_from_tty_fd;
577   for (;;)
578     {
579       int num;
580       FD_SET (in_from_inferior_fd, &in_set);
581       FD_SET (in_from_tty_fd, &in_set);
582 
583       num = select(maxfd+1, &in_set, NULL, NULL, NULL);
584 
585       if (propagate_sigwinch)
586 	{
587 	  struct winsize ws;
588 	  if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
589 	    {
590 	      ioctl (master, TIOCSWINSZ, &ws);
591 	    }
592 	  propagate_sigwinch = 0;
593 	  continue;
594 	}
595 
596       if (num <= 0)
597 	{
598 	  perror ("select");
599 	  exit (-1);
600 	}
601       if (FD_ISSET (in_from_tty_fd, &in_set))
602 	{
603 	  extern int _rl_echoing_p;
604 	  struct termios term_master;
605 	  int do_canon = 1;
606 	  int do_icrnl = 1;
607 	  int ioctl_ret;
608 
609 	  DPRINT1("[tty avail num_keys:%d]\n", num_keys);
610 
611 	  /* If we can't get tty modes for the master side of the pty, we
612 	     can't handle non-canonical-mode programs.  Always assume the
613 	     master is in canonical echo mode if we can't tell. */
614 	  ioctl_ret = tcgetattr(master, &term_master);
615 
616 	  if (ioctl_ret >= 0)
617 	    {
618 	      do_canon = (term_master.c_lflag & ICANON) != 0;
619 	      do_icrnl = (term_master.c_lflag & ICRNL) != 0;
620 	      _rl_echoing_p = (term_master.c_lflag & ECHO) != 0;
621 	      DPRINT1 ("echo,canon,crnl:%03d\n",
622 		       100 * _rl_echoing_p
623 		       + 10 * do_canon
624 		       + 1 * do_icrnl);
625 	    }
626 	  else
627 	    {
628 	      if (ioctl_err == 0)
629 		DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
630 	      ioctl_err = 1;
631 	    }
632 
633 	  if (do_canon == 0 && num_keys == 0)
634 	    {
635 	      char ch[10];
636 	      int count = read (STDIN_FILENO, ch, sizeof(ch));
637 	      DPRINT1("[read %d chars from stdin: ", count);
638 	      DPRINT2(" \"%.*s\"]\n", count, ch);
639 	      if (do_icrnl)
640 		{
641 		  int i = count;
642 		  while (--i >= 0)
643 		    {
644 		      if (ch[i] == '\r')
645 			ch[i] = '\n';
646 		    }
647 		}
648 	      maybe_emphasize_input (1);
649 	      write (out_to_inferior_fd, ch, count);
650 	    }
651 	  else
652 	    {
653 	      if (num_keys == 0)
654 		{
655 		  int i;
656 		  /* Re-install callback handler for new prompt. */
657 		  if (prompt != empty_string)
658 		    free (prompt);
659 		  if (prompt == NULL)
660 		    {
661 		      DPRINT0("New empty prompt\n");
662 		      prompt = empty_string;
663 		    }
664 		  else
665 		    {
666 		      if (do_emphasize_input && buf_count > 0)
667 			{
668 			  prompt = malloc (buf_count + strlen (end_input_mode)
669 					   + strlen (start_input_mode) + 5);
670 			  sprintf (prompt, "\001%s\002%.*s\001%s\002",
671 				   end_input_mode,
672 				   buf_count, buf,
673 				   start_input_mode);
674 			}
675 		      else
676 			{
677 			  prompt = malloc (buf_count + 1);
678 			  memcpy (prompt, buf, buf_count);
679 			  prompt[buf_count] = '\0';
680 			}
681 		      DPRINT1("New prompt '%s'\n", prompt);
682 #if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED */
683 		      /* Doesn't quite work when do_emphasize_input is 1. */
684 		      rl_already_prompted = buf_count > 0;
685 #else
686 		      if (buf_count > 0)
687 			write (1, "\r", 1);
688 #endif
689 		    }
690 
691 		  rl_callback_handler_install (prompt, line_handler);
692 		}
693 	      num_keys++;
694 	      maybe_emphasize_input (1);
695 	      rl_callback_read_char ();
696 	    }
697 	}
698       else /* output from inferior. */
699 	{
700 	  int i;
701 	  int count;
702 	  int old_count;
703 	  if (buf_count > (sizeof(buf) >> 2))
704 	    buf_count = 0;
705 	  count = read (in_from_inferior_fd, buf+buf_count,
706 			sizeof(buf) - buf_count);
707           DPRINT2("read %d from inferior, buf_count=%d", count, buf_count);
708 	  DPRINT2(": \"%.*s\"", count, buf+buf_count);
709 	  maybe_emphasize_input (0);
710 	  if (count <= 0)
711 	    {
712 	      DPRINT0 ("(Connection closed by foreign host.)\n");
713 	      tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
714 	      exit (0);
715 	    }
716 	  old_count = buf_count;
717 
718           /* Look for any pending echo that we need to suppress. */
719 	  while (echo_suppress_start < echo_suppress_limit
720 		 && count > 0
721 		 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
722 	    {
723 	      count--;
724 	      buf_count++;
725 	      echo_suppress_start++;
726 	    }
727 	  DPRINT1("suppressed %d characters of echo.\n", buf_count-old_count);
728 
729           /* Write to the terminal anything that was not suppressed. */
730           if (count > 0)
731             write (1, buf + buf_count, count);
732 
733           /* Finally, look for a prompt candidate.
734            * When we get around to going input (from the keyboard),
735            * we will consider the prompt to be anything since the last
736            * line terminator.  So we need to save that text in the
737            * initial part of buf.  However, anything before the
738            * most recent end-of-line is not interesting. */
739 	  buf_count += count;
740 #if 1
741 	  for (i = buf_count;  --i >= old_count; )
742 #else
743 	  for (i = buf_count - 1;  i-- >= buf_count - count; )
744 #endif
745 	    {
746 	      if (buf[i] == '\n' || buf[i] == '\r')
747 		{
748 		  i++;
749 		  memmove (buf, buf+i, buf_count - i);
750 		  buf_count -= i;
751 		  break;
752 		}
753 	    }
754 	  DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
755 	}
756     }
757 }
758 
set_edit_mode()759 static void set_edit_mode ()
760 {
761   int vi = 0;
762   char *shellopts;
763 
764   shellopts = getenv ("SHELLOPTS");
765   while (shellopts != 0)
766     {
767       if (strncmp ("vi", shellopts, 2) == 0)
768 	{
769 	  vi = 1;
770 	  break;
771 	}
772       shellopts = strchr (shellopts + 1, ':');
773     }
774 
775   if (!vi)
776     {
777       if (getenv ("EDITOR") != 0)
778 	vi |= strcmp (getenv ("EDITOR"), "vi") == 0;
779     }
780 
781   if (vi)
782     rl_variable_bind ("editing-mode", "vi");
783   else
784     rl_variable_bind ("editing-mode", "emacs");
785 }
786 
787 
usage_exit()788 static void usage_exit ()
789 {
790   fprintf (stderr, "Usage: rlfe [-h histfile] [-s size] cmd [arg1] [arg2] ...\n\n");
791   exit (1);
792 }
793