import of readlilne 5.1
[deliverable/binutils-gdb.git] / readline / 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 }
This page took 0.061459 seconds and 4 git commands to generate.