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