always set the return screen to something, and do it immediately so
[nvi.git] / ex / ex_script.c
bloba10ebbd86775c32fb98f0eec370ffa264bbcc1bf
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.10 1993/12/22 15:44:39 bostic Exp $ (Berkeley) $Date: 1993/12/22 15:44:39 $";
10 #endif /* not lint */
12 #include <sys/types.h>
13 #include <sys/ioctl.h>
14 #include <sys/wait.h>
16 #include <errno.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
21 #include "vi.h"
22 #include "excmd.h"
23 #include "script.h"
26 * XXX
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.
40 int
41 ex_script(sp, ep, cmdp)
42 SCR *sp;
43 EXF *ep;
44 EXCMDARG *cmdp;
46 /* Vi only command. */
47 if (!IN_VI_MODE(sp)) {
48 msgq(sp, M_ERR,
49 "The script command is only available in vi mode.");
50 return (1);
53 /* Switch to the new file. */
54 if (cmdp->argc != 0 && ex_edit(sp, ep, cmdp))
55 return (1);
58 * Create the shell, figure out the prompt.
60 * !!!
61 * The files just switched, use sp->ep.
63 if (sscr_init(sp, sp->ep))
64 return (1);
66 return (0);
70 * sscr_init --
71 * Create a pty setup for a shell.
73 static int
74 sscr_init(sp, ep)
75 SCR *sp;
76 EXF *ep;
78 SCRIPT *sc;
79 char *sh, *sh_path;
81 MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
82 sp->script = sc;
83 sc->sh_prompt = NULL;
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");
94 goto err;
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");
105 goto err;
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");
111 goto err;
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);
125 return (1);
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);
135 * XXX
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");
142 (void)setsid();
143 #ifdef TIOCSCTTY
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);
151 #endif
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)
161 sh = sh_path;
162 else
163 ++sh;
164 execl(sh_path, sh, "-i", NULL);
165 msgq(sp, M_ERR,
166 "Error: execl: %s: %s", sh_path, strerror(errno));
167 _exit(127);
168 default:
169 break;
172 if (sscr_getprompt(sp, ep))
173 return (1);
175 F_SET(sp, S_REDRAW | S_SCRIPT);
176 return (0);
181 * sscr_getprompt --
182 * Eat lines printed by the shell until a line with no trailing
183 * carriage return comes; set the prompt from that line.
185 static int
186 sscr_getprompt(sp, ep)
187 SCR *sp;
188 EXF *ep;
190 struct timeval tv;
191 SCRIPT *sc;
192 fd_set fdset;
193 recno_t lline;
194 size_t llen, len;
195 u_int value;
196 int nr;
197 char *endp, *p, *t, buf[1024];
199 FD_ZERO(&fdset);
200 endp = buf;
201 len = sizeof(buf);
203 /* Wait up to a second for characters to read. */
204 tv.tv_sec = 5;
205 tv.tv_usec = 0;
206 sc = sp->script;
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");
211 goto prompterr;
212 case 0: /* Timeout */
213 msgq(sp, M_ERR, "Error: timed out.");
214 goto prompterr;
215 case 1: /* Characters to read. */
216 break;
219 /* Read the characters. */
220 more: len = sizeof(buf) - (endp - buf);
221 switch (nr = read(sc->sh_master, endp, len)) {
222 case 0: /* EOF. */
223 msgq(sp, M_ERR, "Error: shell: EOF");
224 goto prompterr;
225 case -1: /* Error or interrupt. */
226 msgq(sp, M_SYSERR, "shell");
227 goto prompterr;
228 default:
229 endp += nr;
230 break;
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))
239 goto prompterr;
240 t = p + 1;
243 if (p > buf) {
244 memmove(buf, t, endp - t);
245 endp = buf + (endp - t);
247 if (endp == buf)
248 goto more;
250 /* Wait up 1/10 of a second to make sure that we got it all. */
251 tv.tv_sec = 0;
252 tv.tv_usec = 100000;
253 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
254 case -1: /* Error or interrupt. */
255 msgq(sp, M_SYSERR, "select");
256 goto prompterr;
257 case 0: /* Timeout */
258 break;
259 case 1: /* Characters to read. */
260 goto more;
263 /* Timed out, so theoretically we have a prompt. */
264 llen = endp - buf;
265 endp = buf;
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);
271 return (1);
274 return (sscr_setprompt(sp, buf, llen));
278 * sscr_exec --
279 * Take a line and hand it off to the shell.
282 sscr_exec(sp, ep, lno)
283 SCR *sp;
284 EXF *ep;
285 recno_t lno;
287 SCRIPT *sc;
288 recno_t last_lno;
289 size_t blen, len, last_len, tlen;
290 int matchprompt, nw, rval;
291 char *bp, *p;
293 /* If there's a prompt on the last line, append the command. */
294 if (file_lline(sp, ep, &last_lno))
295 return (1);
296 if ((p = file_gline(sp, ep, last_lno, &last_len)) == NULL) {
297 GETLINE_ERR(sp, last_lno);
298 return (1);
300 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
301 matchprompt = 1;
302 GET_SPACE_RET(sp, bp, blen, last_len + 128);
303 memmove(bp, p, last_len);
304 } else
305 matchprompt = 0;
307 /* Get something to execute. */
308 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
309 if (file_lline(sp, ep, &lno))
310 goto err1;
311 if (lno == 0)
312 goto empty;
313 else
314 GETLINE_ERR(sp, lno);
315 goto err1;
318 /* Empty lines aren't interesting. */
319 if (len == 0)
320 goto empty;
322 /* Delete any prompt. */
323 if (sscr_matchprompt(sp, p, len, &tlen)) {
324 if (tlen == len) {
325 empty: msgq(sp, M_BERR, "Nothing to execute.");
326 goto err1;
328 p += (len - tlen);
329 len = tlen;
332 /* Push the line to the shell. */
333 sc = sp->script;
334 if ((nw = write(sc->sh_master, p, len)) != len)
335 goto err2;
336 rval = 0;
337 if (write(sc->sh_master, "\n", 1) != 1) {
338 err2: if (nw == 0)
339 errno = EIO;
340 msgq(sp, M_SYSERR, "shell");
341 goto err1;
344 if (matchprompt) {
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))
348 err1: rval = 1;
350 if (matchprompt)
351 FREE_SPACE(sp, bp, blen);
352 return (rval);
356 * sscr_input --
357 * Take a line from the shell and insert it into the file.
360 sscr_input(sp)
361 SCR *sp;
363 struct timeval tv;
364 SCRIPT *sc;
365 EXF *ep;
366 recno_t lno;
367 size_t blen, len, tlen;
368 u_int value;
369 int nr, rval;
370 char *bp, *endp, *p, *t;
372 /* Find out where the end of the file is. */
373 ep = sp->ep;
374 if (file_lline(sp, ep, &lno))
375 return (1);
377 #define MINREAD 1024
378 GET_SPACE_RET(sp, bp, blen, MINREAD);
379 endp = bp;
381 /* Read the characters. */
382 rval = 1;
383 sc = sp->script;
384 more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
385 case 0: /* EOF; shell just exited. */
386 sscr_end(sp);
387 F_CLR(sp, S_SCRIPT);
388 rval = 0;
389 goto ret;
390 case -1: /* Error or interrupt. */
391 msgq(sp, M_SYSERR, "shell");
392 goto ret;
393 default:
394 endp += nr;
395 break;
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) {
402 len = p - t;
403 if (file_aline(sp, ep, 1, lno++, t, len))
404 goto ret;
405 t = p + 1;
408 if (p > t) {
409 len = p - t;
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) {
418 tv.tv_sec = 0;
419 tv.tv_usec = 100000;
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) {
424 memmove(bp, t, len);
425 endp = bp + len;
426 goto more;
429 if (sscr_setprompt(sp, t, len))
430 return (1);
431 if (file_aline(sp, ep, 1, lno++, t, len))
432 goto ret;
435 /* The cursor moves to EOF. */
436 sp->lno = lno;
437 sp->cno = len ? len - 1 : 0;
438 rval = sp->s_refresh(sp, ep);
440 ret: FREE_SPACE(sp, bp, blen);
441 return (rval);
445 * sscr_setprompt --
447 * Set the prompt to the last line we got from the shell.
450 static int
451 sscr_setprompt(sp, buf, len)
452 SCR *sp;
453 char* buf;
454 size_t len;
456 SCRIPT *sc;
458 sc = sp->script;
459 if (sc->sh_prompt)
460 FREE(sc->sh_prompt, sc->sh_prompt_len);
461 MALLOC(sp, sc->sh_prompt, char *, len + 1);
462 if (sc->sh_prompt == NULL) {
463 sscr_end(sp);
464 return (1);
466 memmove(sc->sh_prompt, buf, len);
467 sc->sh_prompt_len = len;
468 sc->sh_prompt[len] = '\0';
469 return (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.
477 static int
478 sscr_matchprompt(sp, lp, line_len, lenp)
479 SCR *sp;
480 char *lp;
481 size_t line_len, *lenp;
483 SCRIPT *sc;
484 size_t prompt_len;
485 char *pp;
487 sc = sp->script;
488 if (line_len < (prompt_len = sc->sh_prompt_len))
489 return (0);
491 for (pp = sc->sh_prompt;
492 prompt_len && line_len; --prompt_len, --line_len) {
493 if (*pp == '\0') {
494 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
495 if (!prompt_len)
496 return (0);
497 for (; line_len && *lp != *pp; --line_len, ++lp);
498 if (!line_len)
499 return (0);
501 if (*pp++ != *lp++)
502 break;
505 if (prompt_len)
506 return (0);
507 if (lenp != NULL)
508 *lenp = line_len;
509 return (1);
513 * sscr_end --
514 * End the pipe to a shell.
517 sscr_end(sp)
518 SCR *sp;
520 SCRIPT *sc;
521 int rval;
523 if ((sc = sp->script) == NULL)
524 return (0);
526 /* Turn off the script flag. */
527 F_CLR(sp, S_SCRIPT);
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);
538 /* Free memory. */
539 FREE(sc->sh_prompt, sc->sh_prompt_len);
540 FREE(sc, sizeof(SCRIPT));
541 sp->script = NULL;
543 return (rval);