This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / usr.bin / vi / ex / ex_script.c
CommitLineData
395c4480
AM
1/*-
2 * Copyright (c) 1992, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifndef lint
35static char sccsid[] = "@(#)ex_script.c 8.12 (Berkeley) 3/14/94";
36#endif /* not lint */
37
38#include <sys/types.h>
39#include <sys/ioctl.h>
40#include <queue.h>
41#include <sys/time.h>
42#include <sys/wait.h>
43
44#include <bitstring.h>
45#include <errno.h>
46#include <limits.h>
47#include <signal.h>
48#include <stdio.h>
49#include <stdlib.h>
50#include <string.h>
51#include <termios.h>
52#include <unistd.h>
53
54#include <db.h>
55#include <regex.h>
56
57#include "vi.h"
58#include "excmd.h"
59#include "script.h"
60
61/*
62 * XXX
63 */
64int openpty __P((int *, int *, char *, struct termios *, struct winsize *));
65
66static int sscr_getprompt __P((SCR *, EXF *));
67static int sscr_init __P((SCR *, EXF *));
68static int sscr_matchprompt __P((SCR *, char *, size_t, size_t *));
69static int sscr_setprompt __P((SCR *, char *, size_t));
70
71/*
72 * ex_script -- : sc[ript][!] [file]
73 *
74 * Switch to script mode.
75 */
76int
77ex_script(sp, ep, cmdp)
78 SCR *sp;
79 EXF *ep;
80 EXCMDARG *cmdp;
81{
82 /* Vi only command. */
83 if (!IN_VI_MODE(sp)) {
84 msgq(sp, M_ERR,
85 "The script command is only available in vi mode.");
86 return (1);
87 }
88
89 /* Switch to the new file. */
90 if (cmdp->argc != 0 && ex_edit(sp, ep, cmdp))
91 return (1);
92
93 /*
94 * Create the shell, figure out the prompt.
95 *
96 * !!!
97 * The files just switched, use sp->ep.
98 */
99 if (sscr_init(sp, sp->ep))
100 return (1);
101
102 return (0);
103}
104
105/*
106 * sscr_init --
107 * Create a pty setup for a shell.
108 */
109static int
110sscr_init(sp, ep)
111 SCR *sp;
112 EXF *ep;
113{
114 SCRIPT *sc;
115 char *sh, *sh_path;
116
117 MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
118 sp->script = sc;
119 sc->sh_prompt = NULL;
120 sc->sh_prompt_len = 0;
121
122 /*
123 * There are two different processes running through this code.
124 * They are the shell and the parent.
125 */
126 sc->sh_master = sc->sh_slave = -1;
127
128 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
129 msgq(sp, M_SYSERR, "tcgetattr");
130 goto err;
131 }
132
133 /*
134 * Turn off output postprocessing and echo.
135 */
136 sc->sh_term.c_oflag &= ~OPOST;
137 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
138
139 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
140 msgq(sp, M_SYSERR, "tcgetattr");
141 goto err;
142 }
143
144 if (openpty(&sc->sh_master,
145 &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
146 msgq(sp, M_SYSERR, "openpty");
147 goto err;
148 }
149
150 /*
151 * Don't use vfork() here, because the signal semantics
152 * differ from implementation to implementation.
153 */
154 switch (sc->sh_pid = fork()) {
155 case -1: /* Error. */
156 msgq(sp, M_SYSERR, "fork");
157err: if (sc->sh_master != -1)
158 (void)close(sc->sh_master);
159 if (sc->sh_slave != -1)
160 (void)close(sc->sh_slave);
161 return (1);
162 case 0: /* Utility. */
163 /*
164 * The utility has default signal behavior. Don't bother
165 * using sigaction(2) 'cause we want the default behavior.
166 */
167 (void)signal(SIGINT, SIG_DFL);
168 (void)signal(SIGQUIT, SIG_DFL);
169
170 /*
171 * XXX
172 * So that shells that do command line editing turn it off.
173 */
174 (void)putenv("TERM=emacs");
175 (void)putenv("TERMCAP=emacs:");
176 (void)putenv("EMACS=t");
177
178 (void)setsid();
179#ifdef TIOCSCTTY
180 /*
181 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
182 * ioctl, not by opening a terminal device file. POSIX 1003.1
183 * doesn't define a portable way to do this. If TIOCSCTTY is
184 * not available, hope that the open does it.
185 */
186 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
187#endif
188 (void)close(sc->sh_master);
189 (void)dup2(sc->sh_slave, STDIN_FILENO);
190 (void)dup2(sc->sh_slave, STDOUT_FILENO);
191 (void)dup2(sc->sh_slave, STDERR_FILENO);
192 (void)close(sc->sh_slave);
193
194 /* Assumes that all shells have -i. */
195 sh_path = O_STR(sp, O_SHELL);
196 if ((sh = strrchr(sh_path, '/')) == NULL)
197 sh = sh_path;
198 else
199 ++sh;
200 execl(sh_path, sh, "-i", NULL);
201 msgq(sp, M_ERR,
202 "Error: execl: %s: %s", sh_path, strerror(errno));
203 _exit(127);
204 default:
205 break;
206 }
207
208 if (sscr_getprompt(sp, ep))
209 return (1);
210
211 F_SET(sp, S_REDRAW | S_SCRIPT);
212 return (0);
213
214}
215
216/*
217 * sscr_getprompt --
218 * Eat lines printed by the shell until a line with no trailing
219 * carriage return comes; set the prompt from that line.
220 */
221static int
222sscr_getprompt(sp, ep)
223 SCR *sp;
224 EXF *ep;
225{
226 struct timeval tv;
227 SCRIPT *sc;
228 fd_set fdset;
229 recno_t lline;
230 size_t llen, len;
231 u_int value;
232 int nr;
233 char *endp, *p, *t, buf[1024];
234
235 FD_ZERO(&fdset);
236 endp = buf;
237 len = sizeof(buf);
238
239 /* Wait up to a second for characters to read. */
240 tv.tv_sec = 5;
241 tv.tv_usec = 0;
242 sc = sp->script;
243 FD_SET(sc->sh_master, &fdset);
244 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
245 case -1: /* Error or interrupt. */
246 msgq(sp, M_SYSERR, "select");
247 goto prompterr;
248 case 0: /* Timeout */
249 msgq(sp, M_ERR, "Error: timed out.");
250 goto prompterr;
251 case 1: /* Characters to read. */
252 break;
253 }
254
255 /* Read the characters. */
256more: len = sizeof(buf) - (endp - buf);
257 switch (nr = read(sc->sh_master, endp, len)) {
258 case 0: /* EOF. */
259 msgq(sp, M_ERR, "Error: shell: EOF");
260 goto prompterr;
261 case -1: /* Error or interrupt. */
262 msgq(sp, M_SYSERR, "shell");
263 goto prompterr;
264 default:
265 endp += nr;
266 break;
267 }
268
269 /* If any complete lines, push them into the file. */
270 for (p = t = buf; p < endp; ++p) {
271 value = term_key_val(sp, *p);
272 if (value == K_CR || value == K_NL) {
273 if (file_lline(sp, ep, &lline) ||
274 file_aline(sp, ep, 0, lline, t, p - t))
275 goto prompterr;
276 t = p + 1;
277 }
278 }
279 if (p > buf) {
280 memmove(buf, t, endp - t);
281 endp = buf + (endp - t);
282 }
283 if (endp == buf)
284 goto more;
285
286 /* Wait up 1/10 of a second to make sure that we got it all. */
287 tv.tv_sec = 0;
288 tv.tv_usec = 100000;
289 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
290 case -1: /* Error or interrupt. */
291 msgq(sp, M_SYSERR, "select");
292 goto prompterr;
293 case 0: /* Timeout */
294 break;
295 case 1: /* Characters to read. */
296 goto more;
297 }
298
299 /* Timed out, so theoretically we have a prompt. */
300 llen = endp - buf;
301 endp = buf;
302
303 /* Append the line into the file. */
304 if (file_lline(sp, ep, &lline) ||
305 file_aline(sp, ep, 0, lline, buf, llen)) {
306prompterr: sscr_end(sp);
307 return (1);
308 }
309
310 return (sscr_setprompt(sp, buf, llen));
311}
312
313/*
314 * sscr_exec --
315 * Take a line and hand it off to the shell.
316 */
317int
318sscr_exec(sp, ep, lno)
319 SCR *sp;
320 EXF *ep;
321 recno_t lno;
322{
323 SCRIPT *sc;
324 recno_t last_lno;
325 size_t blen, len, last_len, tlen;
326 int matchprompt, nw, rval;
327 char *bp, *p;
328
329 /* If there's a prompt on the last line, append the command. */
330 if (file_lline(sp, ep, &last_lno))
331 return (1);
332 if ((p = file_gline(sp, ep, last_lno, &last_len)) == NULL) {
333 GETLINE_ERR(sp, last_lno);
334 return (1);
335 }
336 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
337 matchprompt = 1;
338 GET_SPACE_RET(sp, bp, blen, last_len + 128);
339 memmove(bp, p, last_len);
340 } else
341 matchprompt = 0;
342
343 /* Get something to execute. */
344 if ((p = file_gline(sp, ep, lno, &len)) == NULL) {
345 if (file_lline(sp, ep, &lno))
346 goto err1;
347 if (lno == 0)
348 goto empty;
349 else
350 GETLINE_ERR(sp, lno);
351 goto err1;
352 }
353
354 /* Empty lines aren't interesting. */
355 if (len == 0)
356 goto empty;
357
358 /* Delete any prompt. */
359 if (sscr_matchprompt(sp, p, len, &tlen)) {
360 if (tlen == len) {
361empty: msgq(sp, M_BERR, "Nothing to execute.");
362 goto err1;
363 }
364 p += (len - tlen);
365 len = tlen;
366 }
367
368 /* Push the line to the shell. */
369 sc = sp->script;
370 if ((nw = write(sc->sh_master, p, len)) != len)
371 goto err2;
372 rval = 0;
373 if (write(sc->sh_master, "\n", 1) != 1) {
374err2: if (nw == 0)
375 errno = EIO;
376 msgq(sp, M_SYSERR, "shell");
377 goto err1;
378 }
379
380 if (matchprompt) {
381 ADD_SPACE_RET(sp, bp, blen, last_len + len);
382 memmove(bp + last_len, p, len);
383 if (file_sline(sp, ep, last_lno, bp, last_len + len))
384err1: rval = 1;
385 }
386 if (matchprompt)
387 FREE_SPACE(sp, bp, blen);
388 return (rval);
389}
390
391/*
392 * sscr_input --
393 * Take a line from the shell and insert it into the file.
394 */
395int
396sscr_input(sp)
397 SCR *sp;
398{
399 struct timeval tv;
400 SCRIPT *sc;
401 EXF *ep;
402 recno_t lno;
403 size_t blen, len, tlen;
404 u_int value;
405 int nr, rval;
406 char *bp, *endp, *p, *t;
407
408 /* Find out where the end of the file is. */
409 ep = sp->ep;
410 if (file_lline(sp, ep, &lno))
411 return (1);
412
413#define MINREAD 1024
414 GET_SPACE_RET(sp, bp, blen, MINREAD);
415 endp = bp;
416
417 /* Read the characters. */
418 rval = 1;
419 sc = sp->script;
420more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
421 case 0: /* EOF; shell just exited. */
422 sscr_end(sp);
423 F_CLR(sp, S_SCRIPT);
424 rval = 0;
425 goto ret;
426 case -1: /* Error or interrupt. */
427 msgq(sp, M_SYSERR, "shell");
428 goto ret;
429 default:
430 endp += nr;
431 break;
432 }
433
434 /* Append the lines into the file. */
435 for (p = t = bp; p < endp; ++p) {
436 value = term_key_val(sp, *p);
437 if (value == K_CR || value == K_NL) {
438 len = p - t;
439 if (file_aline(sp, ep, 1, lno++, t, len))
440 goto ret;
441 t = p + 1;
442 }
443 }
444 if (p > t) {
445 len = p - t;
446 /*
447 * If the last thing from the shell isn't another prompt, wait
448 * up to 1/10 of a second for more stuff to show up, so that
449 * we don't break the output into two separate lines. Don't
450 * want to hang indefinitely because some program is hanging,
451 * confused the shell, or whatever.
452 */
453 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
454 tv.tv_sec = 0;
455 tv.tv_usec = 100000;
456 FD_SET(sc->sh_master, &sp->rdfd);
457 FD_CLR(STDIN_FILENO, &sp->rdfd);
458 if (select(sc->sh_master + 1,
459 &sp->rdfd, NULL, NULL, &tv) == 1) {
460 memmove(bp, t, len);
461 endp = bp + len;
462 goto more;
463 }
464 }
465 if (sscr_setprompt(sp, t, len))
466 return (1);
467 if (file_aline(sp, ep, 1, lno++, t, len))
468 goto ret;
469 }
470
471 /* The cursor moves to EOF. */
472 sp->lno = lno;
473 sp->cno = len ? len - 1 : 0;
474 rval = sp->s_refresh(sp, ep);
475
476ret: FREE_SPACE(sp, bp, blen);
477 return (rval);
478}
479
480/*
481 * sscr_setprompt --
482 *
483 * Set the prompt to the last line we got from the shell.
484 *
485 */
486static int
487sscr_setprompt(sp, buf, len)
488 SCR *sp;
489 char* buf;
490 size_t len;
491{
492 SCRIPT *sc;
493
494 sc = sp->script;
495 if (sc->sh_prompt)
496 FREE(sc->sh_prompt, sc->sh_prompt_len);
497 MALLOC(sp, sc->sh_prompt, char *, len + 1);
498 if (sc->sh_prompt == NULL) {
499 sscr_end(sp);
500 return (1);
501 }
502 memmove(sc->sh_prompt, buf, len);
503 sc->sh_prompt_len = len;
504 sc->sh_prompt[len] = '\0';
505 return (0);
506}
507
508/*
509 * sscr_matchprompt --
510 * Check to see if a line matches the prompt. Nul's indicate
511 * parts that can change, in both content and size.
512 */
513static int
514sscr_matchprompt(sp, lp, line_len, lenp)
515 SCR *sp;
516 char *lp;
517 size_t line_len, *lenp;
518{
519 SCRIPT *sc;
520 size_t prompt_len;
521 char *pp;
522
523 sc = sp->script;
524 if (line_len < (prompt_len = sc->sh_prompt_len))
525 return (0);
526
527 for (pp = sc->sh_prompt;
528 prompt_len && line_len; --prompt_len, --line_len) {
529 if (*pp == '\0') {
530 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
531 if (!prompt_len)
532 return (0);
533 for (; line_len && *lp != *pp; --line_len, ++lp);
534 if (!line_len)
535 return (0);
536 }
537 if (*pp++ != *lp++)
538 break;
539 }
540
541 if (prompt_len)
542 return (0);
543 if (lenp != NULL)
544 *lenp = line_len;
545 return (1);
546}
547
548/*
549 * sscr_end --
550 * End the pipe to a shell.
551 */
552int
553sscr_end(sp)
554 SCR *sp;
555{
556 SCRIPT *sc;
557 int rval;
558
559 if ((sc = sp->script) == NULL)
560 return (0);
561
562 /* Turn off the script flag. */
563 F_CLR(sp, S_SCRIPT);
564
565 /* Close down the parent's file descriptors. */
566 if (sc->sh_master != -1)
567 (void)close(sc->sh_master);
568 if (sc->sh_slave != -1)
569 (void)close(sc->sh_slave);
570
571 /* This should have killed the child. */
572 rval = proc_wait(sp, (long)sc->sh_pid, "script-shell", 0);
573
574 /* Free memory. */
575 FREE(sc->sh_prompt, sc->sh_prompt_len);
576 FREE(sc, sizeof(SCRIPT));
577 sp->script = NULL;
578
579 return (rval);
580}