if no messages waiting, always display the file status line on
[nvi.git] / ex / ex_script.c
blobcab30ea615a9f5c0a1b34fe8d21f74eb2029fba3
1 /*-
2 * Copyright (c) 1992, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
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 $";
10 #endif /* not lint */
12 #include <sys/types.h>
13 #include <sys/wait.h>
15 #include <errno.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
20 #include "vi.h"
21 #include "excmd.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.
31 int
32 ex_script(sp, ep, cmdp)
33 SCR *sp;
34 EXF *ep;
35 EXCMDARG *cmdp;
37 /* Vi only command. */
38 if (!IN_VI_MODE(sp)) {
39 msgq(sp, M_ERR,
40 "The script command is only available in vi mode.");
41 return (1);
44 /* Switch to the new file. */
45 if (ex_edit(sp, ep, cmdp))
46 return (1);
49 * Create the shell, figure out the prompt.
51 * !!!
52 * The files just switched, use sp->ep.
54 if (sscr_init(sp, sp->ep))
55 return (1);
57 return (0);
61 * sscr_init --
62 * Create a pipe to a shell.
64 static int
65 sscr_init(sp, ep)
66 SCR *sp;
67 EXF *ep;
69 struct timeval tv;
70 fd_set fdset;
71 recno_t lline;
72 size_t llen, len;
73 int cnt, nr;
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");
87 goto err;
90 switch (sp->sh_pid = vfork()) {
91 case -1: /* Error. */
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]);
101 return (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
113 * sh_out pipe.
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)
128 sh = sh_path;
129 else
130 ++sh;
131 execl(sh_path, sh, "-i", NULL);
132 msgq(sp, M_ERR,
133 "Error: execl: %s: %s", sh_path, strerror(errno));
134 _exit(127);
135 default:
136 /* Close the pipe ends the parent won't use. */
137 (void)close(sp->sh_in[0]);
138 (void)close(sp->sh_out[1]);
139 break;
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.
149 FD_ZERO(&fdset);
150 for (endp = buf, len = sizeof(buf), cnt = 0;; ++cnt) {
151 /* Wait up to a second for characters to read. */
152 tv.tv_sec = 1;
153 tv.tv_usec = 0;
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");
158 goto prompterr;
159 case 0: /* Timeout */
160 msgq(sp, M_ERR, "Error: timed out.");
161 goto prompterr;
162 case 1: /* Characters to read. */
163 break;
166 /* Read the characters. */
167 more: len = sizeof(buf) - (endp - buf);
168 switch (nr = read(sp->sh_out[0], endp, len)) {
169 case 0: /* EOF. */
170 msgq(sp, M_ERR, "Error: shell: EOF");
171 goto prompterr;
172 case -1: /* Error or interrupt. */
173 msgq(sp, M_SYSERR, "shell");
174 goto prompterr;
175 default:
176 endp += nr;
177 break;
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))
186 goto prompterr;
187 p = ++t;
189 if (p > buf) {
190 memmove(buf, p, endp - p);
191 endp = buf + (endp - p);
193 if (endp == buf)
194 goto more;
196 /* Wait up 1/10 of a second to make sure that we got it all. */
197 tv.tv_sec = 0;
198 tv.tv_usec = 100000;
199 switch (select(sp->sh_out[0] + 1, &fdset, NULL, NULL, &tv)) {
200 case -1: /* Error or interrupt. */
201 msgq(sp, M_SYSERR, "select");
202 goto prompterr;
203 case 0: /* Timeout */
204 break;
205 case 1: /* Characters to read. */
206 goto more;
209 /* Timed out, so theoretically we have a prompt. */
210 llen = endp - buf;
211 endp = buf;
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);
217 return (1);
220 if (cnt == 0) {
221 if ((sp->sh_prompt = malloc(llen)) == NULL) {
222 msgq(sp, M_SYSERR, NULL);
223 goto prompterr;
225 memmove(sp->sh_prompt, buf, llen);
226 sp->sh_prompt_len = llen;
227 } else {
228 if (sp->sh_prompt_len < llen)
229 llen = sp->sh_prompt_len;
230 else
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)
234 if (*p != *t)
235 *t = '\0';
237 #define TESTCNT 3
238 if (cnt == TESTCNT)
239 break;
241 /* Write a command to the shell. */
242 (void)write(sp->sh_in[1], "date\n", 5);
245 /* Turn on the script. */
246 F_SET(sp, S_SCRIPT);
248 return (0);
252 * sscr_exec --
253 * Take a line and hand it off to the shell.
256 sscr_exec(sp, ep, lno)
257 SCR *sp;
258 EXF *ep;
259 recno_t lno;
261 recno_t last_lno;
262 size_t blen, len, last_len, tlen;
263 int matchprompt, nw;
264 char *bp, *p;
266 /* If there's a prompt on the last line, append the command. */
267 if (file_lline(sp, ep, &last_lno))
268 return (1);
269 if ((p = file_gline(sp, ep, last_lno, &last_len)) == NULL) {
270 GETLINE_ERR(sp, last_lno);
271 return (1);
273 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
274 matchprompt = 1;
275 GET_SPACE(sp, bp, blen, 128 + last_len);
276 memmove(bp, p, last_len);
277 } else
278 matchprompt = 0;
280 /* Get something to execute. */
281 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
282 if (file_lline(sp, ep, &lno))
283 goto err1;
284 if (lno == 0)
285 goto empty;
286 else
287 GETLINE_ERR(sp, lno);
288 goto err1;
291 /* Empty lines aren't interesting. */
292 if (len == 0)
293 goto empty;
295 /* Delete any prompt. */
296 if (sscr_matchprompt(sp, p, len, &tlen)) {
297 if (tlen == len) {
298 empty: msgq(sp, M_BERR, "Nothing to execute.");
299 goto err1;
301 p += (len - tlen);
302 len = tlen;
305 /* Push the line to the shell. */
306 if ((nw = write(sp->sh_in[1], p, len)) != len)
307 goto err2;
308 if (write(sp->sh_in[1], "\n", 1) != 1) {
309 err2: if (nw == 0)
310 errno = EIO;
311 msgq(sp, M_SYSERR, "shell");
312 goto err1;
315 if (matchprompt) {
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);
321 return (1);
324 return (0);
328 * sscr_input --
329 * Take a line from the shell and insert it into the file.
332 sscr_input(sp)
333 SCR *sp;
335 struct timeval tv;
336 EXF *ep;
337 recno_t lno;
338 size_t blen, len, tlen;
339 int nr, rval;
340 char *bp, *endp, *p, *t;
342 /* Find out where the end of the file is. */
343 ep = sp->ep;
344 if (file_lline(sp, ep, &lno))
345 return (1);
347 #define MINREAD 1024
348 GET_SPACE(sp, bp, blen, MINREAD);
349 endp = bp;
351 /* Read the characters. */
352 rval = 1;
353 more: switch (nr = read(sp->sh_out[0], endp, MINREAD)) {
354 case 0: /* EOF; shell just exited. */
355 sscr_end(sp);
356 F_CLR(sp, S_SCRIPT);
357 rval = 0;
358 goto ret;
359 case -1: /* Error or interrupt. */
360 msgq(sp, M_SYSERR, "shell");
361 goto ret;
362 default:
363 endp += nr;
364 break;
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) {
370 len = p - t;
371 if (file_aline(sp, ep, 1, lno++, t, len))
372 goto ret;
373 t = p + 1;
375 if (p > t) {
376 len = p - t;
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) {
385 tv.tv_sec = 0;
386 tv.tv_usec = 100000;
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) {
391 memmove(bp, t, len);
392 endp = bp + len;
393 goto more;
396 if (file_aline(sp, ep, 1, lno++, t, len))
397 goto ret;
400 /* The cursor moves to EOF. */
401 sp->lno = lno;
402 sp->cno = len ? len - 1 : 0;
403 rval = sp->s_refresh(sp, ep);
405 ret: FREE_SPACE(sp, bp, blen);
406 return (rval);
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.
414 static int
415 sscr_matchprompt(sp, lp, line_len, lenp)
416 SCR *sp;
417 char *lp;
418 size_t line_len, *lenp;
420 char *pp;
421 size_t prompt_len;
423 if (line_len < (prompt_len = sp->sh_prompt_len))
424 return (0);
426 for (pp = sp->sh_prompt;
427 prompt_len && line_len; --prompt_len, --line_len) {
428 if (*pp == '\0') {
429 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
430 if (!prompt_len)
431 return (0);
432 for (; line_len && *lp != *pp; --line_len, ++lp);
433 if (!line_len)
434 return (0);
436 if (*pp++ != *lp++)
437 break;
440 if (prompt_len)
441 return (0);
442 if (lenp != NULL)
443 *lenp = line_len;
444 return (1);
448 * sscr_end --
449 * End the pipe to a shell.
452 sscr_end(sp)
453 SCR *sp;
455 int pstat;
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);
464 /* Free memory. */
465 FREE(sp->sh_prompt, sp->sh_prompt_len);
467 /* Turn off the script. */
468 F_CLR(sp, S_SCRIPT);
470 return (0);