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.6 1993/11/13 18:02:26 bostic Exp $ (Berkeley) $Date: 1993/11/13 18:02:26 $";
12 #include <sys/types.h>
23 static int sscr_init
__P((SCR
*, EXF
*));
24 static int sscr_matchprompt
__P((SCR
*, char *, size_t, size_t *));
27 * ex_script -- : sc[ript][!] [file]
29 * Switch to visual mode.
32 ex_script(sp
, ep
, cmdp
)
37 /* Vi only command. */
38 if (!IN_VI_MODE(sp
)) {
40 "The script command is only available in vi mode.");
44 /* Switch to the new file. */
45 if (ex_edit(sp
, ep
, cmdp
))
49 * Create the shell, figure out the prompt.
52 * The files just switched, use sp->ep.
54 if (sscr_init(sp
, sp
->ep
))
62 * Create a pipe to a shell.
74 char *endp
, *p
, *t
, *sh
, *sh_path
, buf
[1024];
77 * There are two different processes running through this code.
78 * They are the shell and the parent.
80 * Input and output are named from the shell's point of view. The
81 * shell reads from sh_in[0] and the parent writes to sh_in[1].
82 * The parent reads from sh_out[0] and the shell writes to sh_out[1].
84 sp
->sh_in
[0] = sp
->sh_in
[1] = sp
->sh_out
[0] = sp
->sh_out
[1] = -1;
85 if (pipe(sp
->sh_in
) < 0 || pipe(sp
->sh_out
) < 0) {
86 msgq(sp
, M_SYSERR
, "pipe");
90 switch (sp
->sh_pid
= vfork()) {
92 msgq(sp
, M_SYSERR
, "vfork");
93 err
: if (sp
->sh_in
[0] != -1)
94 (void)close(sp
->sh_in
[0]);
95 if (sp
->sh_in
[1] != -1)
96 (void)close(sp
->sh_in
[1]);
97 if (sp
->sh_out
[0] != -1)
98 (void)close(sp
->sh_out
[0]);
99 if (sp
->sh_out
[1] != -1)
100 (void)close(sp
->sh_out
[1]);
102 case 0: /* Utility. */
104 * The utility has default signal behavior. Don't bother
105 * using sigaction(2) 'cause we want the default behavior.
107 (void)signal(SIGINT
, SIG_DFL
);
108 (void)signal(SIGQUIT
, SIG_DFL
);
111 * Redirect stdin from the read end of the sh_in pipe,
112 * and redirect stdout/stderr to the write end of the
115 (void)dup2(sp
->sh_in
[0], STDIN_FILENO
);
116 (void)dup2(sp
->sh_out
[1], STDOUT_FILENO
);
117 (void)dup2(sp
->sh_out
[1], STDERR_FILENO
);
119 /* Close the utility's file descriptors. */
120 (void)close(sp
->sh_in
[0]);
121 (void)close(sp
->sh_in
[1]);
122 (void)close(sp
->sh_out
[0]);
123 (void)close(sp
->sh_out
[1]);
125 /* Assumes that all shells have -i. */
126 sh_path
= O_STR(sp
, O_SHELL
);
127 if ((sh
= strrchr(sh_path
, '/')) == NULL
)
131 execl(sh_path
, sh
, "-i", NULL
);
133 "Error: execl: %s: %s", sh_path
, strerror(errno
));
136 /* Close the pipe ends the parent won't use. */
137 (void)close(sp
->sh_in
[0]);
138 (void)close(sp
->sh_out
[1]);
143 * Figure out the prompt. The scheme here is pretty simple. Take
144 * the first three lines, i.e. lines that don't end with a newline,
145 * and see what they share. If they don't share anything, we quit.
146 * Otherwise, we mark what they share and use it for comparison later.
147 * There are a lot of built-in assumptions about how shells behave.
150 for (endp
= buf
, len
= sizeof(buf
), cnt
= 0;; ++cnt
) {
151 /* Wait up to a second for characters to read. */
154 FD_SET(sp
->sh_out
[0], &fdset
);
155 switch (select(sp
->sh_out
[0] + 1, &fdset
, NULL
, NULL
, &tv
)) {
156 case -1: /* Error or interrupt. */
157 msgq(sp
, M_SYSERR
, "select");
159 case 0: /* Timeout */
160 msgq(sp
, M_ERR
, "Error: timed out.");
162 case 1: /* Characters to read. */
166 /* Read the characters. */
167 more
: len
= sizeof(buf
) - (endp
- buf
);
168 switch (nr
= read(sp
->sh_out
[0], endp
, len
)) {
170 msgq(sp
, M_ERR
, "Error: shell: EOF");
172 case -1: /* Error or interrupt. */
173 msgq(sp
, M_SYSERR
, "shell");
180 /* If any complete lines, push them into the file. */
181 for (p
= t
= buf
; t
< endp
; ++t
)
182 if (sp
->special
[*t
] == K_CR
||
183 sp
->special
[*t
] == K_NL
) {
184 if (file_lline(sp
, ep
, &lline
) ||
185 file_aline(sp
, ep
, 0, lline
, buf
, t
- p
))
190 memmove(buf
, p
, endp
- p
);
191 endp
= buf
+ (endp
- p
);
196 /* Wait up 1/10 of a second to make sure that we got it all. */
199 switch (select(sp
->sh_out
[0] + 1, &fdset
, NULL
, NULL
, &tv
)) {
200 case -1: /* Error or interrupt. */
201 msgq(sp
, M_SYSERR
, "select");
203 case 0: /* Timeout */
205 case 1: /* Characters to read. */
209 /* Timed out, so theoretically we have a prompt. */
213 /* Append the line into the file. */
214 if (file_lline(sp
, ep
, &lline
) ||
215 file_aline(sp
, ep
, 0, lline
, buf
, llen
)) {
216 prompterr
: sscr_end(sp
);
221 if ((sp
->sh_prompt
= malloc(llen
)) == NULL
) {
222 msgq(sp
, M_SYSERR
, NULL
);
225 memmove(sp
->sh_prompt
, buf
, llen
);
226 sp
->sh_prompt_len
= llen
;
228 if (sp
->sh_prompt_len
< llen
)
229 llen
= sp
->sh_prompt_len
;
231 sp
->sh_prompt_len
= llen
;
232 /* Nul's in the prompt are "don't match" characters. */
233 for (t
= sp
->sh_prompt
, p
= buf
; --llen
; ++p
, ++t
)
241 /* Write a command to the shell. */
242 (void)write(sp
->sh_in
[1], "date\n", 5);
245 /* Turn on the script. */
253 * Take a line and hand it off to the shell.
256 sscr_exec(sp
, ep
, lno
)
262 size_t blen
, len
, last_len
, tlen
;
266 /* If there's a prompt on the last line, append the command. */
267 if (file_lline(sp
, ep
, &last_lno
))
269 if ((p
= file_gline(sp
, ep
, last_lno
, &last_len
)) == NULL
) {
270 GETLINE_ERR(sp
, last_lno
);
273 if (sscr_matchprompt(sp
, p
, last_len
, &tlen
) && tlen
== 0) {
275 GET_SPACE(sp
, bp
, blen
, 128 + last_len
);
276 memmove(bp
, p
, last_len
);
280 /* Get something to execute. */
281 if ((p
= file_gline(sp
, ep
, lno
, &len
)) == NULL
) {
282 if (file_lline(sp
, ep
, &lno
))
287 GETLINE_ERR(sp
, lno
);
291 /* Empty lines aren't interesting. */
295 /* Delete any prompt. */
296 if (sscr_matchprompt(sp
, p
, len
, &tlen
)) {
298 empty
: msgq(sp
, M_BERR
, "Nothing to execute.");
305 /* Push the line to the shell. */
306 if ((nw
= write(sp
->sh_in
[1], p
, len
)) != len
)
308 if (write(sp
->sh_in
[1], "\n", 1) != 1) {
311 msgq(sp
, M_SYSERR
, "shell");
316 ADD_SPACE(sp
, bp
, blen
, last_len
+ len
);
317 memmove(bp
+ last_len
, p
, len
);
318 if (file_sline(sp
, ep
, last_lno
, bp
, last_len
+ len
)) {
319 err1
: if (matchprompt
)
320 FREE_SPACE(sp
, bp
, blen
);
329 * Take a line from the shell and insert it into the file.
338 size_t blen
, len
, tlen
;
340 char *bp
, *endp
, *p
, *t
;
342 /* Find out where the end of the file is. */
344 if (file_lline(sp
, ep
, &lno
))
348 GET_SPACE(sp
, bp
, blen
, MINREAD
);
351 /* Read the characters. */
353 more
: switch (nr
= read(sp
->sh_out
[0], endp
, MINREAD
)) {
354 case 0: /* EOF; shell just exited. */
359 case -1: /* Error or interrupt. */
360 msgq(sp
, M_SYSERR
, "shell");
367 /* Append the lines into the file. */
368 for (p
= t
= bp
; p
< endp
; ++p
)
369 if (sp
->special
[*p
] == K_CR
|| sp
->special
[*p
] == K_NL
) {
371 if (file_aline(sp
, ep
, 1, lno
++, t
, len
))
378 * If the last thing from the shell isn't another prompt, wait
379 * up to 1/10 of a second for more stuff to show up, so that
380 * we don't break the output into two separate lines. Don't
381 * want to hang indefinitely because some program is hanging,
382 * confused the shell, or whatever.
384 if (!sscr_matchprompt(sp
, t
, len
, &tlen
) || tlen
!= 0) {
387 FD_SET(sp
->sh_out
[0], &sp
->rdfd
);
388 FD_CLR(STDIN_FILENO
, &sp
->rdfd
);
389 if (select(sp
->sh_out
[0] + 1,
390 &sp
->rdfd
, NULL
, NULL
, &tv
) == 1) {
396 if (file_aline(sp
, ep
, 1, lno
++, t
, len
))
400 /* The cursor moves to EOF. */
402 sp
->cno
= len
? len
- 1 : 0;
403 rval
= sp
->s_refresh(sp
, ep
);
405 ret
: FREE_SPACE(sp
, bp
, blen
);
410 * sscr_matchprompt --
411 * Check to see if a line matches the prompt. Nul's indicate
412 * parts that can change, in both content and size.
415 sscr_matchprompt(sp
, lp
, line_len
, lenp
)
418 size_t line_len
, *lenp
;
423 if (line_len
< (prompt_len
= sp
->sh_prompt_len
))
426 for (pp
= sp
->sh_prompt
;
427 prompt_len
&& line_len
; --prompt_len
, --line_len
) {
429 for (; prompt_len
&& *pp
== '\0'; --prompt_len
, ++pp
);
432 for (; line_len
&& *lp
!= *pp
; --line_len
, ++lp
);
449 * End the pipe to a shell.
457 /* Close down the parent's file descriptors. */
458 (void)close(sp
->sh_in
[1]);
459 (void)close(sp
->sh_out
[0]);
461 /* This should have killed the child. */
462 (void)waitpid(sp
->sh_pid
, &pstat
, 0);
465 FREE(sp
->sh_prompt
, sp
->sh_prompt_len
);
467 /* Turn off the script. */