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.
7 * This code is derived from software contributed to Berkeley by
10 * See the LICENSE file for redistribution information.
15 #include <sys/types.h>
16 #include <sys/ioctl.h>
17 #include <sys/queue.h>
18 #include <sys/select.h>
22 #include <bitstring.h>
39 #include "../common/common.h"
42 #include "pathnames.h"
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);
52 * ex_script -- : sc[ript][!] [file]
53 * Switch to script mode.
55 * PUBLIC: int ex_script(SCR *, EXCMD *);
58 ex_script(SCR *sp, EXCMD *cmdp)
60 /* Vi only command. */
61 if (!F_ISSET(sp, SC_VI)) {
63 "150|The script command is only available in vi mode");
67 /* Switch to the new file. */
68 if (cmdp->argc != 0 && ex_edit(sp, cmdp))
71 /* Create the shell, figure out the prompt. */
80 * Create a pty setup for a shell.
88 /* We're going to need a shell. */
89 if (opts_empty(sp, O_SHELL, 0))
92 MALLOC_RET(sp, sc, sizeof(SCRIPT));
95 sc->sh_prompt_len = 0;
98 * There are two different processes running through this code.
99 * They are the shell and the parent.
101 sc->sh_master = sc->sh_slave = -1;
103 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
104 msgq(sp, M_SYSERR, "tcgetattr");
109 * Turn off output postprocessing and echo.
111 sc->sh_term.c_oflag &= ~OPOST;
112 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
114 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
115 msgq(sp, M_SYSERR, "tcgetattr");
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");
127 * Don't use vfork() here, because the signal semantics differ from
128 * implementation to implementation.
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);
138 case 0: /* Utility. */
141 * So that shells that do command line editing turn it off.
143 (void)setenv("TERM", "emacs", 1);
144 (void)setenv("TERMCAP", "emacs:", 1);
145 (void)setenv("EMACS", "t", 1);
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.
155 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
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);
163 /* Assumes that all shells have -i. */
164 sh_path = O_STR(sp, O_SHELL);
165 if ((sh = strrchr(sh_path, '/')) == NULL)
169 execl(sh_path, sh, "-i", NULL);
170 msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
172 default: /* Parent. */
176 if (sscr_getprompt(sp))
179 F_SET(sp, SC_SCRIPT);
180 F_SET(sp->gp, G_SCRWIN);
186 * Eat lines printed by the shell until a line with no trailing
187 * carriage return comes; set the prompt from that line.
190 sscr_getprompt(SCR *sp)
194 char *endp, *p, *t, buf[1024];
209 /* Wait up to a second for characters to read. */
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");
218 case 0: /* Timeout */
219 msgq(sp, M_ERR, "Error: timed out");
221 case 1: /* Characters to read. */
225 /* Read the characters. */
226 more: len = sizeof(buf) - (endp - buf);
227 switch (nr = read(sc->sh_master, endp, len)) {
229 msgq(sp, M_ERR, "Error: shell: EOF");
231 case -1: /* Error or interrupt. */
232 msgq(sp, M_SYSERR, "shell");
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))
244 if (db_last(sp, &lline) ||
245 db_append(sp, 0, lline, wp, wlen))
251 memmove(buf, t, endp - t);
252 endp = buf + (endp - t);
257 /* Wait up 1/10 of a second to make sure that we got it all. */
260 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
261 case -1: /* Error or interrupt. */
262 msgq(sp, M_SYSERR, "select");
264 case 0: /* Timeout */
266 case 1: /* Characters to read. */
270 /* Timed out, so theoretically we have a prompt. */
274 /* Append the line into the file. */
275 if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen))
277 if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) {
279 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated.");
280 prompterr: sscr_end(sp);
284 return (sscr_setprompt(sp, buf, llen));
289 * Take a line and hand it off to the shell.
291 * PUBLIC: int sscr_exec(SCR *, recno_t);
294 sscr_exec(SCR *sp, recno_t lno)
298 size_t blen, len, last_len, tlen;
299 int isempty, matchprompt, nw, rval;
304 /* If there's a prompt on the last line, append the command. */
305 if (db_last(sp, &last_lno))
307 if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen))
309 INT2CHAR(sp, wp, wlen, p, last_len);
310 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
312 GET_SPACE_RETC(sp, bp, blen, last_len + 128);
313 memmove(bp, p, last_len);
317 /* Get something to execute. */
318 if (db_eget(sp, lno, &wp, &wlen, &isempty)) {
324 /* Empty lines aren't interesting. */
327 INT2CHAR(sp, wp, wlen, p, len);
329 /* Delete any prompt. */
330 if (sscr_matchprompt(sp, p, len, &tlen)) {
332 empty: msgq(sp, M_BERR, "151|No command to execute");
339 /* Push the line to the shell. */
341 if ((nw = write(sc->sh_master, p, len)) != len)
344 if (write(sc->sh_master, "\n", 1) != 1) {
347 msgq(sp, M_SYSERR, "shell");
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))
359 FREE_SPACE(sp, bp, blen);
365 * Read any waiting shell input.
367 * PUBLIC: int sscr_input(SCR *);
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;
392 /* Check for input. */
393 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
395 msgq(sp, M_SYSERR, "select");
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) &&
414 * Take a line from the shell and insert it into the file.
425 size_t blen, len, tlen;
434 /* Find out where the end of the file is. */
435 if (db_last(sp, &lno))
439 GET_SPACE_RETC(sp, bp, blen, MINREAD);
442 /* Read the characters. */
445 more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
446 case 0: /* EOF; shell just exited. */
450 case -1: /* Error or interrupt. */
451 msgq(sp, M_SYSERR, "shell");
458 /* Append the lines into the file. */
459 for (p = t = bp; p < endp; ++p) {
460 if (*p == '\r' || *p == '\n') {
462 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
464 if (db_append(sp, 1, lno++, wp, wlen))
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.
478 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
482 FD_SET(sc->sh_master, &rdfd);
483 if (select(sc->sh_master + 1,
484 &rdfd, NULL, NULL, &tv) == 1) {
490 if (sscr_setprompt(sp, t, len))
492 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
494 if (db_append(sp, 1, lno++, wp, wlen))
498 /* The cursor moves to EOF. */
500 sp->cno = wlen ? wlen - 1 : 0;
501 rval = vs_refresh(sp, 1);
504 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated.");
506 ret: FREE_SPACE(sp, bp, blen);
513 * Set the prompt to the last line we got from the shell.
517 sscr_setprompt(SCR *sp, char *buf, size_t len)
523 MALLOC(sp, sc->sh_prompt, len + 1);
524 if (sc->sh_prompt == NULL) {
528 memmove(sc->sh_prompt, buf, len);
529 sc->sh_prompt_len = len;
530 sc->sh_prompt[len] = '\0';
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.
540 sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp)
547 if (line_len < (prompt_len = sc->sh_prompt_len))
550 for (pp = sc->sh_prompt;
551 prompt_len && line_len; --prompt_len, --line_len) {
553 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
556 for (; line_len && *lp != *pp; --line_len, ++lp);
573 * End the pipe to a shell.
575 * PUBLIC: int sscr_end(SCR *);
582 if ((sc = sp->script) == NULL)
585 /* Turn off the script flags. */
586 F_CLR(sp, SC_SCRIPT);
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);
595 /* This should have killed the child. */
596 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
608 * Set/clear the global scripting bit.
616 TAILQ_FOREACH(sp, gp->dq, q)
617 if (F_ISSET(sp, SC_SCRIPT)) {