when end a "temporary file" edit session, turn on the FR_IGNORE bit
[nvi.git] / ex / ex_script.c
blob72ca7385abea2e4cc6125423362fb29f025ab38f
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.8 1993/12/19 19:36:59 bostic Exp $ (Berkeley) $Date: 1993/12/19 19:36:59 $";
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 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
144 (void)close(sc->sh_master);
145 (void)dup2(sc->sh_slave, STDIN_FILENO);
146 (void)dup2(sc->sh_slave, STDOUT_FILENO);
147 (void)dup2(sc->sh_slave, STDERR_FILENO);
148 (void)close(sc->sh_slave);
150 /* Assumes that all shells have -i. */
151 sh_path = O_STR(sp, O_SHELL);
152 if ((sh = strrchr(sh_path, '/')) == NULL)
153 sh = sh_path;
154 else
155 ++sh;
156 execl(sh_path, sh, "-i", NULL);
157 msgq(sp, M_ERR,
158 "Error: execl: %s: %s", sh_path, strerror(errno));
159 _exit(127);
160 default:
161 break;
164 if (sscr_getprompt(sp, ep))
165 return (1);
167 F_SET(sp, S_REDRAW | S_SCRIPT);
168 return (0);
173 * sscr_getprompt --
174 * Eat lines printed by the shell until a line with no trailing
175 * carriage return comes; set the prompt from that line.
177 static int
178 sscr_getprompt(sp, ep)
179 SCR *sp;
180 EXF *ep;
182 struct timeval tv;
183 SCRIPT *sc;
184 fd_set fdset;
185 recno_t lline;
186 size_t llen, len;
187 u_int value;
188 int nr;
189 char *endp, *p, *t, buf[1024];
191 FD_ZERO(&fdset);
192 endp = buf;
193 len = sizeof(buf);
195 /* Wait up to a second for characters to read. */
196 tv.tv_sec = 5;
197 tv.tv_usec = 0;
198 sc = sp->script;
199 FD_SET(sc->sh_master, &fdset);
200 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
201 case -1: /* Error or interrupt. */
202 msgq(sp, M_SYSERR, "select");
203 goto prompterr;
204 case 0: /* Timeout */
205 msgq(sp, M_ERR, "Error: timed out.");
206 goto prompterr;
207 case 1: /* Characters to read. */
208 break;
211 /* Read the characters. */
212 more: len = sizeof(buf) - (endp - buf);
213 switch (nr = read(sc->sh_master, endp, len)) {
214 case 0: /* EOF. */
215 msgq(sp, M_ERR, "Error: shell: EOF");
216 goto prompterr;
217 case -1: /* Error or interrupt. */
218 msgq(sp, M_SYSERR, "shell");
219 goto prompterr;
220 default:
221 endp += nr;
222 break;
225 /* If any complete lines, push them into the file. */
226 for (p = t = buf; t < endp; ++t) {
227 value = term_key_val(sp, *t);
228 if (value == K_CR || value == K_NL) {
229 if (file_lline(sp, ep, &lline) ||
230 file_aline(sp, ep, 0, lline, buf, t - p))
231 goto prompterr;
232 p = ++t;
235 if (p > buf) {
236 memmove(buf, p, endp - p);
237 endp = buf + (endp - p);
239 if (endp == buf)
240 goto more;
242 /* Wait up 1/10 of a second to make sure that we got it all. */
243 tv.tv_sec = 0;
244 tv.tv_usec = 100000;
245 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
246 case -1: /* Error or interrupt. */
247 msgq(sp, M_SYSERR, "select");
248 goto prompterr;
249 case 0: /* Timeout */
250 break;
251 case 1: /* Characters to read. */
252 goto more;
255 /* Timed out, so theoretically we have a prompt. */
256 llen = endp - buf;
257 endp = buf;
259 /* Append the line into the file. */
260 if (file_lline(sp, ep, &lline) ||
261 file_aline(sp, ep, 0, lline, buf, llen)) {
262 prompterr: sscr_end(sp);
263 return (1);
266 return (sscr_setprompt(sp, buf, llen));
270 * sscr_exec --
271 * Take a line and hand it off to the shell.
274 sscr_exec(sp, ep, lno)
275 SCR *sp;
276 EXF *ep;
277 recno_t lno;
279 SCRIPT *sc;
280 recno_t last_lno;
281 size_t blen, len, last_len, tlen;
282 int matchprompt, nw, rval;
283 char *bp, *p;
285 /* If there's a prompt on the last line, append the command. */
286 if (file_lline(sp, ep, &last_lno))
287 return (1);
288 if ((p = file_gline(sp, ep, last_lno, &last_len)) == NULL) {
289 GETLINE_ERR(sp, last_lno);
290 return (1);
292 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
293 matchprompt = 1;
294 GET_SPACE_RET(sp, bp, blen, last_len + 128);
295 memmove(bp, p, last_len);
296 } else
297 matchprompt = 0;
299 /* Get something to execute. */
300 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
301 if (file_lline(sp, ep, &lno))
302 goto err1;
303 if (lno == 0)
304 goto empty;
305 else
306 GETLINE_ERR(sp, lno);
307 goto err1;
310 /* Empty lines aren't interesting. */
311 if (len == 0)
312 goto empty;
314 /* Delete any prompt. */
315 if (sscr_matchprompt(sp, p, len, &tlen)) {
316 if (tlen == len) {
317 empty: msgq(sp, M_BERR, "Nothing to execute.");
318 goto err1;
320 p += (len - tlen);
321 len = tlen;
324 /* Push the line to the shell. */
325 sc = sp->script;
326 if ((nw = write(sc->sh_master, p, len)) != len)
327 goto err2;
328 rval = 0;
329 if (write(sc->sh_master, "\n", 1) != 1) {
330 err2: if (nw == 0)
331 errno = EIO;
332 msgq(sp, M_SYSERR, "shell");
333 goto err1;
336 if (matchprompt) {
337 ADD_SPACE_RET(sp, bp, blen, last_len + len);
338 memmove(bp + last_len, p, len);
339 if (file_sline(sp, ep, last_lno, bp, last_len + len))
340 err1: rval = 1;
342 if (matchprompt)
343 FREE_SPACE(sp, bp, blen);
344 return (rval);
348 * sscr_input --
349 * Take a line from the shell and insert it into the file.
352 sscr_input(sp)
353 SCR *sp;
355 struct timeval tv;
356 SCRIPT *sc;
357 EXF *ep;
358 recno_t lno;
359 size_t blen, len, tlen;
360 u_int value;
361 int nr, rval;
362 char *bp, *endp, *p, *t;
364 /* Find out where the end of the file is. */
365 ep = sp->ep;
366 if (file_lline(sp, ep, &lno))
367 return (1);
369 #define MINREAD 1024
370 GET_SPACE_RET(sp, bp, blen, MINREAD);
371 endp = bp;
373 /* Read the characters. */
374 rval = 1;
375 sc = sp->script;
376 more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
377 case 0: /* EOF; shell just exited. */
378 sscr_end(sp);
379 F_CLR(sp, S_SCRIPT);
380 rval = 0;
381 goto ret;
382 case -1: /* Error or interrupt. */
383 msgq(sp, M_SYSERR, "shell");
384 goto ret;
385 default:
386 endp += nr;
387 break;
390 /* Append the lines into the file. */
391 for (p = t = bp; p < endp; ++p) {
392 value = term_key_val(sp, *p);
393 if (value == K_CR || value == K_NL) {
394 len = p - t;
395 if (file_aline(sp, ep, 1, lno++, t, len))
396 goto ret;
397 t = p + 1;
400 if (p > t) {
401 len = p - t;
403 * If the last thing from the shell isn't another prompt, wait
404 * up to 1/10 of a second for more stuff to show up, so that
405 * we don't break the output into two separate lines. Don't
406 * want to hang indefinitely because some program is hanging,
407 * confused the shell, or whatever.
409 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
410 tv.tv_sec = 0;
411 tv.tv_usec = 100000;
412 FD_SET(sc->sh_master, &sp->rdfd);
413 FD_CLR(STDIN_FILENO, &sp->rdfd);
414 if (select(sc->sh_master + 1,
415 &sp->rdfd, NULL, NULL, &tv) == 1) {
416 memmove(bp, t, len);
417 endp = bp + len;
418 goto more;
421 if (sscr_setprompt(sp, t, len))
422 return (1);
423 if (file_aline(sp, ep, 1, lno++, t, len))
424 goto ret;
427 /* The cursor moves to EOF. */
428 sp->lno = lno;
429 sp->cno = len ? len - 1 : 0;
430 rval = sp->s_refresh(sp, ep);
432 ret: FREE_SPACE(sp, bp, blen);
433 return (rval);
437 * sscr_setprompt --
439 * Set the prompt to the last line we got from the shell.
442 static int
443 sscr_setprompt(sp, buf, len)
444 SCR *sp;
445 char* buf;
446 size_t len;
448 SCRIPT *sc;
450 sc = sp->script;
451 if (sc->sh_prompt)
452 FREE(sc->sh_prompt, sc->sh_prompt_len);
453 MALLOC(sp, sc->sh_prompt, char *, len + 1);
454 if (sc->sh_prompt == NULL) {
455 sscr_end(sp);
456 return (1);
458 memmove(sc->sh_prompt, buf, len);
459 sc->sh_prompt_len = len;
460 sc->sh_prompt[len] = '\0';
461 return (0);
465 * sscr_matchprompt --
466 * Check to see if a line matches the prompt. Nul's indicate
467 * parts that can change, in both content and size.
469 static int
470 sscr_matchprompt(sp, lp, line_len, lenp)
471 SCR *sp;
472 char *lp;
473 size_t line_len, *lenp;
475 SCRIPT *sc;
476 size_t prompt_len;
477 char *pp;
479 sc = sp->script;
480 if (line_len < (prompt_len = sc->sh_prompt_len))
481 return (0);
483 for (pp = sc->sh_prompt;
484 prompt_len && line_len; --prompt_len, --line_len) {
485 if (*pp == '\0') {
486 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
487 if (!prompt_len)
488 return (0);
489 for (; line_len && *lp != *pp; --line_len, ++lp);
490 if (!line_len)
491 return (0);
493 if (*pp++ != *lp++)
494 break;
497 if (prompt_len)
498 return (0);
499 if (lenp != NULL)
500 *lenp = line_len;
501 return (1);
505 * sscr_end --
506 * End the pipe to a shell.
509 sscr_end(sp)
510 SCR *sp;
512 SCRIPT *sc;
513 int rval;
515 if ((sc = sp->script) == NULL)
516 return (0);
518 /* Turn off the script flag. */
519 F_CLR(sp, S_SCRIPT);
521 /* Close down the parent's file descriptors. */
522 if (sc->sh_master != -1)
523 (void)close(sc->sh_master);
524 if (sc->sh_slave != -1)
525 (void)close(sc->sh_slave);
527 /* This should have killed the child. */
528 rval = proc_wait(sp, (long)sc->sh_pid, "script-shell", 0);
530 /* Free memory. */
531 FREE(sc->sh_prompt, sc->sh_prompt_len);
532 FREE(sc, sizeof(SCRIPT));
533 sp->script = NULL;
535 return (rval);