Annotation of embedaddon/readline/examples/rlfe/rlfe.c, revision 1.1.1.1
1.1 misho 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
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: */
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:
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
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
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:
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
276: null_prep_terminal (int meta)
277: {
278: }
279:
280: static void
281: null_deprep_terminal ()
282: {
283: maybe_emphasize_input (0);
284: }
285:
286: static int
287: pre_input_change_mode (void)
288: {
289: return 0;
290: }
291:
292: char pending_special_char;
293:
294: static void
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
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
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:
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:
788: static void usage_exit ()
789: {
790: fprintf (stderr, "Usage: rlfe [-h histfile] [-s size] cmd [arg1] [arg2] ...\n\n");
791: exit (1);
792: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>