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