1 /* A front-end using readline to "cook" input lines.
3 * Copyright (C) 2004, 1999 Per Bothner
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)
10 * Some code from Johnson & Troan: "Linux Application Development"
11 * (Addison-Wesley, 1998) was used directly or for inspiration.
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.
20 * Only tested under GNU/Linux and Mac OS 10.x; needs to be ported.
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.
29 * When running mc -c under the Linux console, mc does not recognize
30 * mouse clicks, which mc does when not running under rlfe.
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.
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.
41 * The latest readline may have some hooks we can use to avoid having
42 * to back up the prompt. (See HAVE_ALREADY_PROMPTED.)
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.
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.
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.
60 #include <sys/types.h>
61 #include <sys/socket.h>
62 #include <netinet/in.h>
63 #include <arpa/inet.h>
72 #include <sys/ioctl.h>
78 #if defined (HAVE_SYS_WAIT_H)
79 # include <sys/wait.h>
82 #ifdef READLINE_LIBRARY
83 # include "readline.h"
86 # include <readline/readline.h>
87 # include <readline/history.h>
91 #define COMMAND "/bin/bash"
94 #define COMMAND_ARGS COMMAND
98 #define ALT_COMMAND "/bin/sh"
100 #ifndef ALT_COMMAND_ARGS
101 #define ALT_COMMAND_ARGS ALT_COMMAND
106 # define memmove(d, s, n) __builtin_memcpy(d, s, n)
108 # define memmove(d, s, n) memcpy(d, s, n)
111 # define memmove(d, s, n) memcpy(d, s, n)
114 #define APPLICATION_NAME "rlfe"
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;
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;
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))
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 */
151 struct termios orig_term;
153 /* Pid of child process. */
154 static pid_t child = -1;
157 sig_child (int signo)
163 write_history (hist_file);
165 history_truncate_file (hist_file, hist_size);
167 DPRINT0 ("(Child process died.)\n");
168 tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
172 volatile int propagate_sigwinch = 0;
175 * propagate window size changes from input file descriptor to
176 * master side of pty.
178 void sigwinch_handler(int signal) {
179 propagate_sigwinch = 1;
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.
188 int get_slave_pty(char *name) {
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
198 if ((gptr = getgrnam("tty")) != 0) {
201 /* if the tty group does not exist, don't change the
202 * group on the slave pty, only the owner
207 /* Note that we do not check for errors here. If this is code
208 * where these actions are critical, check for errors!
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.
216 chmod(name, S_IRUSR|S_IWUSR);
218 /* open the corresponding slave pty */
219 slave = open(name, O_RDWR);
223 /* Certain special characters, such as ctrl/C, we want to pass directly
224 to the inferior, rather than letting readline handle them. */
226 static char special_chars[20];
227 static int special_chars_count;
230 add_special_char(int ch)
233 special_chars[special_chars_count++] = ch;
239 is_special_char(int ch)
243 if (ch == eof_char && rl_point == rl_end)
246 for (i = special_chars_count; --i >= 0; )
247 if (special_chars[i] == ch)
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;
257 int do_emphasize_input = 1;
258 int current_emphasize_input;
260 char *start_input_mode = "\033[1m";
261 char *end_input_mode = "\033[0m";
265 static void maybe_emphasize_input (int on)
267 if (on == current_emphasize_input
268 || (on && ! do_emphasize_input))
270 fprintf (rl_outstream, on ? start_input_mode : end_input_mode);
271 fflush (rl_outstream);
272 current_emphasize_input = on;
276 null_prep_terminal (int meta)
281 null_deprep_terminal ()
283 maybe_emphasize_input (0);
287 pre_input_change_mode (void)
292 char pending_special_char;
295 line_handler (char *line)
300 DPRINT0("saw eof!\n");
301 buf[0] = '\004'; /* ctrl/d */
302 write (out_to_inferior_fd, buf, 1);
306 static char enter[] = "\r";
307 /* Send line to inferior: */
308 int length = strlen (line);
309 if (length > ECHO_SUPPRESS_MAX-2)
311 echo_suppress_start = 0;
312 echo_suppress_limit = 0;
316 if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
318 if (echo_suppress_limit - echo_suppress_start + length
319 <= ECHO_SUPPRESS_MAX - 2)
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;
329 echo_suppress_limit = 0;
331 echo_suppress_start = 0;
333 memcpy (echo_suppress_buffer + echo_suppress_limit,
335 echo_suppress_limit += length;
336 echo_suppress_buffer[echo_suppress_limit++] = '\r';
337 echo_suppress_buffer[echo_suppress_limit++] = '\n';
339 write (out_to_inferior_fd, line, length);
340 if (pending_special_char == 0)
342 write (out_to_inferior_fd, enter, sizeof(enter)-1);
348 rl_callback_handler_remove ();
351 if (pending_special_char != 0)
353 write (out_to_inferior_fd, &pending_special_char, 1);
354 pending_special_char = 0;
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). */
363 my_rl_getc (FILE *dummy)
365 int ch = rl_getc (stdin);
366 if (is_special_char (ch))
368 pending_special_char = ch;
375 main(int argc, char** argv)
382 struct sigaction act;
387 static char empty_string[1] = "";
388 char *prompt = empty_string;
393 logfile = fopen("/tmp/rlfe.log", "w");
396 while (arg_base<argc)
398 if (argv[arg_base][0] != '-')
400 if (arg_base+1 >= argc )
402 switch(argv[arg_base][1])
406 hist_file = argv[arg_base];
410 hist_size = atoi(argv[arg_base]);
420 read_history (hist_file);
424 rl_readline_name = APPLICATION_NAME;
426 if ((master = OpenPTY (&name)) < 0)
428 perror("ptypair: could not open master pty");
432 DPRINT1("pty name: '%s'\n", name);
434 /* set up SIGWINCH handler */
435 act.sa_handler = sigwinch_handler;
436 sigemptyset(&(act.sa_mask));
438 if (sigaction(SIGWINCH, &act, NULL) < 0)
440 perror("ptypair: could not handle SIGWINCH ");
444 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
446 perror("ptypair: could not get window size");
450 if ((child = fork()) < 0)
452 perror("cannot fork");
458 int slave; /* file descriptor for slave pty */
460 /* We are in the child process */
464 if ((slave = get_slave_pty(name)) < 0)
466 perror("ptypair: could not open slave pty");
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
480 perror("could not set session leader");
483 /* Tie us to our new controlling tty. */
485 if (ioctl(slave, TIOCSCTTY, NULL))
487 perror("could not set new controlling tty");
490 if ((slave = get_slave_pty(name)) < 0)
492 perror("ptypair: could not open slave pty");
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);
502 /* at this point the slave pty should be standard input */
508 /* Try to restore window size; failure isn't critical */
509 if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
511 perror("could not restore window size");
514 /* now start the shell */
516 static char* command_args[] = { COMMAND_ARGS, NULL };
517 static char* alt_command_args[] = { ALT_COMMAND_ARGS, NULL };
520 execvp (COMMAND, command_args);
521 execvp (ALT_COMMAND, alt_command_args);
524 execvp (argv[arg_base], &argv[arg_base]);
527 /* should never be reached */
532 signal (SIGCHLD, sig_child);
534 /* Note that we only set termios settings for standard input;
535 * the master side of a pty is NOT a tty.
537 tcgetattr(STDIN_FILENO, &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]);
549 t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
554 #if defined (ECHOPRT)
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;
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);
573 in_from_tty_fd = STDIN_FILENO;
575 maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
580 FD_SET (in_from_inferior_fd, &in_set);
581 FD_SET (in_from_tty_fd, &in_set);
583 num = select(maxfd+1, &in_set, NULL, NULL, NULL);
585 if (propagate_sigwinch)
588 if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
590 ioctl (master, TIOCSWINSZ, &ws);
592 propagate_sigwinch = 0;
601 if (FD_ISSET (in_from_tty_fd, &in_set))
603 extern int _rl_echoing_p;
604 struct termios term_master;
609 DPRINT1("[tty avail num_keys:%d]\n", num_keys);
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);
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",
629 DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
633 if (do_canon == 0 && num_keys == 0)
636 int count = read (STDIN_FILENO, ch, sizeof(ch));
637 DPRINT1("[read %d chars from stdin: ", count);
638 DPRINT2(" \"%.*s\"]\n", count, ch);
648 maybe_emphasize_input (1);
649 write (out_to_inferior_fd, ch, count);
656 /* Re-install callback handler for new prompt. */
657 if (prompt != empty_string)
661 DPRINT0("New empty prompt\n");
662 prompt = empty_string;
666 if (do_emphasize_input && buf_count > 0)
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",
677 prompt = malloc (buf_count + 1);
678 memcpy (prompt, buf, buf_count);
679 prompt[buf_count] = '\0';
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;
691 rl_callback_handler_install (prompt, line_handler);
694 maybe_emphasize_input (1);
695 rl_callback_read_char ();
698 else /* output from inferior. */
703 if (buf_count > (sizeof(buf) >> 2))
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);
712 DPRINT0 ("(Connection closed by foreign host.)\n");
713 tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
716 old_count = buf_count;
718 /* Look for any pending echo that we need to suppress. */
719 while (echo_suppress_start < echo_suppress_limit
721 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
725 echo_suppress_start++;
727 DPRINT1("suppressed %d characters of echo.\n", buf_count-old_count);
729 /* Write to the terminal anything that was not suppressed. */
731 write (1, buf + buf_count, count);
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. */
741 for (i = buf_count; --i >= old_count; )
743 for (i = buf_count - 1; i-- >= buf_count - count; )
746 if (buf[i] == '\n' || buf[i] == '\r')
749 memmove (buf, buf+i, buf_count - i);
754 DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
759 static void set_edit_mode ()
764 shellopts = getenv ("SHELLOPTS");
765 while (shellopts != 0)
767 if (strncmp ("vi", shellopts, 2) == 0)
772 shellopts = strchr (shellopts + 1, ':');
777 if (getenv ("EDITOR") != 0)
778 vi |= strcmp (getenv ("EDITOR"), "vi") == 0;
782 rl_variable_bind ("editing-mode", "vi");
784 rl_variable_bind ("editing-mode", "emacs");
788 static void usage_exit ()
790 fprintf (stderr, "Usage: rlfe [-h histfile] [-s size] cmd [arg1] [arg2] ...\n\n");