1 /* $NetBSD: ex_script.c,v 1.6 2015/11/29 17:09:33 christos 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.6 2015/11/29 17:09:33 christos 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 __NetBSD__ 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 __NetBSD__ 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 strnstr(p, sc->sh_prompt, last_len) == p) { 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 (strnstr(p, sc->sh_prompt, len) == p) { 298 len -= sc->sh_prompt_len; 299 if (len == 0) { 300 empty: msgq(sp, M_BERR, "151|No command to execute"); 301 goto err1; 302 } 303 p += sc->sh_prompt_len; 304 } 305 306 /* Push the line to the shell. */ 307 if ((size_t)(nw = write(sc->sh_master, p, len)) != len) 308 goto err2; 309 rval = 0; 310 if (write(sc->sh_master, "\n", 1) != 1) { 311 err2: if (nw == 0) 312 errno = EIO; 313 msgq(sp, M_SYSERR, "shell"); 314 goto err1; 315 } 316 317 if (matchprompt) { 318 ADD_SPACE_GOTO(sp, char, bp, blen, last_len + len); 319 memmove(bp + last_len, p, len); 320 CHAR2INT(sp, bp, last_len + len, ip, ilen); 321 if (db_set(sp, last_lno, ip, ilen)) 322 err1: rval = 1; 323 } 324 if (matchprompt) 325 alloc_err: FREE_SPACE(sp, bp, blen); 326 return (rval); 327 } 328 329 /* 330 * sscr_check_input - 331 * Check whether any input from shell or passed set. 332 * 333 * PUBLIC: int sscr_check_input __P((SCR *sp, fd_set *rdfd, int maxfd)); 334 */ 335 int 336 sscr_check_input(SCR *sp, fd_set *fdset, int maxfd) 337 { 338 fd_set rdfd; 339 SCR *tsp; 340 WIN *wp; 341 342 wp = sp->wp; 343 344 loop: memcpy(&rdfd, fdset, sizeof(fd_set)); 345 346 TAILQ_FOREACH(tsp, &wp->scrq, q) 347 if (F_ISSET(sp, SC_SCRIPT)) { 348 FD_SET(sp->script->sh_master, &rdfd); 349 if (sp->script->sh_master > maxfd) 350 maxfd = sp->script->sh_master; 351 } 352 switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) { 353 case 0: 354 abort(); 355 case -1: 356 return 1; 357 default: 358 break; 359 } 360 TAILQ_FOREACH(tsp, &wp->scrq, q) 361 if (F_ISSET(sp, SC_SCRIPT) && 362 FD_ISSET(sp->script->sh_master, &rdfd)) { 363 if (sscr_input(sp)) 364 return 1; 365 goto loop; 366 } 367 return 0; 368 } 369 370 /* 371 * sscr_input -- 372 * Read any waiting shell input. 373 * 374 * PUBLIC: int sscr_input __P((SCR *)); 375 */ 376 int 377 sscr_input(SCR *sp) 378 { 379 WIN *wp; 380 struct timeval poll; 381 fd_set rdfd; 382 int maxfd; 383 384 wp = sp->wp; 385 386 loop: maxfd = 0; 387 FD_ZERO(&rdfd); 388 poll.tv_sec = 0; 389 poll.tv_usec = 0; 390 391 /* Set up the input mask. */ 392 TAILQ_FOREACH(sp, &wp->scrq, q) 393 if (F_ISSET(sp, SC_SCRIPT)) { 394 FD_SET(sp->script->sh_master, &rdfd); 395 if (sp->script->sh_master > maxfd) 396 maxfd = sp->script->sh_master; 397 } 398 399 /* Check for input. */ 400 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) { 401 case -1: 402 msgq(sp, M_SYSERR, "select"); 403 return (1); 404 case 0: 405 return (0); 406 default: 407 break; 408 } 409 410 /* Read the input. */ 411 TAILQ_FOREACH(sp, &wp->scrq, q) 412 if (F_ISSET(sp, SC_SCRIPT) && 413 FD_ISSET(sp->script->sh_master, &rdfd) && 414 sscr_insert(sp)) 415 return (1); 416 goto loop; 417 } 418 419 /* 420 * sscr_insert -- 421 * Take a line from the shell and insert it into the file. 422 */ 423 static int 424 sscr_insert(SCR *sp) 425 { 426 struct timeval tv; 427 char *endp, *p, *t; 428 SCRIPT *sc; 429 fd_set rdfd; 430 db_recno_t lno; 431 size_t len; 432 ssize_t nr; 433 char bp[1024]; 434 const CHAR_T *ip; 435 size_t ilen = 0; 436 437 /* Find out where the end of the file is. */ 438 if (db_last(sp, &lno)) 439 return (1); 440 441 endp = bp; 442 443 /* Read the characters. */ 444 sc = sp->script; 445 more: switch (nr = read(sc->sh_master, endp, bp + sizeof(bp) - endp)) { 446 case 0: /* EOF; shell just exited. */ 447 sscr_end(sp); 448 return (0); 449 case -1: /* Error or interrupt. */ 450 msgq(sp, M_SYSERR, "shell"); 451 return (1); 452 default: 453 endp += nr; 454 break; 455 } 456 457 /* Append the lines into the file. */ 458 for (p = t = bp; p < endp; ++p) { 459 if (*p == '\r' || *p == '\n') { 460 len = p - t; 461 if (CHAR2INT(sp, t, len, ip, ilen) || 462 db_append(sp, 1, lno++, ip, ilen)) 463 return (1); 464 t = p + 1; 465 } 466 } 467 /* 468 * If the last thing from the shell isn't another prompt, wait up to 469 * 1/10 of a second for more stuff to show up, so that we don't break 470 * the output into two separate lines. Don't want to hang indefinitely 471 * because some program is hanging, confused the shell, or whatever. 472 * Note that sc->sh_prompt can be NULL here. 473 */ 474 len = p - t; 475 if (sc->sh_prompt == NULL || len != sc->sh_prompt_len || 476 strnstr(p, sc->sh_prompt, len) == NULL) { 477 tv.tv_sec = 0; 478 tv.tv_usec = 100000; 479 FD_ZERO(&rdfd); 480 FD_SET(sc->sh_master, &rdfd); 481 if (select(sc->sh_master + 1, &rdfd, NULL, NULL, &tv) == 1) { 482 if (len == sizeof(bp)) { 483 if (CHAR2INT(sp, t, len, ip, ilen) || 484 db_append(sp, 1, lno++, ip, ilen)) 485 return (1); 486 endp = bp; 487 } else { 488 memmove(bp, t, len); 489 endp = bp + len; 490 } 491 goto more; 492 } 493 if (sscr_setprompt(sp, t, len)) 494 return (1); 495 } 496 497 /* Append the remains into the file, and the cursor moves to EOF. */ 498 if (len > 0) { 499 if (CHAR2INT(sp, t, len, ip, ilen) || 500 db_append(sp, 1, lno++, ip, ilen)) 501 return (1); 502 sp->cno = ilen - 1; 503 } else 504 sp->cno = 0; 505 sp->lno = lno; 506 return (vs_refresh(sp, 1)); 507 } 508 509 /* 510 * sscr_setprompt -- 511 * 512 * Set the prompt to the last line we got from the shell. 513 * 514 */ 515 static int 516 sscr_setprompt(SCR *sp, char *buf, size_t len) 517 { 518 SCRIPT *sc; 519 520 sc = sp->script; 521 if (sc->sh_prompt) 522 free(sc->sh_prompt); 523 MALLOC(sp, sc->sh_prompt, char *, len + 1); 524 if (sc->sh_prompt == NULL) { 525 sscr_end(sp); 526 return (1); 527 } 528 memmove(sc->sh_prompt, buf, len); 529 sc->sh_prompt_len = len; 530 sc->sh_prompt[len] = '\0'; 531 return (0); 532 } 533 534 /* 535 * sscr_end -- 536 * End the pipe to a shell. 537 * 538 * PUBLIC: int sscr_end __P((SCR *)); 539 */ 540 int 541 sscr_end(SCR *sp) 542 { 543 SCRIPT *sc; 544 545 if ((sc = sp->script) == NULL) 546 return (0); 547 548 /* Turn off the script flags. */ 549 F_CLR(sp, SC_SCRIPT); 550 sscr_check(sp); 551 552 /* Close down the parent's file descriptors. */ 553 if (sc->sh_master != -1) 554 (void)close(sc->sh_master); 555 if (sc->sh_slave != -1) 556 (void)close(sc->sh_slave); 557 558 /* This should have killed the child. */ 559 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0); 560 561 /* Free memory. */ 562 free(sc->sh_prompt); 563 free(sc); 564 sp->script = NULL; 565 566 return (0); 567 } 568 569 /* 570 * sscr_check -- 571 * Set/clear the global scripting bit. 572 */ 573 static void 574 sscr_check(SCR *sp) 575 { 576 GS *gp; 577 WIN *wp; 578 579 gp = sp->gp; 580 wp = sp->wp; 581 TAILQ_FOREACH(sp, &wp->scrq, q) 582 if (F_ISSET(sp, SC_SCRIPT)) { 583 F_SET(gp, G_SCRWIN); 584 return; 585 } 586 F_CLR(gp, G_SCRWIN); 587 } 588 589 #ifndef __NetBSD__ 590 #ifdef HAVE_SYS5_PTY 591 static int ptys_open __P((int, char *)); 592 static int ptym_open __P((char *)); 593 594 static int 595 sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp) 596 { 597 int master, slave; 598 599 /* open master terminal */ 600 if ((master = ptym_open(name)) < 0) { 601 errno = ENOENT; /* out of ptys */ 602 return (-1); 603 } 604 605 /* open slave terminal */ 606 if ((slave = ptys_open(master, name)) >= 0) { 607 *amaster = master; 608 *aslave = slave; 609 } else { 610 errno = ENOENT; /* out of ptys */ 611 return (-1); 612 } 613 614 if (termp) 615 (void) tcsetattr(slave, TCSAFLUSH, termp); 616 #ifdef TIOCSWINSZ 617 if (winp != NULL) 618 (void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp); 619 #endif 620 return (0); 621 } 622 623 /* 624 * ptym_open -- 625 * This function opens a master pty and returns the file descriptor 626 * to it. pts_name is also returned which is the name of the slave. 627 */ 628 static int 629 ptym_open(char *pts_name) 630 { 631 int fdm; 632 char *ptr; 633 634 strcpy(pts_name, _PATH_SYSV_PTY); 635 if ((fdm = open(pts_name, O_RDWR)) < 0 ) 636 return (-1); 637 638 if (grantpt(fdm) < 0) { 639 close(fdm); 640 return (-2); 641 } 642 643 if (unlockpt(fdm) < 0) { 644 close(fdm); 645 return (-3); 646 } 647 648 if (unlockpt(fdm) < 0) { 649 close(fdm); 650 return (-3); 651 } 652 653 /* get slave's name */ 654 if ((ptr = ptsname(fdm)) == NULL) { 655 close(fdm); 656 return (-3); 657 } 658 strcpy(pts_name, ptr); 659 return (fdm); 660 } 661 662 /* 663 * ptys_open -- 664 * This function opens the slave pty. 665 */ 666 static int 667 ptys_open(int fdm, char *pts_name) 668 { 669 int fds; 670 671 if ((fds = open(pts_name, O_RDWR)) < 0) { 672 close(fdm); 673 return (-5); 674 } 675 676 #ifdef I_PUSH 677 if (ioctl(fds, I_PUSH, "ptem") < 0) { 678 close(fds); 679 close(fdm); 680 return (-6); 681 } 682 683 if (ioctl(fds, I_PUSH, "ldterm") < 0) { 684 close(fds); 685 close(fdm); 686 return (-7); 687 } 688 689 if (ioctl(fds, I_PUSH, "ttcompat") < 0) { 690 close(fds); 691 close(fdm); 692 return (-8); 693 } 694 #endif /* I_PUSH */ 695 696 return (fds); 697 } 698 699 #else /* !HAVE_SYS5_PTY */ 700 701 static int 702 sscr_pty(amaster, aslave, name, termp, winp) 703 int *amaster, *aslave; 704 char *name; 705 struct termios *termp; 706 void *winp; 707 { 708 static char line[] = "/dev/ptyXX"; 709 const char *cp1, *cp2; 710 int master, slave, ttygid; 711 struct group *gr; 712 713 if ((gr = getgrnam("tty")) != NULL) 714 ttygid = gr->gr_gid; 715 else 716 ttygid = -1; 717 718 for (cp1 = "pqrs"; *cp1; cp1++) { 719 line[8] = *cp1; 720 for (cp2 = "0123456789abcdef"; *cp2; cp2++) { 721 line[5] = 'p'; 722 line[9] = *cp2; 723 if ((master = open(line, O_RDWR, 0)) == -1) { 724 if (errno == ENOENT) 725 return (-1); /* out of ptys */ 726 } else { 727 line[5] = 't'; 728 (void) chown(line, getuid(), ttygid); 729 (void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP); 730 #ifdef HAVE_REVOKE 731 (void) revoke(line); 732 #endif 733 if ((slave = open(line, O_RDWR, 0)) != -1) { 734 *amaster = master; 735 *aslave = slave; 736 if (name) 737 strcpy(name, line); 738 if (termp) 739 (void) tcsetattr(slave, 740 TCSAFLUSH, termp); 741 #ifdef TIOCSWINSZ 742 if (winp) 743 (void) ioctl(slave, TIOCSWINSZ, 744 (char *)winp); 745 #endif 746 return (0); 747 } 748 (void) close(master); 749 } 750 } 751 } 752 errno = ENOENT; /* out of ptys */ 753 return (-1); 754 } 755 756 #endif /* HAVE_SYS5_PTY */ 757 #endif /* !__NetBSD__ */ 758