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