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>
77 #ifdef READLINE_LIBRARY
78 # include "readline.h"
81 # include <readline/readline.h>
82 # include <readline/history.h>
86 #define COMMAND "/bin/bash"
89 #define COMMAND_ARGS COMMAND
93 #define ALT_COMMAND "/bin/sh"
95 #ifndef ALT_COMMAND_ARGS
96 #define ALT_COMMAND_ARGS ALT_COMMAND
101 # define memmove(d, s, n) __builtin_memcpy(d, s, n)
103 # define memmove(d, s, n) memcpy(d, s, n)
106 # define memmove(d, s, n) memcpy(d, s, n)
109 #define APPLICATION_NAME "rlfe"
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;
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;
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))
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 */
146 struct termios orig_term;
148 /* Pid of child process. */
149 static pid_t child = -1;
152 sig_child (int signo)
158 write_history (hist_file);
160 history_truncate_file (hist_file, hist_size);
162 DPRINT0 ("(Child process died.)\n");
163 tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
167 volatile int propagate_sigwinch = 0;
170 * propagate window size changes from input file descriptor to
171 * master side of pty.
173 void sigwinch_handler(int signal) {
174 propagate_sigwinch = 1;
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.
183 int get_slave_pty(char *name) {
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
193 if ((gptr = getgrnam("tty")) != 0) {
196 /* if the tty group does not exist, don't change the
197 * group on the slave pty, only the owner
202 /* Note that we do not check for errors here. If this is code
203 * where these actions are critical, check for errors!
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.
211 chmod(name, S_IRUSR|S_IWUSR);
213 /* open the corresponding slave pty */
214 slave = open(name, O_RDWR);
218 /* Certain special characters, such as ctrl/C, we want to pass directly
219 to the inferior, rather than letting readline handle them. */
221 static char special_chars[20];
222 static int special_chars_count;
225 add_special_char(int ch)
228 special_chars[special_chars_count++] = ch;
234 is_special_char(int ch)
238 if (ch == eof_char && rl_point == rl_end)
241 for (i = special_chars_count; --i >= 0; )
242 if (special_chars[i] == ch)
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;
252 int do_emphasize_input = 1;
253 int current_emphasize_input;
255 char *start_input_mode = "\033[1m";
256 char *end_input_mode = "\033[0m";
260 static void maybe_emphasize_input (int on)
262 if (on == current_emphasize_input
263 || (on && ! do_emphasize_input))
265 fprintf (rl_outstream, on ? start_input_mode : end_input_mode);
266 fflush (rl_outstream);
267 current_emphasize_input = on;
271 null_prep_terminal (int meta)
276 null_deprep_terminal ()
278 maybe_emphasize_input (0);
282 pre_input_change_mode (void)
287 char pending_special_char;
290 line_handler (char *line)
295 DPRINT0("saw eof!\n");
296 buf[0] = '\004'; /* ctrl/d */
297 write (out_to_inferior_fd, buf, 1);
301 static char enter[] = "\r";
302 /* Send line to inferior: */
303 int length = strlen (line);
304 if (length > ECHO_SUPPRESS_MAX-2)
306 echo_suppress_start = 0;
307 echo_suppress_limit = 0;
311 if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
313 if (echo_suppress_limit - echo_suppress_start + length
314 <= ECHO_SUPPRESS_MAX - 2)
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;
324 echo_suppress_limit = 0;
326 echo_suppress_start = 0;
328 memcpy (echo_suppress_buffer + echo_suppress_limit,
330 echo_suppress_limit += length;
331 echo_suppress_buffer[echo_suppress_limit++] = '\r';
332 echo_suppress_buffer[echo_suppress_limit++] = '\n';
334 write (out_to_inferior_fd, line, length);
335 if (pending_special_char == 0)
337 write (out_to_inferior_fd, enter, sizeof(enter)-1);
343 rl_callback_handler_remove ();
346 if (pending_special_char != 0)
348 write (out_to_inferior_fd, &pending_special_char, 1);
349 pending_special_char = 0;
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). */
358 my_rl_getc (FILE *dummy)
360 int ch = rl_getc (stdin);
361 if (is_special_char (ch))
363 pending_special_char = ch;
370 main(int argc, char** argv)
377 struct sigaction act;
382 static char empty_string[1] = "";
383 char *prompt = empty_string;
388 logfile = fopen("/tmp/rlfe.log", "w");
391 while (arg_base<argc)
393 if (argv[arg_base][0] != '-')
395 if (arg_base+1 >= argc )
397 switch(argv[arg_base][1])
401 hist_file = argv[arg_base];
405 hist_size = atoi(argv[arg_base]);
415 read_history (hist_file);
419 rl_readline_name = APPLICATION_NAME;
421 if ((master = OpenPTY (&name)) < 0)
423 perror("ptypair: could not open master pty");
427 DPRINT1("pty name: '%s'\n", name);
429 /* set up SIGWINCH handler */
430 act.sa_handler = sigwinch_handler;
431 sigemptyset(&(act.sa_mask));
433 if (sigaction(SIGWINCH, &act, NULL) < 0)
435 perror("ptypair: could not handle SIGWINCH ");
439 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
441 perror("ptypair: could not get window size");
445 if ((child = fork()) < 0)
447 perror("cannot fork");
453 int slave; /* file descriptor for slave pty */
455 /* We are in the child process */
459 if ((slave = get_slave_pty(name)) < 0)
461 perror("ptypair: could not open slave pty");
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
475 perror("could not set session leader");
478 /* Tie us to our new controlling tty. */
480 if (ioctl(slave, TIOCSCTTY, NULL))
482 perror("could not set new controlling tty");
485 if ((slave = get_slave_pty(name)) < 0)
487 perror("ptypair: could not open slave pty");
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);
497 /* at this point the slave pty should be standard input */
503 /* Try to restore window size; failure isn't critical */
504 if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
506 perror("could not restore window size");
509 /* now start the shell */
511 static char* command_args[] = { COMMAND_ARGS, NULL };
512 static char* alt_command_args[] = { ALT_COMMAND_ARGS, NULL };
515 execvp (COMMAND, command_args);
516 execvp (ALT_COMMAND, alt_command_args);
519 execvp (argv[arg_base], &argv[arg_base]);
522 /* should never be reached */
527 signal (SIGCHLD, sig_child);
529 /* Note that we only set termios settings for standard input;
530 * the master side of a pty is NOT a tty.
532 tcgetattr(STDIN_FILENO, &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]);
544 t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
545 ECHOK | ECHOKE | ECHONL | ECHOPRT );
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;
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);
561 in_from_tty_fd = STDIN_FILENO;
563 maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
568 FD_SET (in_from_inferior_fd, &in_set);
569 FD_SET (in_from_tty_fd, &in_set);
571 num = select(maxfd+1, &in_set, NULL, NULL, NULL);
573 if (propagate_sigwinch)
576 if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
578 ioctl (master, TIOCSWINSZ, &ws);
580 propagate_sigwinch = 0;
589 if (FD_ISSET (in_from_tty_fd, &in_set))
591 extern int readline_echoing_p;
592 struct termios term_master;
597 DPRINT1("[tty avail num_keys:%d]\n", num_keys);
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);
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
617 DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
621 if (do_canon == 0 && num_keys == 0)
624 int count = read (STDIN_FILENO, ch, sizeof(ch));
625 DPRINT1("[read %d chars from stdin: ", count);
626 DPRINT2(" \"%.*s\"]\n", count, ch);
636 maybe_emphasize_input (1);
637 write (out_to_inferior_fd, ch, count);
644 /* Re-install callback handler for new prompt. */
645 if (prompt != empty_string)
649 DPRINT0("New empty prompt\n");
650 prompt = empty_string;
654 if (do_emphasize_input && buf_count > 0)
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",
665 prompt = malloc (buf_count + 1);
666 memcpy (prompt, buf, buf_count);
667 prompt[buf_count] = '\0';
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;
679 rl_callback_handler_install (prompt, line_handler);
682 maybe_emphasize_input (1);
683 rl_callback_read_char ();
686 else /* output from inferior. */
691 if (buf_count > (sizeof(buf) >> 2))
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);
700 DPRINT0 ("(Connection closed by foreign host.)\n");
701 tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
704 old_count = buf_count;
706 /* Look for any pending echo that we need to suppress. */
707 while (echo_suppress_start < echo_suppress_limit
709 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
713 echo_suppress_start++;
715 DPRINT1("suppressed %d characters of echo.\n", buf_count-old_count);
717 /* Write to the terminal anything that was not suppressed. */
719 write (1, buf + buf_count, count);
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. */
729 for (i = buf_count; --i >= old_count; )
731 for (i = buf_count - 1; i-- >= buf_count - count; )
734 if (buf[i] == '\n' || buf[i] == '\r')
737 memmove (buf, buf+i, buf_count - i);
742 DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
747 static void set_edit_mode ()
752 shellopts = getenv ("SHELLOPTS");
753 while (shellopts != 0)
755 if (strncmp ("vi", shellopts, 2) == 0)
760 shellopts = index (shellopts + 1, ':');
765 if (getenv ("EDITOR") != 0)
766 vi |= strcmp (getenv ("EDITOR"), "vi") == 0;
770 rl_variable_bind ("editing-mode", "vi");
772 rl_variable_bind ("editing-mode", "emacs");
776 static void usage_exit ()
778 fprintf (stderr, "Usage: rlfe [-h histfile] [-s size] cmd [arg1] [arg2] ...\n\n");