xref: /openbsd-src/gnu/lib/libreadline/examples/rlfe.c (revision b2ea75c1b17e1a9a339660e7ed45cd24946b230e)
1 /* A front-end using readline to "cook" input lines for Kawa.
2  *
3  * Copyright (C) 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 
14 /* PROBLEMS/TODO:
15  *
16  * Only tested under Linux;  needs to be ported.
17  *
18  * When running mc -c under the Linux console, mc does not recognize
19  * mouse clicks, which mc does when not running under fep.
20  *
21  * Pasting selected text containing tabs is like hitting the tab character,
22  * which invokes readline completion.  We don't want this.  I don't know
23  * if this is fixable without integrating fep into a terminal emulator.
24  *
25  * Echo suppression is a kludge, but can only be avoided with better kernel
26  * support: We need a tty mode to disable "real" echoing, while still
27  * letting the inferior think its tty driver to doing echoing.
28  * Stevens's book claims SCR$ and BSD4.3+ have TIOCREMOTE.
29  *
30  * The latest readline may have some hooks we can use to avoid having
31  * to back up the prompt.
32  *
33  * Desirable readline feature:  When in cooked no-echo mode (e.g. password),
34  * echo characters are they are types with '*', but remove them when done.
35  *
36  * A synchronous output while we're editing an input line should be
37  * inserted in the output view *before* the input line, so that the
38  * lines being edited (with the prompt) float at the end of the input.
39  *
40  * A "page mode" option to emulate more/less behavior:  At each page of
41  * output, pause for a user command.  This required parsing the output
42  * to keep track of line lengths.  It also requires remembering the
43  * output, if we want an option to scroll back, which suggests that
44  * this should be integrated with a terminal emulator like xterm.
45  */
46 
47 #ifdef HAVE_CONFIG_H
48 #  include <config.h>
49 #endif
50 
51 #include <stdio.h>
52 #include <fcntl.h>
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
57 #include <signal.h>
58 #include <netdb.h>
59 #include <stdlib.h>
60 #include <errno.h>
61 #include <grp.h>
62 #include <string.h>
63 #include <sys/stat.h>
64 #include <unistd.h>
65 #include <sys/ioctl.h>
66 #include <termios.h>
67 
68 #ifdef READLINE_LIBRARY
69 #  include "readline.h"
70 #  include "history.h"
71 #else
72 #  include <readline/readline.h>
73 #  include <readline/history.h>
74 #endif
75 
76 #ifndef COMMAND
77 #define COMMAND "/bin/sh"
78 #endif
79 #ifndef COMMAND_ARGS
80 #define COMMAND_ARGS COMMAND
81 #endif
82 
83 #ifndef HAVE_MEMMOVE
84 #  if __GNUC__ > 1
85 #    define memmove(d, s, n)	__builtin_memcpy(d, s, n)
86 #  else
87 #    define memmove(d, s, n)	memcpy(d, s, n)
88 #  endif
89 #else
90 #  define memmove(d, s, n)	memcpy(d, s, n)
91 #endif
92 
93 #define APPLICATION_NAME "Fep"
94 
95 static int in_from_inferior_fd;
96 static int out_to_inferior_fd;
97 
98 /* Unfortunately, we cannot safely display echo from the inferior process.
99    The reason is that the echo bit in the pty is "owned" by the inferior,
100    and if we try to turn it off, we could confuse the inferior.
101    Thus, when echoing, we get echo twice:  First readline echoes while
102    we're actually editing. Then we send the line to the inferior, and the
103    terminal driver send back an extra echo.
104    The work-around is to remember the input lines, and when we see that
105    line come back, we supress the output.
106    A better solution (supposedly available on SVR4) would be a smarter
107    terminal driver, with more flags ... */
108 #define ECHO_SUPPRESS_MAX 1024
109 char echo_suppress_buffer[ECHO_SUPPRESS_MAX];
110 int echo_suppress_start = 0;
111 int echo_suppress_limit = 0;
112 
113 #define DEBUG
114 
115 #ifdef DEBUG
116 FILE *logfile = NULL;
117 #define DPRINT0(FMT) (fprintf(logfile, FMT), fflush(logfile))
118 #define DPRINT1(FMT, V1) (fprintf(logfile, FMT, V1), fflush(logfile))
119 #define DPRINT2(FMT, V1, V2) (fprintf(logfile, FMT, V1, V2), fflush(logfile))
120 #else
121 #define DPRINT0(FMT) /* Do nothing */
122 #define DPRINT1(FMT, V1) /* Do nothing */
123 #define DPRINT2(FMT, V1, V2) /* Do nothing */
124 #endif
125 
126 struct termios orig_term;
127 
128 /* Pid of child process. */
129 static pid_t child = -1;
130 
131 static void
132 sig_child (int signo)
133 {
134   int status;
135   wait (&status);
136   DPRINT0 ("(Child process died.)\n");
137   tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
138   exit (0);
139 }
140 
141 volatile int propagate_sigwinch = 0;
142 
143 /* sigwinch_handler
144  * propagate window size changes from input file descriptor to
145  * master side of pty.
146  */
147 void sigwinch_handler(int signal) {
148    propagate_sigwinch = 1;
149 }
150 
151 /* get_master_pty() takes a double-indirect character pointer in which
152  * to put a slave name, and returns an integer file descriptor.
153  * If it returns < 0, an error has occurred.
154  * Otherwise, it has returned the master pty file descriptor, and fills
155  * in *name with the name of the corresponding slave pty.
156  * Once the slave pty has been opened, you are responsible to free *name.
157  */
158 
159 int get_master_pty(char **name) {
160    int i, j;
161    /* default to returning error */
162    int master = -1;
163 
164    /* create a dummy name to fill in */
165    *name = strdup("/dev/ptyXX");
166 
167    /* search for an unused pty */
168    for (i=0; i<16 && master <= 0; i++) {
169       for (j=0; j<16 && master <= 0; j++) {
170          (*name)[5] = 'p';
171          (*name)[8] = "pqrstuvwxyzPQRST"[i];
172          (*name)[9] = "0123456789abcdef"[j];
173          /* open the master pty */
174          if ((master = open(*name, O_RDWR)) < 0) {
175             if (errno == ENOENT) {
176                /* we are out of pty devices */
177                free (*name);
178                return (master);
179             }
180          }
181          else {
182            /* By substituting a letter, we change the master pty
183             * name into the slave pty name.
184             */
185            (*name)[5] = 't';
186            if (access(*name, R_OK|W_OK) != 0)
187              {
188                close(master);
189                master = -1;
190              }
191          }
192       }
193    }
194    if ((master < 0) && (i == 16) && (j == 16)) {
195       /* must have tried every pty unsuccessfully */
196       free (*name);
197       return (master);
198    }
199 
200    (*name)[5] = 't';
201 
202    return (master);
203 }
204 
205 /* get_slave_pty() returns an integer file descriptor.
206  * If it returns < 0, an error has occurred.
207  * Otherwise, it has returned the slave file descriptor.
208  */
209 
210 int get_slave_pty(char *name) {
211    struct group *gptr;
212    gid_t gid;
213    int slave = -1;
214 
215    /* chown/chmod the corresponding pty, if possible.
216     * This will only work if the process has root permissions.
217     * Alternatively, write and exec a small setuid program that
218     * does just this.
219     */
220    if ((gptr = getgrnam("tty")) != 0) {
221       gid = gptr->gr_gid;
222    } else {
223       /* if the tty group does not exist, don't change the
224        * group on the slave pty, only the owner
225        */
226       gid = -1;
227    }
228 
229    /* Note that we do not check for errors here.  If this is code
230     * where these actions are critical, check for errors!
231     */
232    chown(name, getuid(), gid);
233    /* This code only makes the slave read/writeable for the user.
234     * If this is for an interactive shell that will want to
235     * receive "write" and "wall" messages, OR S_IWGRP into the
236     * second argument below.
237     */
238    chmod(name, S_IRUSR|S_IWUSR);
239 
240    /* open the corresponding slave pty */
241    slave = open(name, O_RDWR);
242    return (slave);
243 }
244 
245 /* Certain special characters, such as ctrl/C, we want to pass directly
246    to the inferior, rather than letting readline handle them. */
247 
248 static char special_chars[20];
249 static int special_chars_count;
250 
251 static void
252 add_special_char(int ch)
253 {
254   if (ch != 0)
255     special_chars[special_chars_count++] = ch;
256 }
257 
258 static int eof_char;
259 
260 static int
261 is_special_char(int ch)
262 {
263   int i;
264 #if 0
265   if (ch == eof_char && rl_point == rl_end)
266     return 1;
267 #endif
268   for (i = special_chars_count;  --i >= 0; )
269     if (special_chars[i] == ch)
270       return 1;
271   return 0;
272 }
273 
274 static char buf[1024];
275 /* buf[0 .. buf_count-1] is the what has been emitted on the current line.
276    It is used as the readline prompt. */
277 static int buf_count = 0;
278 
279 int num_keys = 0;
280 
281 static void
282 null_prep_terminal (int meta)
283 {
284 }
285 
286 static void
287 null_deprep_terminal ()
288 {
289 }
290 
291 char pending_special_char;
292 
293 static void
294 line_handler (char *line)
295 {
296   if (line == NULL)
297     {
298       char buf[1];
299       DPRINT0("saw eof!\n");
300       buf[0] = '\004'; /* ctrl/d */
301       write (out_to_inferior_fd, buf, 1);
302     }
303   else
304     {
305       static char enter[] = "\r";
306       /*  Send line to inferior: */
307       int length = strlen (line);
308       if (length > ECHO_SUPPRESS_MAX-2)
309 	{
310 	  echo_suppress_start = 0;
311 	  echo_suppress_limit = 0;
312 	}
313       else
314 	{
315 	  if (echo_suppress_limit + length > ECHO_SUPPRESS_MAX - 2)
316 	    {
317 	      if (echo_suppress_limit - echo_suppress_start + length
318 		  <= ECHO_SUPPRESS_MAX - 2)
319 		{
320 		  memmove (echo_suppress_buffer,
321 			   echo_suppress_buffer + echo_suppress_start,
322 			   echo_suppress_limit - echo_suppress_start);
323 		  echo_suppress_limit -= echo_suppress_start;
324 		  echo_suppress_start = 0;
325 		}
326 	      else
327 		{
328 		  echo_suppress_limit = 0;
329 		}
330 	      echo_suppress_start = 0;
331 	    }
332 	  memcpy (echo_suppress_buffer + echo_suppress_limit,
333 		  line, length);
334 	  echo_suppress_limit += length;
335 	  echo_suppress_buffer[echo_suppress_limit++] = '\r';
336 	  echo_suppress_buffer[echo_suppress_limit++] = '\n';
337 	}
338       write (out_to_inferior_fd, line, length);
339       if (pending_special_char == 0)
340         {
341           write (out_to_inferior_fd, enter, sizeof(enter)-1);
342           if (*line)
343             add_history (line);
344         }
345       free (line);
346     }
347   rl_callback_handler_remove ();
348   buf_count = 0;
349   num_keys = 0;
350   if (pending_special_char != 0)
351     {
352       write (out_to_inferior_fd, &pending_special_char, 1);
353       pending_special_char = 0;
354     }
355 }
356 
357 /* Value of rl_getc_function.
358    Use this because readline should read from stdin, not rl_instream,
359    points to the pty (so readline has monitor its terminal modes). */
360 
361 int
362 my_rl_getc (FILE *dummy)
363 {
364   int ch = rl_getc (stdin);
365   if (is_special_char (ch))
366     {
367       pending_special_char = ch;
368       return '\r';
369     }
370   return ch;
371 }
372 
373 int
374 main(int argc, char** argv)
375 {
376   char *path;
377   int i;
378   int master;
379   char *name;
380   int in_from_tty_fd;
381   struct sigaction act;
382   struct winsize ws;
383   struct termios t;
384   int maxfd;
385   fd_set in_set;
386   static char empty_string[1] = "";
387   char *prompt = empty_string;
388   int ioctl_err = 0;
389 
390 #ifdef DEBUG
391   logfile = fopen("LOG", "w");
392 #endif
393 
394   rl_readline_name = APPLICATION_NAME;
395 
396   if ((master = get_master_pty(&name)) < 0)
397     {
398       perror("ptypair: could not open master pty");
399       exit(1);
400     }
401 
402   DPRINT1("pty name: '%s'\n", name);
403 
404   /* set up SIGWINCH handler */
405   act.sa_handler = sigwinch_handler;
406   sigemptyset(&(act.sa_mask));
407   act.sa_flags = 0;
408   if (sigaction(SIGWINCH, &act, NULL) < 0)
409     {
410       perror("ptypair: could not handle SIGWINCH ");
411       exit(1);
412     }
413 
414   if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
415     {
416       perror("ptypair: could not get window size");
417       exit(1);
418     }
419 
420   if ((child = fork()) < 0)
421     {
422       perror("cannot fork");
423       exit(1);
424     }
425 
426   if (child == 0)
427     {
428       int slave;  /* file descriptor for slave pty */
429 
430       /* We are in the child process */
431       close(master);
432 
433 #ifdef TIOCSCTTY
434       if ((slave = get_slave_pty(name)) < 0)
435 	{
436 	  perror("ptypair: could not open slave pty");
437 	  exit(1);
438 	}
439       free(name);
440 #endif
441 
442       /* We need to make this process a session group leader, because
443        * it is on a new PTY, and things like job control simply will
444        * not work correctly unless there is a session group leader
445        * and process group leader (which a session group leader
446        * automatically is). This also disassociates us from our old
447        * controlling tty.
448        */
449       if (setsid() < 0)
450 	{
451 	  perror("could not set session leader");
452 	}
453 
454       /* Tie us to our new controlling tty. */
455 #ifdef TIOCSCTTY
456       if (ioctl(slave, TIOCSCTTY, NULL))
457 	{
458 	  perror("could not set new controlling tty");
459 	}
460 #else
461       if ((slave = get_slave_pty(name)) < 0)
462 	{
463 	  perror("ptypair: could not open slave pty");
464 	  exit(1);
465 	}
466       free(name);
467 #endif
468 
469       /* make slave pty be standard in, out, and error */
470       dup2(slave, STDIN_FILENO);
471       dup2(slave, STDOUT_FILENO);
472       dup2(slave, STDERR_FILENO);
473 
474       /* at this point the slave pty should be standard input */
475       if (slave > 2)
476 	{
477 	  close(slave);
478 	}
479 
480       /* Try to restore window size; failure isn't critical */
481       if (ioctl(STDOUT_FILENO, TIOCSWINSZ, &ws) < 0)
482 	{
483 	  perror("could not restore window size");
484 	}
485 
486       /* now start the shell */
487       {
488 	static char* command_args[] = { COMMAND_ARGS, NULL };
489 	if (argc <= 1)
490 	  execvp(COMMAND, command_args);
491 	else
492 	  execvp(argv[1], &argv[1]);
493       }
494 
495       /* should never be reached */
496       exit(1);
497     }
498 
499   /* parent */
500   signal (SIGCHLD, sig_child);
501   free(name);
502 
503   /* Note that we only set termios settings for standard input;
504    * the master side of a pty is NOT a tty.
505    */
506   tcgetattr(STDIN_FILENO, &orig_term);
507 
508   t = orig_term;
509   eof_char = t.c_cc[VEOF];
510   /*  add_special_char(t.c_cc[VEOF]);*/
511   add_special_char(t.c_cc[VINTR]);
512   add_special_char(t.c_cc[VQUIT]);
513   add_special_char(t.c_cc[VSUSP]);
514 #if defined (VDISCARD)
515   add_special_char(t.c_cc[VDISCARD]);
516 #endif
517 
518 #if 0
519   t.c_lflag |= (ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
520 		ECHOK | ECHOKE | ECHONL | ECHOPRT );
521 #else
522   t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | \
523 		 ECHOK | ECHOKE | ECHONL | ECHOPRT );
524 #endif
525   t.c_iflag |= IGNBRK;
526   t.c_cc[VMIN] = 1;
527   t.c_cc[VTIME] = 0;
528   tcsetattr(STDIN_FILENO, TCSANOW, &t);
529   in_from_inferior_fd = master;
530   out_to_inferior_fd = master;
531   rl_instream = fdopen (master, "r");
532   rl_getc_function = my_rl_getc;
533 
534   rl_prep_term_function = null_prep_terminal;
535   rl_deprep_term_function = null_deprep_terminal;
536   rl_callback_handler_install (prompt, line_handler);
537 
538   in_from_tty_fd = STDIN_FILENO;
539   FD_ZERO (&in_set);
540   maxfd = in_from_inferior_fd > in_from_tty_fd ? in_from_inferior_fd
541     : in_from_tty_fd;
542   for (;;)
543     {
544       int num;
545       FD_SET (in_from_inferior_fd, &in_set);
546       FD_SET (in_from_tty_fd, &in_set);
547 
548       num = select(maxfd+1, &in_set, NULL, NULL, NULL);
549 
550       if (propagate_sigwinch)
551 	{
552 	  struct winsize ws;
553 	  if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) >= 0)
554 	    {
555 	      ioctl (master, TIOCSWINSZ, &ws);
556 	    }
557 	  propagate_sigwinch = 0;
558 	  continue;
559 	}
560 
561       if (num <= 0)
562 	{
563 	  perror ("select");
564 	  exit (-1);
565 	}
566       if (FD_ISSET (in_from_tty_fd, &in_set))
567 	{
568 	  extern int readline_echoing_p;
569 	  struct termios term_master;
570 	  int do_canon = 1;
571 	  int ioctl_ret;
572 
573 	  DPRINT1("[tty avail num_keys:%d]\n", num_keys);
574 
575 	  /* If we can't get tty modes for the master side of the pty, we
576 	     can't handle non-canonical-mode programs.  Always assume the
577 	     master is in canonical echo mode if we can't tell. */
578 	  ioctl_ret = tcgetattr(master, &term_master);
579 
580 	  if (ioctl_ret >= 0)
581 	    {
582 	      DPRINT2 ("echo:%d, canon:%d\n",
583 			(term_master.c_lflag & ECHO) != 0,
584 			(term_master.c_lflag & ICANON) != 0);
585 	      do_canon = (term_master.c_lflag & ICANON) != 0;
586 	      readline_echoing_p = (term_master.c_lflag & ECHO) != 0;
587 	    }
588 	  else
589 	    {
590 	      if (ioctl_err == 0)
591 		DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno);
592 	      ioctl_err = 1;
593 	    }
594 
595 	  if (do_canon == 0 && num_keys == 0)
596 	    {
597 	      char ch[10];
598 	      int count = read (STDIN_FILENO, ch, sizeof(ch));
599 	      write (out_to_inferior_fd, ch, count);
600 	    }
601 	  else
602 	    {
603 	      if (num_keys == 0)
604 		{
605 		  int i;
606 		  /* Re-install callback handler for new prompt. */
607 		  if (prompt != empty_string)
608 		    free (prompt);
609 		  prompt = malloc (buf_count + 1);
610 		  if (prompt == NULL)
611 		    prompt = empty_string;
612 		  else
613 		    {
614 		      memcpy (prompt, buf, buf_count);
615 		      prompt[buf_count] = '\0';
616 		      DPRINT1("New prompt '%s'\n", prompt);
617 #if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED -- doesn't work */
618 		      rl_already_prompted = buf_count > 0;
619 #else
620 		      if (buf_count > 0)
621 			write (1, "\r", 1);
622 #endif
623 		    }
624 		  rl_callback_handler_install (prompt, line_handler);
625 		}
626 	      num_keys++;
627 	      rl_callback_read_char ();
628 	    }
629 	}
630       else /* input from inferior. */
631 	{
632 	  int i;
633 	  int count;
634 	  int old_count;
635 	  if (buf_count > (sizeof(buf) >> 2))
636 	    buf_count = 0;
637 	  count = read (in_from_inferior_fd, buf+buf_count,
638 			sizeof(buf) - buf_count);
639 	  if (count <= 0)
640 	    {
641 	      DPRINT0 ("(Connection closed by foreign host.)\n");
642 	      tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
643 	      exit (0);
644 	    }
645 	  old_count = buf_count;
646 
647           /* Look for any pending echo that we need to suppress. */
648 	  while (echo_suppress_start < echo_suppress_limit
649 		 && count > 0
650 		 && buf[buf_count] == echo_suppress_buffer[echo_suppress_start])
651 	    {
652 	      count--;
653 	      buf_count++;
654 	      echo_suppress_start++;
655 	    }
656 
657           /* Write to the terminal anything that was not suppressed. */
658           if (count > 0)
659             write (1, buf + buf_count, count);
660 
661           /* Finally, look for a prompt candidate.
662            * When we get around to going input (from the keyboard),
663            * we will consider the prompt to be anything since the last
664            * line terminator.  So we need to save that text in the
665            * initial part of buf.  However, anything before the
666            * most recent end-of-line is not interesting. */
667 	  buf_count += count;
668 #if 1
669 	  for (i = buf_count;  --i >= old_count; )
670 #else
671 	  for (i = buf_count - 1;  i-- >= buf_count - count; )
672 #endif
673 	    {
674 	      if (buf[i] == '\n' || buf[i] == '\r')
675 		{
676 		  i++;
677 		  memmove (buf, buf+i, buf_count - i);
678 		  buf_count -= i;
679 		  break;
680 		}
681 	    }
682 	  DPRINT2("-> i: %d, buf_count: %d\n", i, buf_count);
683 	}
684     }
685 }
686