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