1 /*- 2 * Copyright (c) 1992, 1993, 1994 3 * The Regents of the University of California. All rights reserved. 4 * Copyright (c) 1992, 1993, 1994, 1995, 1996 5 * Keith Bostic. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Brian Hirt. 9 * 10 * See the LICENSE file for redistribution information. 11 */ 12 13 #include "config.h" 14 15 #include <sys/types.h> 16 #include <sys/ioctl.h> 17 #include <sys/queue.h> 18 #include <sys/select.h> 19 #include <sys/stat.h> 20 #include <sys/wait.h> 21 22 #include <bitstring.h> 23 #include <errno.h> 24 #include <fcntl.h> 25 #include <grp.h> 26 #include <limits.h> 27 #include <signal.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <termios.h> 32 #include <unistd.h> 33 #ifdef HAVE_LIBUTIL_H 34 #include <libutil.h> 35 #else 36 #include <util.h> 37 #endif 38 39 #include "../common/common.h" 40 #include "../vi/vi.h" 41 #include "script.h" 42 #include "pathnames.h" 43 44 static void sscr_check(SCR *); 45 static int sscr_getprompt(SCR *); 46 static int sscr_init(SCR *); 47 static int sscr_insert(SCR *); 48 static int sscr_matchprompt(SCR *, char *, size_t, size_t *); 49 static int sscr_setprompt(SCR *, char *, size_t); 50 51 /* 52 * ex_script -- : sc[ript][!] [file] 53 * Switch to script mode. 54 * 55 * PUBLIC: int ex_script(SCR *, EXCMD *); 56 */ 57 int 58 ex_script(SCR *sp, EXCMD *cmdp) 59 { 60 /* Vi only command. */ 61 if (!F_ISSET(sp, SC_VI)) { 62 msgq(sp, M_ERR, 63 "150|The script command is only available in vi mode"); 64 return (1); 65 } 66 67 /* Switch to the new file. */ 68 if (cmdp->argc != 0 && ex_edit(sp, cmdp)) 69 return (1); 70 71 /* Create the shell, figure out the prompt. */ 72 if (sscr_init(sp)) 73 return (1); 74 75 return (0); 76 } 77 78 /* 79 * sscr_init -- 80 * Create a pty setup for a shell. 81 */ 82 static int 83 sscr_init(SCR *sp) 84 { 85 SCRIPT *sc; 86 char *sh, *sh_path; 87 88 /* We're going to need a shell. */ 89 if (opts_empty(sp, O_SHELL, 0)) 90 return (1); 91 92 MALLOC_RET(sp, sc, sizeof(SCRIPT)); 93 sp->script = sc; 94 sc->sh_prompt = NULL; 95 sc->sh_prompt_len = 0; 96 97 /* 98 * There are two different processes running through this code. 99 * They are the shell and the parent. 100 */ 101 sc->sh_master = sc->sh_slave = -1; 102 103 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) { 104 msgq(sp, M_SYSERR, "tcgetattr"); 105 goto err; 106 } 107 108 /* 109 * Turn off output postprocessing and echo. 110 */ 111 sc->sh_term.c_oflag &= ~OPOST; 112 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK); 113 114 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) { 115 msgq(sp, M_SYSERR, "tcgetattr"); 116 goto err; 117 } 118 119 if (openpty(&sc->sh_master, 120 &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) { 121 msgq(sp, M_SYSERR, "openpty"); 122 goto err; 123 } 124 125 /* 126 * __TK__ huh? 127 * Don't use vfork() here, because the signal semantics differ from 128 * implementation to implementation. 129 */ 130 switch (sc->sh_pid = fork()) { 131 case -1: /* Error. */ 132 msgq(sp, M_SYSERR, "fork"); 133 err: if (sc->sh_master != -1) 134 (void)close(sc->sh_master); 135 if (sc->sh_slave != -1) 136 (void)close(sc->sh_slave); 137 return (1); 138 case 0: /* Utility. */ 139 /* 140 * XXX 141 * So that shells that do command line editing turn it off. 142 */ 143 (void)setenv("TERM", "emacs", 1); 144 (void)setenv("TERMCAP", "emacs:", 1); 145 (void)setenv("EMACS", "t", 1); 146 147 (void)setsid(); 148 #ifdef TIOCSCTTY 149 /* 150 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY 151 * ioctl, not by opening a terminal device file. POSIX 1003.1 152 * doesn't define a portable way to do this. If TIOCSCTTY is 153 * not available, hope that the open does it. 154 */ 155 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0); 156 #endif 157 (void)close(sc->sh_master); 158 (void)dup2(sc->sh_slave, STDIN_FILENO); 159 (void)dup2(sc->sh_slave, STDOUT_FILENO); 160 (void)dup2(sc->sh_slave, STDERR_FILENO); 161 (void)close(sc->sh_slave); 162 163 /* Assumes that all shells have -i. */ 164 sh_path = O_STR(sp, O_SHELL); 165 if ((sh = strrchr(sh_path, '/')) == NULL) 166 sh = sh_path; 167 else 168 ++sh; 169 execl(sh_path, sh, "-i", NULL); 170 msgq_str(sp, M_SYSERR, sh_path, "execl: %s"); 171 _exit(127); 172 default: /* Parent. */ 173 break; 174 } 175 176 if (sscr_getprompt(sp)) 177 return (1); 178 179 F_SET(sp, SC_SCRIPT); 180 F_SET(sp->gp, G_SCRWIN); 181 return (0); 182 } 183 184 /* 185 * sscr_getprompt -- 186 * Eat lines printed by the shell until a line with no trailing 187 * carriage return comes; set the prompt from that line. 188 */ 189 static int 190 sscr_getprompt(SCR *sp) 191 { 192 EX_PRIVATE *exp; 193 struct timeval tv; 194 char *endp, *p, *t, buf[1024]; 195 SCRIPT *sc; 196 fd_set fdset; 197 recno_t lline; 198 size_t llen, len; 199 int nr; 200 CHAR_T *wp; 201 size_t wlen; 202 203 exp = EXP(sp); 204 205 FD_ZERO(&fdset); 206 endp = buf; 207 len = sizeof(buf); 208 209 /* Wait up to a second for characters to read. */ 210 tv.tv_sec = 5; 211 tv.tv_usec = 0; 212 sc = sp->script; 213 FD_SET(sc->sh_master, &fdset); 214 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { 215 case -1: /* Error or interrupt. */ 216 msgq(sp, M_SYSERR, "select"); 217 goto prompterr; 218 case 0: /* Timeout */ 219 msgq(sp, M_ERR, "Error: timed out"); 220 goto prompterr; 221 case 1: /* Characters to read. */ 222 break; 223 } 224 225 /* Read the characters. */ 226 more: len = sizeof(buf) - (endp - buf); 227 switch (nr = read(sc->sh_master, endp, len)) { 228 case 0: /* EOF. */ 229 msgq(sp, M_ERR, "Error: shell: EOF"); 230 goto prompterr; 231 case -1: /* Error or interrupt. */ 232 msgq(sp, M_SYSERR, "shell"); 233 goto prompterr; 234 default: 235 endp += nr; 236 break; 237 } 238 239 /* If any complete lines, push them into the file. */ 240 for (p = t = buf; p < endp; ++p) { 241 if (*p == '\r' || *p == '\n') { 242 if (CHAR2INT5(sp, exp->ibcw, t, p - t, wp, wlen)) 243 goto conv_err; 244 if (db_last(sp, &lline) || 245 db_append(sp, 0, lline, wp, wlen)) 246 goto prompterr; 247 t = p + 1; 248 } 249 } 250 if (p > buf) { 251 memmove(buf, t, endp - t); 252 endp = buf + (endp - t); 253 } 254 if (endp == buf) 255 goto more; 256 257 /* Wait up 1/10 of a second to make sure that we got it all. */ 258 tv.tv_sec = 0; 259 tv.tv_usec = 100000; 260 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) { 261 case -1: /* Error or interrupt. */ 262 msgq(sp, M_SYSERR, "select"); 263 goto prompterr; 264 case 0: /* Timeout */ 265 break; 266 case 1: /* Characters to read. */ 267 goto more; 268 } 269 270 /* Timed out, so theoretically we have a prompt. */ 271 llen = endp - buf; 272 endp = buf; 273 274 /* Append the line into the file. */ 275 if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen)) 276 goto conv_err; 277 if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) { 278 if (0) 279 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated."); 280 prompterr: sscr_end(sp); 281 return (1); 282 } 283 284 return (sscr_setprompt(sp, buf, llen)); 285 } 286 287 /* 288 * sscr_exec -- 289 * Take a line and hand it off to the shell. 290 * 291 * PUBLIC: int sscr_exec(SCR *, recno_t); 292 */ 293 int 294 sscr_exec(SCR *sp, recno_t lno) 295 { 296 SCRIPT *sc; 297 recno_t last_lno; 298 size_t blen, len, last_len, tlen; 299 int isempty, matchprompt, nw, rval; 300 char *bp = NULL, *p; 301 CHAR_T *wp; 302 size_t wlen; 303 304 /* If there's a prompt on the last line, append the command. */ 305 if (db_last(sp, &last_lno)) 306 return (1); 307 if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen)) 308 return (1); 309 INT2CHAR(sp, wp, wlen, p, last_len); 310 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) { 311 matchprompt = 1; 312 GET_SPACE_RETC(sp, bp, blen, last_len + 128); 313 memmove(bp, p, last_len); 314 } else 315 matchprompt = 0; 316 317 /* Get something to execute. */ 318 if (db_eget(sp, lno, &wp, &wlen, &isempty)) { 319 if (isempty) 320 goto empty; 321 goto err1; 322 } 323 324 /* Empty lines aren't interesting. */ 325 if (wlen == 0) 326 goto empty; 327 INT2CHAR(sp, wp, wlen, p, len); 328 329 /* Delete any prompt. */ 330 if (sscr_matchprompt(sp, p, len, &tlen)) { 331 if (tlen == len) { 332 empty: msgq(sp, M_BERR, "151|No command to execute"); 333 goto err1; 334 } 335 p += (len - tlen); 336 len = tlen; 337 } 338 339 /* Push the line to the shell. */ 340 sc = sp->script; 341 if ((nw = write(sc->sh_master, p, len)) != len) 342 goto err2; 343 rval = 0; 344 if (write(sc->sh_master, "\n", 1) != 1) { 345 err2: if (nw == 0) 346 errno = EIO; 347 msgq(sp, M_SYSERR, "shell"); 348 goto err1; 349 } 350 351 if (matchprompt) { 352 ADD_SPACE_RETC(sp, bp, blen, last_len + len); 353 memmove(bp + last_len, p, len); 354 CHAR2INT(sp, bp, last_len + len, wp, wlen); 355 if (db_set(sp, last_lno, wp, wlen)) 356 err1: rval = 1; 357 } 358 if (matchprompt) 359 FREE_SPACE(sp, bp, blen); 360 return (rval); 361 } 362 363 /* 364 * sscr_input -- 365 * Read any waiting shell input. 366 * 367 * PUBLIC: int sscr_input(SCR *); 368 */ 369 int 370 sscr_input(SCR *sp) 371 { 372 GS *gp; 373 struct timeval poll; 374 fd_set rdfd; 375 int maxfd; 376 377 gp = sp->gp; 378 379 loop: maxfd = 0; 380 FD_ZERO(&rdfd); 381 poll.tv_sec = 0; 382 poll.tv_usec = 0; 383 384 /* Set up the input mask. */ 385 TAILQ_FOREACH(sp, gp->dq, q) 386 if (F_ISSET(sp, SC_SCRIPT)) { 387 FD_SET(sp->script->sh_master, &rdfd); 388 if (sp->script->sh_master > maxfd) 389 maxfd = sp->script->sh_master; 390 } 391 392 /* Check for input. */ 393 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) { 394 case -1: 395 msgq(sp, M_SYSERR, "select"); 396 return (1); 397 case 0: 398 return (0); 399 default: 400 break; 401 } 402 403 /* Read the input. */ 404 TAILQ_FOREACH(sp, gp->dq, q) 405 if (F_ISSET(sp, SC_SCRIPT) && 406 FD_ISSET(sp->script->sh_master, &rdfd) && 407 sscr_insert(sp)) 408 return (1); 409 goto loop; 410 } 411 412 /* 413 * sscr_insert -- 414 * Take a line from the shell and insert it into the file. 415 */ 416 static int 417 sscr_insert(SCR *sp) 418 { 419 EX_PRIVATE *exp; 420 struct timeval tv; 421 char *endp, *p, *t; 422 SCRIPT *sc; 423 fd_set rdfd; 424 recno_t lno; 425 size_t blen, len, tlen; 426 int nr, rval; 427 char *bp; 428 CHAR_T *wp; 429 size_t wlen = 0; 430 431 exp = EXP(sp); 432 433 434 /* Find out where the end of the file is. */ 435 if (db_last(sp, &lno)) 436 return (1); 437 438 #define MINREAD 1024 439 GET_SPACE_RETC(sp, bp, blen, MINREAD); 440 endp = bp; 441 442 /* Read the characters. */ 443 rval = 1; 444 sc = sp->script; 445 more: switch (nr = read(sc->sh_master, endp, MINREAD)) { 446 case 0: /* EOF; shell just exited. */ 447 sscr_end(sp); 448 rval = 0; 449 goto ret; 450 case -1: /* Error or interrupt. */ 451 msgq(sp, M_SYSERR, "shell"); 452 goto ret; 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 (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen)) 463 goto conv_err; 464 if (db_append(sp, 1, lno++, wp, wlen)) 465 goto ret; 466 t = p + 1; 467 } 468 } 469 if (p > t) { 470 len = p - t; 471 /* 472 * If the last thing from the shell isn't another prompt, wait 473 * up to 1/10 of a second for more stuff to show up, so that 474 * we don't break the output into two separate lines. Don't 475 * want to hang indefinitely because some program is hanging, 476 * confused the shell, or whatever. 477 */ 478 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) { 479 tv.tv_sec = 0; 480 tv.tv_usec = 100000; 481 FD_ZERO(&rdfd); 482 FD_SET(sc->sh_master, &rdfd); 483 if (select(sc->sh_master + 1, 484 &rdfd, NULL, NULL, &tv) == 1) { 485 memmove(bp, t, len); 486 endp = bp + len; 487 goto more; 488 } 489 } 490 if (sscr_setprompt(sp, t, len)) 491 return (1); 492 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen)) 493 goto conv_err; 494 if (db_append(sp, 1, lno++, wp, wlen)) 495 goto ret; 496 } 497 498 /* The cursor moves to EOF. */ 499 sp->lno = lno; 500 sp->cno = wlen ? wlen - 1 : 0; 501 rval = vs_refresh(sp, 1); 502 503 if (0) 504 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated."); 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(SCR *sp, char *buf, size_t len) 518 { 519 SCRIPT *sc; 520 521 sc = sp->script; 522 free(sc->sh_prompt); 523 MALLOC(sp, sc->sh_prompt, 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_matchprompt -- 536 * Check to see if a line matches the prompt. Nul's indicate 537 * parts that can change, in both content and size. 538 */ 539 static int 540 sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp) 541 { 542 SCRIPT *sc; 543 size_t prompt_len; 544 char *pp; 545 546 sc = sp->script; 547 if (line_len < (prompt_len = sc->sh_prompt_len)) 548 return (0); 549 550 for (pp = sc->sh_prompt; 551 prompt_len && line_len; --prompt_len, --line_len) { 552 if (*pp == '\0') { 553 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp); 554 if (!prompt_len) 555 return (0); 556 for (; line_len && *lp != *pp; --line_len, ++lp); 557 if (!line_len) 558 return (0); 559 } 560 if (*pp++ != *lp++) 561 break; 562 } 563 564 if (prompt_len) 565 return (0); 566 if (lenp != NULL) 567 *lenp = line_len; 568 return (1); 569 } 570 571 /* 572 * sscr_end -- 573 * End the pipe to a shell. 574 * 575 * PUBLIC: int sscr_end(SCR *); 576 */ 577 int 578 sscr_end(SCR *sp) 579 { 580 SCRIPT *sc; 581 582 if ((sc = sp->script) == NULL) 583 return (0); 584 585 /* Turn off the script flags. */ 586 F_CLR(sp, SC_SCRIPT); 587 sscr_check(sp); 588 589 /* Close down the parent's file descriptors. */ 590 if (sc->sh_master != -1) 591 (void)close(sc->sh_master); 592 if (sc->sh_slave != -1) 593 (void)close(sc->sh_slave); 594 595 /* This should have killed the child. */ 596 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0); 597 598 /* Free memory. */ 599 free(sc->sh_prompt); 600 free(sc); 601 sp->script = NULL; 602 603 return (0); 604 } 605 606 /* 607 * sscr_check -- 608 * Set/clear the global scripting bit. 609 */ 610 static void 611 sscr_check(SCR *sp) 612 { 613 GS *gp; 614 615 gp = sp->gp; 616 TAILQ_FOREACH(sp, gp->dq, q) 617 if (F_ISSET(sp, SC_SCRIPT)) { 618 F_SET(gp, G_SCRWIN); 619 return; 620 } 621 F_CLR(gp, G_SCRWIN); 622 } 623