Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / libreadline / examples / rlfe.c
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 }