1 /* $NetBSD: ex_script.c,v 1.8 2017/11/06 03:27:34 rin Exp $ */ 2 /*- 3 * Copyright (c) 1992, 1993, 1994 4 * The Regents of the University of California. All rights reserved. 5 * Copyright (c) 1992, 1993, 1994, 1995, 1996 6 * Keith Bostic. All rights reserved. 7 * 8 * This code is derived from software contributed to Berkeley by 9 * Brian Hirt. 10 * 11 * See the LICENSE file for redistribution information. 12 */ 13 14 #include "config.h" 15 16 #include <sys/cdefs.h> 17 #if 0 18 #ifndef lint 19 static const char sccsid[] = "Id: ex_script.c,v 10.38 2001/06/25 15:19:19 skimo Exp (Berkeley) Date: 2001/06/25 15:19:19 "; 20 #endif /* not lint */ 21 #else 22 __RCSID("$NetBSD: ex_script.c,v 1.8 2017/11/06 03:27:34 rin Exp $"); 23 #endif 24 25 #include <sys/types.h> 26 #include <sys/ioctl.h> 27 #include <sys/queue.h> 28 #ifdef HAVE_SYS_SELECT_H 29 #include <sys/select.h> 30 #endif 31 #include <sys/stat.h> 32 #if defined(HAVE_SYS5_PTY) 33 #include <sys/stropts.h> 34 #endif 35 #include <sys/time.h> 36 #include <sys/wait.h> 37 38 #include <bitstring.h> 39 #include <errno.h> 40 #include <fcntl.h> 41 #include <stdio.h> /* XXX: OSF/1 bug: include before <grp.h> */ 42 #include <grp.h> 43 #include <limits.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <termios.h> 47 #include <unistd.h> 48 #ifdef HAVE_UTIL_H 49 #include <util.h> 50 #endif 51 52 #include "../common/common.h" 53 #include "../vi/vi.h" 54 #include "script.h" 55 #include "pathnames.h" 56 57 static void sscr_check __P((SCR *)); 58 static int sscr_getprompt __P((SCR *)); 59 static int sscr_init __P((SCR *)); 60 static int sscr_insert __P((SCR *)); 61 #ifdef HAVE_OPENPTY 62 #define sscr_pty openpty 63 #else 64 static int sscr_pty __P((int *, int *, char *, struct termios *, void *)); 65 #endif 66 static int sscr_setprompt __P((SCR *, char *, size_t)); 67 68 /* 69 * ex_script -- : sc[ript][!] [file] 70 * Switch to script mode. 71 * 72 * PUBLIC: int ex_script __P((SCR *, EXCMD *)); 73 */ 74 int 75 ex_script(SCR *sp, EXCMD *cmdp) 76 { 77 /* Vi only command. */ 78 if (!F_ISSET(sp, SC_VI)) { 79 msgq(sp, M_ERR, 80 "150|The script command is only available in vi mode"); 81 return (1); 82 } 83 84 /* Avoid double run. */ 85 if (F_ISSET(sp, SC_SCRIPT)) { 86 msgq(sp, M_ERR, 87 "The script command is already runninng"); 88 return (1); 89 } 90 91 /* We're going to need a shell. */ 92 if (opts_empty(sp, O_SHELL, 0)) 93 return (1); 94 95 /* Switch to the new file. */ 96 if (cmdp->argc != 0 && ex_edit(sp, cmdp)) 97 return (1); 98 99 /* Create the shell, figure out the prompt. */ 100 if (sscr_init(sp)) 101 return (1); 102 103 return (0); 104 } 105 106 /* 107 * sscr_init -- 108 * Create a pty setup for a shell. 109 */ 110 static int 111 sscr_init(SCR *sp) 112 { 113 SCRIPT *sc; 114 const char *sh, *sh_path; 115 116 MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT)); 117 sp->script = sc; 118 sc->sh_prompt = NULL; 119 sc->sh_prompt_len = 0; 120 121 /* 122 * There are two different processes running through this code. 123 * They are the shell and the parent. 124 */ 125 sc->sh_master = sc->sh_slave = -1; 126 127 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { 128 msgq(sp, M_SYSERR, "tcgetattr"); 129 goto err; 130 } 131 132 /* 133 * Turn off output postprocessing and echo. 134 */ 135 sc->sh_term.c_oflag &= ~OPOST; 136 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); 137 138 #ifdef TIOCGWINSZ 139 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { 140 msgq(sp, M_SYSERR, "tcgetattr"); 141 goto err; 142 } 143 144 if (sscr_pty(&sc->sh_master, 145 &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { 146 msgq(sp, M_SYSERR, "pty"); 147 goto err; 148 } 149 #else 150 if (sscr_pty(&sc->sh_master, 151 &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) { 152 msgq(sp, M_SYSERR, "pty"); 153 goto err; 154 } 155 #endif 156 157 /* 158 * __TK__ huh? 159 * Don't use vfork() here, because the signal semantics differ from 160 * implementation to implementation. 161 */ 162 switch (sc->sh_pid = fork()) { 163 case -1: /* Error. */ 164 msgq(sp, M_SYSERR, "fork"); 165 err: if (sc->sh_master != -1) 166 (void)close(sc->sh_master); 167 if (sc->sh_slave != -1) 168 (void)close(sc->sh_slave); 169 return (1); 170 case 0: /* Utility. */ 171 /* 172 * XXX 173 * So that shells that do command line editing turn it off. 174 */ 175 (void)setenv("TERM", "emacs", 1); 176 (void)setenv("TERMCAP", "emacs:", 1); 177 (void)setenv("EMACS", "t", 1); 178 179 (void)setsid(); 180 #ifdef TIOCSCTTY 181 /* 182 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY 183 * ioctl, not by opening a terminal device file. POSIX 1003.1 184 * doesn't define a portable way to do this. If TIOCSCTTY is 185 * not available, hope that the open does it. 186 */ 187 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); 188 #endif 189 (void)close(sc->sh_master); 190 (void)dup2(sc->sh_slave, STDIN_FILENO); 191 (void)dup2(sc->sh_slave, STDOUT_FILENO); 192 (void)dup2(sc->sh_slave, STDERR_FILENO); 193 (void)close(sc->sh_slave); 194 195 /* Assumes that all shells have -i. */ 196 sh_path = O_STR(sp, O_SHELL); 197 if ((sh = strrchr(sh_path, '/')) == NULL) 198 sh = sh_path; 199 else 200 ++sh; 201 execl(sh_path, sh, "-i", NULL); 202 msgq_str(sp, M_SYSERR, sh_path, "execl: %s"); 203 _exit(127); 204 default: /* Parent. */ 205 break; 206 } 207 208 if (sscr_getprompt(sp)) 209 return (1); 210 211 F_SET(sp, SC_SCRIPT); 212 F_SET(sp->gp, G_SCRWIN); 213 return (0); 214 } 215 216 /* 217 * sscr_getprompt -- 218 * Eat lines printed by the shell until a line with no trailing 219 * carriage return comes; set the prompt from that line. 220 */ 221 static int 222 sscr_getprompt(SCR *sp) 223 { 224 struct timeval tv; 225 fd_set fdset; 226 int master; 227 228 /* Wait up to a second for characters to read. */ 229 tv.tv_sec = 5; 230 tv.tv_usec = 0; 231 master = sp->script->sh_master; 232 FD_ZERO(&fdset); 233 FD_SET(master, &fdset); 234 switch (select(master + 1, &fdset, NULL, NULL, &tv)) { 235 case -1: /* Error or interrupt. */ 236 msgq(sp, M_SYSERR, "select"); 237 break; 238 case 0: /* Timeout */ 239 msgq(sp, M_ERR, "Error: timed out"); 240 break; 241 case 1: /* Characters to read. */ 242 return (sscr_insert(sp) || sp->script == NULL); 243 } 244 245 sscr_end(sp); 246 return (1); 247 } 248 249 /* 250 * sscr_exec -- 251 * Take a line and hand it off to the shell. 252 * 253 * PUBLIC: int sscr_exec __P((SCR *, db_recno_t)); 254 */ 255 int 256 sscr_exec(SCR *sp, db_recno_t lno) 257 { 258 SCRIPT *sc; 259 db_recno_t last_lno; 260 size_t blen, len, last_len; 261 int isempty, matchprompt, rval; 262 ssize_t nw; 263 char *bp = NULL; 264 const char *p; 265 const CHAR_T *ip; 266 size_t ilen; 267 268 sc = sp->script; 269 270 /* If there's a prompt on the last line, append the command. */ 271 if (db_last(sp, &last_lno)) 272 return (1); 273 if (db_get(sp, last_lno, DBG_FATAL, __UNCONST(&ip), &ilen)) 274 return (1); 275 INT2CHAR(sp, ip, ilen, p, last_len); 276 if (last_len == sc->sh_prompt_len && 277 memcmp(p, sc->sh_prompt, last_len) == 0) { 278 matchprompt = 1; 279 GET_SPACE_RETC(sp, bp, blen, last_len + 128); 280 memmove(bp, p, last_len); 281 } else 282 matchprompt = 0; 283 284 /* Get something to execute. */ 285 if (db_eget(sp, lno, __UNCONST(&ip), &ilen, &isempty)) { 286 if (isempty) 287 goto empty; 288 goto err1; 289 } 290 291 /* Empty lines aren't interesting. */ 292 if (ilen == 0) 293 goto empty; 294 INT2CHAR(sp, ip, ilen, p, len); 295 296 /* Delete any prompt. */ 297 if (len >= sc->sh_prompt_len && 298 memcmp(p, sc->sh_prompt, sc->sh_prompt_len) == 0) { 299 len -= sc->sh_prompt_len; 300 if (len == 0) { 301 empty: msgq(sp, M_BERR, "151|No command to execute"); 302 goto err1; 303 } 304 p += sc->sh_prompt_len; 305 } 306 307 /* Push the line to the shell. */ 308 if ((size_t)(nw = write(sc->sh_master, p, len)) != len) 309 goto err2; 310 rval = 0; 311 if (write(sc->sh_master, "\n", 1) != 1) { 312 err2: if (nw == 0) 313 errno = EIO; 314 msgq(sp, M_SYSERR, "shell"); 315 goto err1; 316 } 317 318 if (matchprompt) { 319 ADD_SPACE_GOTO(sp, char, bp, blen, last_len + len); 320 memmove(bp + last_len, p, len); 321 CHAR2INT(sp, bp, last_len + len, ip, ilen); 322 if (db_set(sp, last_lno, ip, ilen)) 323 err1: rval = 1; 324 } 325 if (matchprompt) 326 alloc_err: FREE_SPACE(sp, bp, blen); 327 return (rval); 328 } 329 330 /* 331 * sscr_check_input - 332 * Check whether any input from shell or passed set. 333 * 334 * PUBLIC: int sscr_check_input __P((SCR *sp, fd_set *rdfd, int maxfd)); 335 */ 336 int 337 sscr_check_input(SCR *sp, fd_set *fdset, int maxfd) 338 { 339 fd_set rdfd; 340 SCR *tsp; 341 WIN *wp; 342 343 wp = sp->wp; 344 345 loop: memcpy(&rdfd, fdset, sizeof(fd_set)); 346 347 TAILQ_FOREACH(tsp, &wp->scrq, q) 348 if (F_ISSET(sp, SC_SCRIPT)) { 349 FD_SET(sp->script->sh_master, &rdfd); 350 if (sp->script->sh_master > maxfd) 351 maxfd = sp->script->sh_master; 352 } 353 switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) { 354 case 0: 355 abort(); 356 case -1: 357 return 1; 358 default: 359 break; 360 } 361 TAILQ_FOREACH(tsp, &wp->scrq, q) 362 if (F_ISSET(sp, SC_SCRIPT) && 363 FD_ISSET(sp->script->sh_master, &rdfd)) { 364 if (sscr_input(sp)) 365 return 1; 366 goto loop; 367 } 368 return 0; 369 } 370 371 /* 372 * sscr_input -- 373 * Read any waiting shell input. 374 * 375 * PUBLIC: int sscr_input __P((SCR *)); 376 */ 377 int 378 sscr_input(SCR *sp) 379 { 380 WIN *wp; 381 struct timeval poll; 382 fd_set rdfd; 383 int maxfd; 384 385 wp = sp->wp; 386 387 loop: maxfd = 0; 388 FD_ZERO(&rdfd); 389 poll.tv_sec = 0; 390 poll.tv_usec = 0; 391 392 /* Set up the input mask. */ 393 TAILQ_FOREACH(sp, &wp->scrq, q) 394 if (F_ISSET(sp, SC_SCRIPT)) { 395 FD_SET(sp->script->sh_master, &rdfd); 396 if (sp->script->sh_master > maxfd) 397 maxfd = sp->script->sh_master; 398 } 399 400 /* Check for input. */ 401 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) { 402 case -1: 403 msgq(sp, M_SYSERR, "select"); 404 return (1); 405 case 0: 406 return (0); 407 default: 408 break; 409 } 410 411 /* Read the input. */ 412 TAILQ_FOREACH(sp, &wp->scrq, q) 413 if (F_ISSET(sp, SC_SCRIPT) && 414 FD_ISSET(sp->script->sh_master, &rdfd) && 415 sscr_insert(sp)) 416 return (1); 417 goto loop; 418 } 419 420 /* 421 * sscr_insert -- 422 * Take a line from the shell and insert it into the file. 423 */ 424 static int 425 sscr_insert(SCR *sp) 426 { 427 struct timeval tv; 428 char *endp, *p, *t; 429 SCRIPT *sc; 430 fd_set rdfd; 431 db_recno_t lno; 432 size_t len; 433 ssize_t nr; 434 char bp[1024]; 435 const CHAR_T *ip; 436 size_t ilen = 0; 437 438 /* Find out where the end of the file is. */ 439 if (db_last(sp, &lno)) 440 return (1); 441 442 endp = bp; 443 444 /* Read the characters. */ 445 sc = sp->script; 446 more: switch (nr = read(sc->sh_master, endp, bp + sizeof(bp) - endp)) { 447 case 0: /* EOF; shell just exited. */ 448 sscr_end(sp); 449 return (0); 450 case -1: /* Error or interrupt. */ 451 msgq(sp, M_SYSERR, "shell"); 452 return (1); 453 default: 454 endp += nr; 455 break; 456 } 457 458 /* Append the lines into the file. */ 459 for (p = t = bp; p < endp; ++p) { 460 if (*p == '\r' || *p == '\n') { 461 len = p - t; 462 if (CHAR2INT(sp, t, len, ip, ilen) || 463 db_append(sp, 1, lno++, ip, ilen)) 464 return (1); 465 t = p + 1; 466 } 467 } 468 /* 469 * If the last thing from the shell isn't another prompt, wait up to 470 * 1/10 of a second for more stuff to show up, so that we don't break 471 * the output into two separate lines. Don't want to hang indefinitely 472 * because some program is hanging, confused the shell, or whatever. 473 * Note that sc->sh_prompt can be NULL here. 474 */ 475 len = p - t; 476 if (sc->sh_prompt == NULL || len != sc->sh_prompt_len || 477 memcmp(t, sc->sh_prompt, len) != 0) { 478 tv.tv_sec = 0; 479 tv.tv_usec = 100000; 480 FD_ZERO(&rdfd); 481 FD_SET(sc->sh_master, &rdfd); 482 if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) { 483 if (len == sizeof(bp)) { 484 if (CHAR2INT(sp, t, len, ip, ilen) || 485 db_append(sp, 1, lno++, ip, ilen)) 486 return (1); 487 endp = bp; 488 } else { 489 memmove(bp, t, len); 490 endp = bp + len; 491 } 492 goto more; 493 } 494 if (sscr_setprompt(sp, t, len)) 495 return (1); 496 } 497 498 /* Append the remains into the file, and the cursor moves to EOF. */ 499 if (len > 0) { 500 if (CHAR2INT(sp, t, len, ip, ilen) || 501 db_append(sp, 1, lno++, ip, ilen)) 502 return (1); 503 sp->cno = ilen - 1; 504 } else 505 sp->cno = 0; 506 sp->lno = lno; 507 return (vs_refresh(sp, 1)); 508 } 509 510 /* 511 * sscr_setprompt -- 512 * 513 * Set the prompt in external ("char") encoding. 514 * 515 */ 516 static int 517 sscr_setprompt(SCR *sp, char *buf, size_t len) 518 { 519 SCRIPT *sc; 520 521 sc = sp->script; 522 if (sc->sh_prompt) 523 free(sc->sh_prompt); 524 MALLOC(sp, sc->sh_prompt, char *, len + 1); 525 if (sc->sh_prompt == NULL) { 526 sscr_end(sp); 527 return (1); 528 } 529 memmove(sc->sh_prompt, buf, len); 530 sc->sh_prompt_len = len; 531 sc->sh_prompt[len] = '\0'; 532 return (0); 533 } 534 535 /* 536 * sscr_end -- 537 * End the pipe to a shell. 538 * 539 * PUBLIC: int sscr_end __P((SCR *)); 540 */ 541 int 542 sscr_end(SCR *sp) 543 { 544 SCRIPT *sc; 545 546 if ((sc = sp->script) == NULL) 547 return (0); 548 549 /* Turn off the script flags. */ 550 F_CLR(sp, SC_SCRIPT); 551 sscr_check(sp); 552 553 /* Close down the parent's file descriptors. */ 554 if (sc->sh_master != -1) 555 (void)close(sc->sh_master); 556 if (sc->sh_slave != -1) 557 (void)close(sc->sh_slave); 558 559 /* This should have killed the child. */ 560 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0); 561 562 /* Free memory. */ 563 free(sc->sh_prompt); 564 free(sc); 565 sp->script = NULL; 566 567 return (0); 568 } 569 570 /* 571 * sscr_check -- 572 * Set/clear the global scripting bit. 573 */ 574 static void 575 sscr_check(SCR *sp) 576 { 577 GS *gp; 578 WIN *wp; 579 580 gp = sp->gp; 581 wp = sp->wp; 582 TAILQ_FOREACH(sp, &wp->scrq, q) 583 if (F_ISSET(sp, SC_SCRIPT)) { 584 F_SET(gp, G_SCRWIN); 585 return; 586 } 587 F_CLR(gp, G_SCRWIN); 588 } 589 590 #ifndef HAVE_OPENPTY 591 #ifdef HAVE_SYS5_PTY 592 static int ptys_open __P((int, char *)); 593 static int ptym_open __P((char *)); 594 595 static int 596 sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp) 597 { 598 int master, slave; 599 600 /* open master terminal */ 601 if ((master = ptym_open(name)) < 0) { 602 errno = ENOENT; /* out of ptys */ 603 return (-1); 604 } 605 606 /* open slave terminal */ 607 if ((slave = ptys_open(master, name)) >= 0) { 608 *amaster = master; 609 *aslave = slave; 610 } else { 611 errno = ENOENT; /* out of ptys */ 612 return (-1); 613 } 614 615 if (termp) 616 (void) tcsetattr(slave, TCSAFLUSH, termp); 617 #ifdef TIOCSWINSZ 618 if (winp != NULL) 619 (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp); 620 #endif 621 return (0); 622 } 623 624 /* 625 * ptym_open -- 626 * This function opens a master pty and returns the file descriptor 627 * to it. pts_name is also returned which is the name of the slave. 628 */ 629 static int 630 ptym_open(char *pts_name) 631 { 632 int fdm; 633 char *ptr; 634 635 strcpy(pts_name, _PATH_SYSV_PTY); 636 if ((fdm = open(pts_name, O_RDWR)) < 0 ) 637 return (-1); 638 639 if (grantpt(fdm) < 0) { 640 close(fdm); 641 return (-2); 642 } 643 644 if (unlockpt(fdm) < 0) { 645 close(fdm); 646 return (-3); 647 } 648 649 if (unlockpt(fdm) < 0) { 650 close(fdm); 651 return (-3); 652 } 653 654 /* get slave's name */ 655 if ((ptr = ptsname(fdm)) == NULL) { 656 close(fdm); 657 return (-3); 658 } 659 strcpy(pts_name, ptr); 660 return (fdm); 661 } 662 663 /* 664 * ptys_open -- 665 * This function opens the slave pty. 666 */ 667 static int 668 ptys_open(int fdm, char *pts_name) 669 { 670 int fds; 671 672 if ((fds = open(pts_name, O_RDWR)) < 0) { 673 close(fdm); 674 return (-5); 675 } 676 677 #ifdef I_PUSH 678 if (ioctl(fds, I_PUSH, "ptem") < 0) { 679 close(fds); 680 close(fdm); 681 return (-6); 682 } 683 684 if (ioctl(fds, I_PUSH, "ldterm") < 0) { 685 close(fds); 686 close(fdm); 687 return (-7); 688 } 689 690 if (ioctl(fds, I_PUSH, "ttcompat") < 0) { 691 close(fds); 692 close(fdm); 693 return (-8); 694 } 695 #endif /* I_PUSH */ 696 697 return (fds); 698 } 699 700 #else /* !HAVE_SYS5_PTY */ 701 702 static int 703 sscr_pty(amaster, aslave, name, termp, winp) 704 int *amaster, *aslave; 705 char *name; 706 struct termios *termp; 707 void *winp; 708 { 709 static char line[] = "/dev/ptyXX"; 710 const char *cp1, *cp2; 711 int master, slave, ttygid; 712 struct group *gr; 713 714 if ((gr = getgrnam("tty")) != NULL) 715 ttygid = gr->gr_gid; 716 else 717 ttygid = -1; 718 719 for (cp1 = "pqrs"; *cp1; cp1++) { 720 line[8] = *cp1; 721 for (cp2 = "0123456789abcdef"; *cp2; cp2++) { 722 line[5] = 'p'; 723 line[9] = *cp2; 724 if ((master = open(line, O_RDWR, 0)) == -1) { 725 if (errno == ENOENT) 726 return (-1); /* out of ptys */ 727 } else { 728 line[5] = 't'; 729 (void) chown(line, getuid(), ttygid); 730 (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP); 731 #ifdef HAVE_REVOKE 732 (void) revoke(line); 733 #endif 734 if ((slave = open(line, O_RDWR, 0)) != -1) { 735 *amaster = master; 736 *aslave = slave; 737 if (name) 738 strcpy(name, line); 739 if (termp) 740 (void) tcsetattr(slave, 741 TCSAFLUSH, termp); 742 #ifdef TIOCSWINSZ 743 if (winp) 744 (void) ioctl(slave, TIOCSWINSZ, 745 (char *)winp); 746 #endif 747 return (0); 748 } 749 (void) close(master); 750 } 751 } 752 } 753 errno = ENOENT; /* out of ptys */ 754 return (-1); 755 } 756 757 #endif /* HAVE_SYS5_PTY */ 758 #endif /* !HAVE_OPENPTY */ 759