eacd5abbb7bc1350f3a2cae661809ae597148582
[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 #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 | ECHOKE | ECHONL | ECHOPRT );
551 t.c_iflag &= ~ICRNL;
552 t.c_iflag |= IGNBRK;
553 t.c_cc[VMIN] = 1;
554 t.c_cc[VTIME] = 0;
555 tcsetattr(STDIN_FILENO, TCSANOW, &t);
556 in_from_inferior_fd = master;
557 out_to_inferior_fd = master;
558 rl_instream = fdopen (master, "r");
559 rl_getc_function = my_rl_getc;
560
561 rl_prep_term_function = null_prep_terminal;
562 rl_deprep_term_function = null_deprep_terminal;
563 rl_pre_input_hook = pre_input_change_mode;
564 rl_callback_handler_install (prompt, line_handler);
565
566 in_from_tty_fd = STDIN_FILENO;
567 FD_ZERO (&in_set);
568 maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
569 : in_from_tty_fd;
570 for (;;)
571 {
572 int num;
573 FD_SET (in_from_inferior_fd, &in_set);
574 FD_SET (in_from_tty_fd, &in_set);
575
576 num = select(maxfd+1, &in_set, NULL, NULL, NULL);
577
578 if (propagate_sigwinch)
579 {
580 struct winsize ws;
581 if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
582 {
583 ioctl (master, TIOCSWINSZ, &ws);
584 }
585 propagate_sigwinch = 0;
586 continue;
587 }
588
589 if (num <= 0)
590 {
591 perror ("select");
592 exit (-1);
593 }
594 if (FD_ISSET (in_from_tty_fd, &in_set))
595 {
596 extern int _rl_echoing_p;
597 struct termios term_master;
598 int do_canon = 1;
599 int do_icrnl = 1;
600 int ioctl_ret;
601
602 DPRINT1("[tty avail num_keys:%d]\n", num_keys);
603
604 /* If we can't get tty modes for the master side of the pty, we
605 can't handle non-canonical-mode programs. Always assume the
606 master is in canonical echo mode if we can't tell. */
607 ioctl_ret = tcgetattr(master, &term_master);
608
609 if (ioctl_ret >= 0)
610 {
611 do_canon = (term_master.c_lflag & ICANON) != 0;
612 do_icrnl = (term_master.c_lflag & ICRNL) != 0;
613 _rl_echoing_p = (term_master.c_lflag & ECHO) != 0;
614 DPRINT1 ("echo,canon,crnl:%03d\n",
615 100 * _rl_echoing_p
616 + 10 * do_canon
617 + 1 * do_icrnl);
618 }
619 else
620 {
621 if (ioctl_err == 0)
622 DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
623 ioctl_err = 1;
624 }
625
626 if (do_canon == 0 && num_keys == 0)
627 {
628 char ch[10];
629 int count = read (STDIN_FILENO, ch, sizeof(ch));
630 DPRINT1("[read %d chars from stdin: ", count);
631 DPRINT2(" \"%.*s\"]\n", count, ch);
632 if (do_icrnl)
633 {
634 int i = count;
635 while (--i >= 0)
636 {
637 if (ch[i] == '\r')
638 ch[i] = '\n';
639 }
640 }
641 maybe_emphasize_input (1);
642 write (out_to_inferior_fd, ch, count);
643 }
644 else
645 {
646 if (num_keys == 0)
647 {
648 int i;
649 /* Re-install callback handler for new prompt. */
650 if (prompt != empty_string)
651 free (prompt);
652 if (prompt == NULL)
653 {
654 DPRINT0("New empty prompt\n");
655 prompt = empty_string;
656 }
657 else
658 {
659 if (do_emphasize_input && buf_count > 0)
660 {
661 prompt = malloc (buf_count + strlen (end_input_mode)
662 + strlen (start_input_mode) + 5);
663 sprintf (prompt, "\001%s\002%.*s\001%s\002",
664 end_input_mode,
665 buf_count, buf,
666 start_input_mode);
667 }
668 else
669 {
670 prompt = malloc (buf_count + 1);
671 memcpy (prompt, buf, buf_count);
672 prompt[buf_count] = '\0';
673 }
674 DPRINT1("New prompt '%s'\n", prompt);
675 #if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED */
676 /* Doesn't quite work when do_emphasize_input is 1. */
677 rl_already_prompted = buf_count > 0;
678 #else
679 if (buf_count > 0)
680 write (1, "\r", 1);
681 #endif
682 }
683
684 rl_callback_handler_install (prompt, line_handler);
685 }
686 num_keys++;
687 maybe_emphasize_input (1);
688 rl_callback_read_char ();
689 }
690 }
691 else /* output from inferior. */
692 {
693 int i;
694 int count;
695 int old_count;
696 if (buf_count > (sizeof(buf) >> 2))
697 buf_count = 0;
698 count = read (in_from_inferior_fd, buf+buf_count,
699 sizeof(buf) - buf_count);
700 DPRINT2("read %d from inferior, buf_count=%d", count, buf_count);
701 DPRINT2(": \"%.*s\"", count, buf+buf_count);
702 maybe_emphasize_input (0);
703 if (count <= 0)
704 {
705 DPRINT0 ("(Connection closed by foreign host.)\n");
706 tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
707 exit (0);
708 }
709 old_count = buf_count;
710
711 /* Look for any pending echo that we need to suppress. */
712 while (echo_suppress_start < echo_suppress_limit
713 && count > 0
714 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
715 {
716 count--;
717 buf_count++;
718 echo_suppress_start++;
719 }
720 DPRINT1("suppressed %d characters of echo.\n", buf_count-old_count);
721
722 /* Write to the terminal anything that was not suppressed. */
723 if (count > 0)
724 write (1, buf + buf_count, count);
725
726 /* Finally, look for a prompt candidate.
727 * When we get around to going input (from the keyboard),
728 * we will consider the prompt to be anything since the last
729 * line terminator. So we need to save that text in the
730 * initial part of buf. However, anything before the
731 * most recent end-of-line is not interesting. */
732 buf_count += count;
733 #if 1
734 for (i = buf_count; --i >= old_count; )
735 #else
736 for (i = buf_count - 1; i-- >= buf_count - count; )
737 #endif
738 {
739 if (buf[i] == '\n' || buf[i] == '\r')
740 {
741 i++;
742 memmove (buf, buf+i, buf_count - i);
743 buf_count -= i;
744 break;
745 }
746 }
747 DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
748 }
749 }
750 }
751
752 static void set_edit_mode ()
753 {
754 int vi = 0;
755 char *shellopts;
756
757 shellopts = getenv ("SHELLOPTS");
758 while (shellopts != 0)
759 {
760 if (strncmp ("vi", shellopts, 2) == 0)
761 {
762 vi = 1;
763 break;
764 }
765 shellopts = strchr (shellopts + 1, ':');
766 }
767
768 if (!vi)
769 {
770 if (getenv ("EDITOR") != 0)
771 vi |= strcmp (getenv ("EDITOR"), "vi") == 0;
772 }
773
774 if (vi)
775 rl_variable_bind ("editing-mode", "vi");
776 else
777 rl_variable_bind ("editing-mode", "emacs");
778 }
779
780
781 static void usage_exit ()
782 {
783 fprintf (stderr, "Usage: rlfe [-h histfile] [-s size] cmd [arg1] [arg2] ...\n\n");
784 exit (1);
785 }
This page took 0.079108 seconds and 3 git commands to generate.