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