2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
9 static char sccsid
[] = "$Id: ex_script.c,v 8.10 1993/12/22 15:44:39 bostic Exp $ (Berkeley) $Date: 1993/12/22 15:44:39 $";
12 #include <sys/types.h>
13 #include <sys/ioctl.h>
28 int openpty
__P((int *, int *, char *, struct termios
*, struct winsize
*));
30 static int sscr_getprompt
__P((SCR
*, EXF
*));
31 static int sscr_init
__P((SCR
*, EXF
*));
32 static int sscr_matchprompt
__P((SCR
*, char *, size_t, size_t *));
33 static int sscr_setprompt
__P((SCR
*, char *, size_t));
36 * ex_script -- : sc[ript][!] [file]
38 * Switch to script mode.
41 ex_script(sp
, ep
, cmdp
)
46 /* Vi only command. */
47 if (!IN_VI_MODE(sp
)) {
49 "The script command is only available in vi mode.");
53 /* Switch to the new file. */
54 if (cmdp
->argc
!= 0 && ex_edit(sp
, ep
, cmdp
))
58 * Create the shell, figure out the prompt.
61 * The files just switched, use sp->ep.
63 if (sscr_init(sp
, sp
->ep
))
71 * Create a pty setup for a shell.
81 MALLOC_RET(sp
, sc
, SCRIPT
*, sizeof(SCRIPT
));
84 sc
->sh_prompt_len
= 0;
87 * There are two different processes running through this code.
88 * They are the shell and the parent.
90 sc
->sh_master
= sc
->sh_slave
= -1;
92 if (tcgetattr(STDIN_FILENO
, &sc
->sh_term
) == -1) {
93 msgq(sp
, M_SYSERR
, "tcgetattr");
98 * Turn off output postprocessing and echo.
100 sc
->sh_term
.c_oflag
&= ~OPOST
;
101 sc
->sh_term
.c_cflag
&= ~(ECHO
|ECHOE
|ECHONL
|ECHOK
);
103 if (ioctl(STDIN_FILENO
, TIOCGWINSZ
, &sc
->sh_win
) == -1) {
104 msgq(sp
, M_SYSERR
, "tcgetattr");
108 if (openpty(&sc
->sh_master
,
109 &sc
->sh_slave
, sc
->sh_name
, &sc
->sh_term
, &sc
->sh_win
) == -1) {
110 msgq(sp
, M_SYSERR
, "openpty");
115 * Don't use vfork() here, because the signal semantics
116 * differ from implementation to implementation.
118 switch (sc
->sh_pid
= fork()) {
119 case -1: /* Error. */
120 msgq(sp
, M_SYSERR
, "fork");
121 err
: if (sc
->sh_master
!= -1)
122 (void)close(sc
->sh_master
);
123 if (sc
->sh_slave
!= -1)
124 (void)close(sc
->sh_slave
);
126 case 0: /* Utility. */
128 * The utility has default signal behavior. Don't bother
129 * using sigaction(2) 'cause we want the default behavior.
131 (void)signal(SIGINT
, SIG_DFL
);
132 (void)signal(SIGQUIT
, SIG_DFL
);
136 * So that shells that do command line editing turn it off.
138 (void)putenv("TERM=emacs");
139 (void)putenv("TERMCAP=emacs:");
140 (void)putenv("EMACS=t");
145 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
146 * ioctl, not by opening a terminal device file. POSIX 1003.1
147 * doesn't define a portable way to do this. If TIOCSCTTY is
148 * not available, hope that the open does it.
150 (void)ioctl(sc
->sh_slave
, TIOCSCTTY
, 0);
152 (void)close(sc
->sh_master
);
153 (void)dup2(sc
->sh_slave
, STDIN_FILENO
);
154 (void)dup2(sc
->sh_slave
, STDOUT_FILENO
);
155 (void)dup2(sc
->sh_slave
, STDERR_FILENO
);
156 (void)close(sc
->sh_slave
);
158 /* Assumes that all shells have -i. */
159 sh_path
= O_STR(sp
, O_SHELL
);
160 if ((sh
= strrchr(sh_path
, '/')) == NULL
)
164 execl(sh_path
, sh
, "-i", NULL
);
166 "Error: execl: %s: %s", sh_path
, strerror(errno
));
172 if (sscr_getprompt(sp
, ep
))
175 F_SET(sp
, S_REDRAW
| S_SCRIPT
);
182 * Eat lines printed by the shell until a line with no trailing
183 * carriage return comes; set the prompt from that line.
186 sscr_getprompt(sp
, ep
)
197 char *endp
, *p
, *t
, buf
[1024];
203 /* Wait up to a second for characters to read. */
207 FD_SET(sc
->sh_master
, &fdset
);
208 switch (select(sc
->sh_master
+ 1, &fdset
, NULL
, NULL
, &tv
)) {
209 case -1: /* Error or interrupt. */
210 msgq(sp
, M_SYSERR
, "select");
212 case 0: /* Timeout */
213 msgq(sp
, M_ERR
, "Error: timed out.");
215 case 1: /* Characters to read. */
219 /* Read the characters. */
220 more
: len
= sizeof(buf
) - (endp
- buf
);
221 switch (nr
= read(sc
->sh_master
, endp
, len
)) {
223 msgq(sp
, M_ERR
, "Error: shell: EOF");
225 case -1: /* Error or interrupt. */
226 msgq(sp
, M_SYSERR
, "shell");
233 /* If any complete lines, push them into the file. */
234 for (p
= t
= buf
; p
< endp
; ++p
) {
235 value
= term_key_val(sp
, *p
);
236 if (value
== K_CR
|| value
== K_NL
) {
237 if (file_lline(sp
, ep
, &lline
) ||
238 file_aline(sp
, ep
, 0, lline
, t
, p
- t
))
244 memmove(buf
, t
, endp
- t
);
245 endp
= buf
+ (endp
- t
);
250 /* Wait up 1/10 of a second to make sure that we got it all. */
253 switch (select(sc
->sh_master
+ 1, &fdset
, NULL
, NULL
, &tv
)) {
254 case -1: /* Error or interrupt. */
255 msgq(sp
, M_SYSERR
, "select");
257 case 0: /* Timeout */
259 case 1: /* Characters to read. */
263 /* Timed out, so theoretically we have a prompt. */
267 /* Append the line into the file. */
268 if (file_lline(sp
, ep
, &lline
) ||
269 file_aline(sp
, ep
, 0, lline
, buf
, llen
)) {
270 prompterr
: sscr_end(sp
);
274 return (sscr_setprompt(sp
, buf
, llen
));
279 * Take a line and hand it off to the shell.
282 sscr_exec(sp
, ep
, lno
)
289 size_t blen
, len
, last_len
, tlen
;
290 int matchprompt
, nw
, rval
;
293 /* If there's a prompt on the last line, append the command. */
294 if (file_lline(sp
, ep
, &last_lno
))
296 if ((p
= file_gline(sp
, ep
, last_lno
, &last_len
)) == NULL
) {
297 GETLINE_ERR(sp
, last_lno
);
300 if (sscr_matchprompt(sp
, p
, last_len
, &tlen
) && tlen
== 0) {
302 GET_SPACE_RET(sp
, bp
, blen
, last_len
+ 128);
303 memmove(bp
, p
, last_len
);
307 /* Get something to execute. */
308 if ((p
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
309 if (file_lline(sp
, ep
, &lno
))
314 GETLINE_ERR(sp
, lno
);
318 /* Empty lines aren't interesting. */
322 /* Delete any prompt. */
323 if (sscr_matchprompt(sp
, p
, len
, &tlen
)) {
325 empty
: msgq(sp
, M_BERR
, "Nothing to execute.");
332 /* Push the line to the shell. */
334 if ((nw
= write(sc
->sh_master
, p
, len
)) != len
)
337 if (write(sc
->sh_master
, "\n", 1) != 1) {
340 msgq(sp
, M_SYSERR
, "shell");
345 ADD_SPACE_RET(sp
, bp
, blen
, last_len
+ len
);
346 memmove(bp
+ last_len
, p
, len
);
347 if (file_sline(sp
, ep
, last_lno
, bp
, last_len
+ len
))
351 FREE_SPACE(sp
, bp
, blen
);
357 * Take a line from the shell and insert it into the file.
367 size_t blen
, len
, tlen
;
370 char *bp
, *endp
, *p
, *t
;
372 /* Find out where the end of the file is. */
374 if (file_lline(sp
, ep
, &lno
))
378 GET_SPACE_RET(sp
, bp
, blen
, MINREAD
);
381 /* Read the characters. */
384 more
: switch (nr
= read(sc
->sh_master
, endp
, MINREAD
)) {
385 case 0: /* EOF; shell just exited. */
390 case -1: /* Error or interrupt. */
391 msgq(sp
, M_SYSERR
, "shell");
398 /* Append the lines into the file. */
399 for (p
= t
= bp
; p
< endp
; ++p
) {
400 value
= term_key_val(sp
, *p
);
401 if (value
== K_CR
|| value
== K_NL
) {
403 if (file_aline(sp
, ep
, 1, lno
++, t
, len
))
411 * If the last thing from the shell isn't another prompt, wait
412 * up to 1/10 of a second for more stuff to show up, so that
413 * we don't break the output into two separate lines. Don't
414 * want to hang indefinitely because some program is hanging,
415 * confused the shell, or whatever.
417 if (!sscr_matchprompt(sp
, t
, len
, &tlen
) || tlen
!= 0) {
420 FD_SET(sc
->sh_master
, &sp
->rdfd
);
421 FD_CLR(STDIN_FILENO
, &sp
->rdfd
);
422 if (select(sc
->sh_master
+ 1,
423 &sp
->rdfd
, NULL
, NULL
, &tv
) == 1) {
429 if (sscr_setprompt(sp
, t
, len
))
431 if (file_aline(sp
, ep
, 1, lno
++, t
, len
))
435 /* The cursor moves to EOF. */
437 sp
->cno
= len
? len
- 1 : 0;
438 rval
= sp
->s_refresh(sp
, ep
);
440 ret
: FREE_SPACE(sp
, bp
, blen
);
447 * Set the prompt to the last line we got from the shell.
451 sscr_setprompt(sp
, buf
, len
)
460 FREE(sc
->sh_prompt
, sc
->sh_prompt_len
);
461 MALLOC(sp
, sc
->sh_prompt
, char *, len
+ 1);
462 if (sc
->sh_prompt
== NULL
) {
466 memmove(sc
->sh_prompt
, buf
, len
);
467 sc
->sh_prompt_len
= len
;
468 sc
->sh_prompt
[len
] = '\0';
473 * sscr_matchprompt --
474 * Check to see if a line matches the prompt. Nul's indicate
475 * parts that can change, in both content and size.
478 sscr_matchprompt(sp
, lp
, line_len
, lenp
)
481 size_t line_len
, *lenp
;
488 if (line_len
< (prompt_len
= sc
->sh_prompt_len
))
491 for (pp
= sc
->sh_prompt
;
492 prompt_len
&& line_len
; --prompt_len
, --line_len
) {
494 for (; prompt_len
&& *pp
== '\0'; --prompt_len
, ++pp
);
497 for (; line_len
&& *lp
!= *pp
; --line_len
, ++lp
);
514 * End the pipe to a shell.
523 if ((sc
= sp
->script
) == NULL
)
526 /* Turn off the script flag. */
529 /* Close down the parent's file descriptors. */
530 if (sc
->sh_master
!= -1)
531 (void)close(sc
->sh_master
);
532 if (sc
->sh_slave
!= -1)
533 (void)close(sc
->sh_slave
);
535 /* This should have killed the child. */
536 rval
= proc_wait(sp
, (long)sc
->sh_pid
, "script-shell", 0);
539 FREE(sc
->sh_prompt
, sc
->sh_prompt_len
);
540 FREE(sc
, sizeof(SCRIPT
));