1 /* A front-end using readline to "cook" input lines for Kawa.
3 * Copyright (C) 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.
16 * Only tested under Linux; needs to be ported.
18 * When running mc -c under the Linux console, mc does not recognize
19 * mouse clicks, which mc does when not running under fep.
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.
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.
30 * The latest readline may have some hooks we can use to avoid having
31 * to back up the prompt.
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.
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.
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.
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
65 #include <sys/ioctl.h>
70 #ifdef READLINE_LIBRARY
71 # include "readline.h"
74 # include <readline/readline.h>
75 # include <readline/history.h>
79 #define COMMAND "/bin/sh"
82 #define COMMAND_ARGS COMMAND
88 # define memmove(d, s, n) __builtin_memcpy(d, s, n)
90 # define memmove(d, s, n) memcpy(d, s, n)
93 # define memmove(d, s, n) memcpy(d, s, n)
97 #define APPLICATION_NAME "Rlfe"
106 static char *progname
;
107 static char *progversion
;
109 static int in_from_inferior_fd
;
110 static int out_to_inferior_fd
;
112 /* Unfortunately, we cannot safely display echo from the inferior process.
113 The reason is that the echo bit in the pty is "owned" by the inferior,
114 and if we try to turn it off, we could confuse the inferior.
115 Thus, when echoing, we get echo twice: First readline echoes while
116 we're actually editing. Then we send the line to the inferior, and the
117 terminal driver send back an extra echo.
118 The work-around is to remember the input lines, and when we see that
119 line come back, we supress the output.
120 A better solution (supposedly available on SVR4) would be a smarter
121 terminal driver, with more flags ... */
122 #define ECHO_SUPPRESS_MAX 1024
123 char echo_suppress_buffer
[ECHO_SUPPRESS_MAX
];
124 int echo_suppress_start
= 0;
125 int echo_suppress_limit
= 0;
129 static FILE *logfile
= NULL
;
132 FILE *debugfile
= NULL
;
133 #define DPRINT0(FMT) (fprintf(debugfile, FMT), fflush(debugfile))
134 #define DPRINT1(FMT, V1) (fprintf(debugfile, FMT, V1), fflush(debugfile))
135 #define DPRINT2(FMT, V1, V2) (fprintf(debugfile, FMT, V1, V2), fflush(debugfile))
137 #define DPRINT0(FMT) /* Do nothing */
138 #define DPRINT1(FMT, V1) /* Do nothing */
139 #define DPRINT2(FMT, V1, V2) /* Do nothing */
142 struct termios orig_term
;
144 static int rlfe_directory_completion_hook
__P((char **));
145 static int rlfe_directory_rewrite_hook
__P((char **));
146 static char *rlfe_filename_completion_function
__P((const char *, int));
148 /* Pid of child process. */
149 static pid_t child
= -1;
152 sig_child (int signo
)
156 DPRINT0 ("(Child process died.)\n");
157 tcsetattr(STDIN_FILENO
, TCSANOW
, &orig_term
);
161 volatile int propagate_sigwinch
= 0;
164 * propagate window size changes from input file descriptor to
165 * master side of pty.
167 void sigwinch_handler(int signal
) {
168 propagate_sigwinch
= 1;
171 /* get_master_pty() takes a double-indirect character pointer in which
172 * to put a slave name, and returns an integer file descriptor.
173 * If it returns < 0, an error has occurred.
174 * Otherwise, it has returned the master pty file descriptor, and fills
175 * in *name with the name of the corresponding slave pty.
176 * Once the slave pty has been opened, you are responsible to free *name.
179 int get_master_pty(char **name
) {
181 /* default to returning error */
184 /* create a dummy name to fill in */
185 *name
= strdup("/dev/ptyXX");
187 /* search for an unused pty */
188 for (i
=0; i
<16 && master
<= 0; i
++) {
189 for (j
=0; j
<16 && master
<= 0; j
++) {
191 (*name
)[8] = "pqrstuvwxyzPQRST"[i
];
192 (*name
)[9] = "0123456789abcdef"[j
];
193 /* open the master pty */
194 if ((master
= open(*name
, O_RDWR
)) < 0) {
195 if (errno
== ENOENT
) {
196 /* we are out of pty devices */
202 /* By substituting a letter, we change the master pty
203 * name into the slave pty name.
206 if (access(*name
, R_OK
|W_OK
) != 0)
214 if ((master
< 0) && (i
== 16) && (j
== 16)) {
215 /* must have tried every pty unsuccessfully */
225 /* get_slave_pty() returns an integer file descriptor.
226 * If it returns < 0, an error has occurred.
227 * Otherwise, it has returned the slave file descriptor.
230 int get_slave_pty(char *name
) {
235 /* chown/chmod the corresponding pty, if possible.
236 * This will only work if the process has root permissions.
237 * Alternatively, write and exec a small setuid program that
240 if ((gptr
= getgrnam("tty")) != 0) {
243 /* if the tty group does not exist, don't change the
244 * group on the slave pty, only the owner
249 /* Note that we do not check for errors here. If this is code
250 * where these actions are critical, check for errors!
252 chown(name
, getuid(), gid
);
253 /* This code only makes the slave read/writeable for the user.
254 * If this is for an interactive shell that will want to
255 * receive "write" and "wall" messages, OR S_IWGRP into the
256 * second argument below.
258 chmod(name
, S_IRUSR
|S_IWUSR
);
260 /* open the corresponding slave pty */
261 slave
= open(name
, O_RDWR
);
265 /* Certain special characters, such as ctrl/C, we want to pass directly
266 to the inferior, rather than letting readline handle them. */
268 static char special_chars
[20];
269 static int special_chars_count
;
272 add_special_char(int ch
)
275 special_chars
[special_chars_count
++] = ch
;
281 is_special_char(int ch
)
285 if (ch
== eof_char
&& rl_point
== rl_end
)
288 for (i
= special_chars_count
; --i
>= 0; )
289 if (special_chars
[i
] == ch
)
294 static char buf
[1024];
295 /* buf[0 .. buf_count-1] is the what has been emitted on the current line.
296 It is used as the readline prompt. */
297 static int buf_count
= 0;
302 null_prep_terminal (int meta
)
307 null_deprep_terminal ()
311 char pending_special_char
;
314 line_handler (char *line
)
319 DPRINT0("saw eof!\n");
320 buf
[0] = '\004'; /* ctrl/d */
321 write (out_to_inferior_fd
, buf
, 1);
325 static char enter
[] = "\r";
326 /* Send line to inferior: */
327 int length
= strlen (line
);
328 if (length
> ECHO_SUPPRESS_MAX
-2)
330 echo_suppress_start
= 0;
331 echo_suppress_limit
= 0;
335 if (echo_suppress_limit
+ length
> ECHO_SUPPRESS_MAX
- 2)
337 if (echo_suppress_limit
- echo_suppress_start
+ length
338 <= ECHO_SUPPRESS_MAX
- 2)
340 memmove (echo_suppress_buffer
,
341 echo_suppress_buffer
+ echo_suppress_start
,
342 echo_suppress_limit
- echo_suppress_start
);
343 echo_suppress_limit
-= echo_suppress_start
;
344 echo_suppress_start
= 0;
348 echo_suppress_limit
= 0;
350 echo_suppress_start
= 0;
352 memcpy (echo_suppress_buffer
+ echo_suppress_limit
,
354 echo_suppress_limit
+= length
;
355 echo_suppress_buffer
[echo_suppress_limit
++] = '\r';
356 echo_suppress_buffer
[echo_suppress_limit
++] = '\n';
358 write (out_to_inferior_fd
, line
, length
);
359 if (pending_special_char
== 0)
361 write (out_to_inferior_fd
, enter
, sizeof(enter
)-1);
367 rl_callback_handler_remove ();
370 if (pending_special_char
!= 0)
372 write (out_to_inferior_fd
, &pending_special_char
, 1);
373 pending_special_char
= 0;
377 /* Value of rl_getc_function.
378 Use this because readline should read from stdin, not rl_instream,
379 points to the pty (so readline has monitor its terminal modes). */
382 my_rl_getc (FILE *dummy
)
384 int ch
= rl_getc (stdin
);
385 if (is_special_char (ch
))
387 pending_special_char
= ch
;
396 fprintf (stderr
, "%s: usage: %s [-l filename] [-a] [-n appname] [-hv] [command [arguments...]]\n",
401 main(int argc
, char** argv
)
406 char *name
, *logfname
, *appname
;
408 struct sigaction act
;
413 static char empty_string
[1] = "";
414 char *prompt
= empty_string
;
417 if ((progname
= strrchr (argv
[0], '/')) == 0)
421 progversion
= RL_LIBRARY_VERSION
;
424 appname
= APPLICATION_NAME
;
425 logfname
= (char *)NULL
;
427 while ((i
= getopt (argc
, argv
, "ahl:n:v")) != EOF
)
444 fprintf (stderr
, "%s version %s\n", progname
, progversion
);
457 logfile
= fopen (logfname
, append
? "a" : "w");
459 fprintf (stderr
, "%s: warning: could not open log file %s: %s\n",
460 progname
, logfname
, strerror (errno
));
463 rl_readline_name
= appname
;
466 debugfile
= fopen("LOG", "w");
469 if ((master
= get_master_pty(&name
)) < 0)
471 perror("ptypair: could not open master pty");
475 DPRINT1("pty name: '%s'\n", name
);
477 /* set up SIGWINCH handler */
478 act
.sa_handler
= sigwinch_handler
;
479 sigemptyset(&(act
.sa_mask
));
481 if (sigaction(SIGWINCH
, &act
, NULL
) < 0)
483 perror("ptypair: could not handle SIGWINCH ");
487 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &ws
) < 0)
489 perror("ptypair: could not get window size");
493 if ((child
= fork()) < 0)
495 perror("cannot fork");
501 int slave
; /* file descriptor for slave pty */
503 /* We are in the child process */
507 if ((slave
= get_slave_pty(name
)) < 0)
509 perror("ptypair: could not open slave pty");
515 /* We need to make this process a session group leader, because
516 * it is on a new PTY, and things like job control simply will
517 * not work correctly unless there is a session group leader
518 * and process group leader (which a session group leader
519 * automatically is). This also disassociates us from our old
524 perror("could not set session leader");
527 /* Tie us to our new controlling tty. */
529 if (ioctl(slave
, TIOCSCTTY
, NULL
))
531 perror("could not set new controlling tty");
534 if ((slave
= get_slave_pty(name
)) < 0)
536 perror("ptypair: could not open slave pty");
542 /* make slave pty be standard in, out, and error */
543 dup2(slave
, STDIN_FILENO
);
544 dup2(slave
, STDOUT_FILENO
);
545 dup2(slave
, STDERR_FILENO
);
547 /* at this point the slave pty should be standard input */
553 /* Try to restore window size; failure isn't critical */
554 if (ioctl(STDOUT_FILENO
, TIOCSWINSZ
, &ws
) < 0)
556 perror("could not restore window size");
559 /* now start the shell */
561 static char* command_args
[] = { COMMAND_ARGS
, NULL
};
563 execvp(COMMAND
, command_args
);
565 execvp(argv
[0], &argv
[0]);
568 /* should never be reached */
573 signal (SIGCHLD
, sig_child
);
576 /* Note that we only set termios settings for standard input;
577 * the master side of a pty is NOT a tty.
579 tcgetattr(STDIN_FILENO
, &orig_term
);
582 eof_char
= t
.c_cc
[VEOF
];
583 /* add_special_char(t.c_cc[VEOF]);*/
584 add_special_char(t
.c_cc
[VINTR
]);
585 add_special_char(t
.c_cc
[VQUIT
]);
586 add_special_char(t
.c_cc
[VSUSP
]);
587 #if defined (VDISCARD)
588 add_special_char(t
.c_cc
[VDISCARD
]);
592 t
.c_lflag
|= (ICANON
| ISIG
| ECHO
| ECHOCTL
| ECHOE
| \
593 ECHOK
| ECHOKE
| ECHONL
| ECHOPRT
);
595 t
.c_lflag
&= ~(ICANON
| ISIG
| ECHO
| ECHOCTL
| ECHOE
| \
596 ECHOK
| ECHOKE
| ECHONL
| ECHOPRT
);
601 tcsetattr(STDIN_FILENO
, TCSANOW
, &t
);
602 in_from_inferior_fd
= master
;
603 out_to_inferior_fd
= master
;
604 rl_instream
= fdopen (master
, "r");
605 rl_getc_function
= my_rl_getc
;
607 rl_prep_term_function
= null_prep_terminal
;
608 rl_deprep_term_function
= null_deprep_terminal
;
609 rl_callback_handler_install (prompt
, line_handler
);
612 rl_directory_completion_hook
= rlfe_directory_completion_hook
;
613 rl_completion_entry_function
= rlfe_filename_completion_function
;
615 rl_directory_rewrite_hook
= rlfe_directory_rewrite_hook
;
618 in_from_tty_fd
= STDIN_FILENO
;
620 maxfd
= in_from_inferior_fd
> in_from_tty_fd
? in_from_inferior_fd
625 FD_SET (in_from_inferior_fd
, &in_set
);
626 FD_SET (in_from_tty_fd
, &in_set
);
628 num
= select(maxfd
+1, &in_set
, NULL
, NULL
, NULL
);
630 if (propagate_sigwinch
)
633 if (ioctl (STDIN_FILENO
, TIOCGWINSZ
, &ws
) >= 0)
635 ioctl (master
, TIOCSWINSZ
, &ws
);
637 propagate_sigwinch
= 0;
646 if (FD_ISSET (in_from_tty_fd
, &in_set
))
648 extern int readline_echoing_p
;
649 struct termios term_master
;
653 DPRINT1("[tty avail num_keys:%d]\n", num_keys
);
655 /* If we can't get tty modes for the master side of the pty, we
656 can't handle non-canonical-mode programs. Always assume the
657 master is in canonical echo mode if we can't tell. */
658 ioctl_ret
= tcgetattr(master
, &term_master
);
662 DPRINT2 ("echo:%d, canon:%d\n",
663 (term_master
.c_lflag
& ECHO
) != 0,
664 (term_master
.c_lflag
& ICANON
) != 0);
665 do_canon
= (term_master
.c_lflag
& ICANON
) != 0;
666 readline_echoing_p
= (term_master
.c_lflag
& ECHO
) != 0;
671 DPRINT1("tcgetattr on master fd failed: errno = %d\n", errno
);
675 if (do_canon
== 0 && num_keys
== 0)
678 int count
= read (STDIN_FILENO
, ch
, sizeof(ch
));
679 write (out_to_inferior_fd
, ch
, count
);
686 /* Re-install callback handler for new prompt. */
687 if (prompt
!= empty_string
)
689 prompt
= malloc (buf_count
+ 1);
691 prompt
= empty_string
;
694 memcpy (prompt
, buf
, buf_count
);
695 prompt
[buf_count
] = '\0';
696 DPRINT1("New prompt '%s'\n", prompt
);
697 #if 0 /* ifdef HAVE_RL_ALREADY_PROMPTED -- doesn't work */
698 rl_already_prompted
= buf_count
> 0;
704 rl_callback_handler_install (prompt
, line_handler
);
707 rl_callback_read_char ();
710 else /* input from inferior. */
715 if (buf_count
> (sizeof(buf
) >> 2))
717 count
= read (in_from_inferior_fd
, buf
+buf_count
,
718 sizeof(buf
) - buf_count
);
721 DPRINT0 ("(Connection closed by foreign host.)\n");
722 tcsetattr(STDIN_FILENO
, TCSANOW
, &orig_term
);
725 old_count
= buf_count
;
727 /* Do some minimal carriage return translation and backspace
728 processing before logging the input line. */
739 b
= malloc (count
+ 1);
742 for (i
= 0; i
< count
; i
++)
743 b
[i
] = buf
[buf_count
+ i
];
745 for (i
= j
= 0; i
<= count
; i
++)
752 else if (b
[i
] == '\b')
760 fprintf (logfile
, "%s", b
);
768 /* Look for any pending echo that we need to suppress. */
769 while (echo_suppress_start
< echo_suppress_limit
771 && buf
[buf_count
] == echo_suppress_buffer
[echo_suppress_start
])
775 echo_suppress_start
++;
778 /* Write to the terminal anything that was not suppressed. */
780 write (1, buf
+ buf_count
, count
);
782 /* Finally, look for a prompt candidate.
783 * When we get around to going input (from the keyboard),
784 * we will consider the prompt to be anything since the last
785 * line terminator. So we need to save that text in the
786 * initial part of buf. However, anything before the
787 * most recent end-of-line is not interesting. */
790 for (i
= buf_count
; --i
>= old_count
; )
792 for (i
= buf_count
- 1; i
-- >= buf_count
- count
; )
795 if (buf
[i
] == '\n' || buf
[i
] == '\r')
798 memmove (buf
, buf
+i
, buf_count
- i
);
803 DPRINT2("-> i: %d, buf_count: %d\n", i
, buf_count
);
810 * FILENAME COMPLETION FOR RLFE
815 # define PATH_MAX 1024
819 #define ISDIRSEP(x) ((x) == '/')
820 #define PATHSEP(x) (ISDIRSEP(x) || (x) == 0)
822 #define DOT_OR_DOTDOT(x) \
823 ((x)[0] == '.' && (PATHSEP((x)[1]) || \
824 ((x)[1] == '.' && PATHSEP((x)[2]))))
826 #define FREE(x) if (x) free(x)
828 #define STRDUP(s, x) do { \
831 return ((char *)NULL); \
835 get_inferior_cwd (path
, psize
)
840 static char procfsbuf
[PATH_MAX
] = { '\0' };
842 if (procfsbuf
[0] == '\0')
843 sprintf (procfsbuf
, "/proc/%d/cwd", (int)child
);
844 n
= readlink (procfsbuf
, path
, psize
);
854 rlfe_directory_rewrite_hook (dirnamep
)
857 char *ldirname
, cwd
[PATH_MAX
], *retdir
, *ld
;
860 ldirname
= *dirnamep
;
862 if (*ldirname
== '/')
865 n
= get_inferior_cwd (cwd
, sizeof(cwd
) - 1);
868 if (n
== 0) /* current directory */
875 /* Minimally canonicalize ldirname by removing leading `./' */
876 for (ld
= ldirname
; *ld
; )
878 if (ISDIRSEP (ld
[0]))
880 else if (ld
[0] == '.' && PATHSEP(ld
[1]))
885 ldlen
= (ld
&& *ld
) ? strlen (ld
) : 0;
887 retdir
= (char *)malloc (n
+ ldlen
+ 3);
891 sprintf (retdir
, "%s/%s", cwd
, ld
);
893 strcpy (retdir
, cwd
);
898 DPRINT1("rl_directory_rewrite_hook returns %s\n", retdir
);
902 /* Translate *DIRNAMEP to be relative to the inferior's CWD. Leave a trailing
903 slash on the result. */
905 rlfe_directory_completion_hook (dirnamep
)
908 char *ldirname
, *retdir
;
911 ldirname
= *dirnamep
;
913 if (*ldirname
== '/')
916 n
= rlfe_directory_rewrite_hook (dirnamep
);
920 ldirname
= *dirnamep
;
921 ldlen
= (ldirname
&& *ldirname
) ? strlen (ldirname
) : 0;
923 if (ldlen
== 0 || ldirname
[ldlen
- 1] != '/')
925 retdir
= (char *)malloc (ldlen
+ 3);
929 strcpy (retdir
, ldirname
);
931 retdir
[ldlen
++] = '.';
933 retdir
[ldlen
+1] = '\0';
939 DPRINT1("rl_directory_completion_hook returns %s\n", retdir
);
944 rlfe_filename_completion_function (text
, state
)
948 static DIR *directory
;
949 static char *filename
= (char *)NULL
;
950 static char *dirname
= (char *)NULL
, *ud
= (char *)NULL
;
951 static int flen
, udlen
;
953 struct dirent
*dentry
;
959 closedir (directory
);
967 STRDUP (filename
, text
);
970 filename
= malloc(1);
972 return ((char *)NULL
);
975 dirname
= (text
&& *text
) ? strdup (text
) : strdup (".");
977 return ((char *)NULL
);
979 temp
= strrchr (dirname
, '/');
982 strcpy (filename
, ++temp
);
991 STRDUP (ud
, dirname
);
994 rlfe_directory_completion_hook (&dirname
);
996 directory
= opendir (dirname
);
997 flen
= strlen (filename
);
999 rl_filename_completion_desired
= 1;
1003 while (directory
&& (dentry
= readdir (directory
)))
1007 if (DOT_OR_DOTDOT(dentry
->d_name
) == 0)
1012 if ((dentry
->d_name
[0] == filename
[0]) &&
1013 (strlen (dentry
->d_name
) >= flen
) &&
1014 (strncmp (filename
, dentry
->d_name
, flen
) == 0))
1023 closedir (directory
);
1029 dirname
= filename
= ud
= 0;
1030 return ((char *)NULL
);
1033 if (ud
== 0 || (ud
[0] == '.' && ud
[1] == '\0'))
1034 temp
= strdup (dentry
->d_name
);
1037 temp
= malloc (1 + udlen
+ strlen (dentry
->d_name
));
1039 strcpy (temp
+ udlen
, dentry
->d_name
);