Commit | Line | Data |
---|---|---|
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 | |
35 | static 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 | */ | |
64 | int openpty __P((int *, int *, char *, struct termios *, struct winsize *)); | |
65 | ||
66 | static int sscr_getprompt __P((SCR *, EXF *)); | |
67 | static int sscr_init __P((SCR *, EXF *)); | |
68 | static int sscr_matchprompt __P((SCR *, char *, size_t, size_t *)); | |
69 | static int sscr_setprompt __P((SCR *, char *, size_t)); | |
70 | ||
71 | /* | |
72 | * ex_script -- : sc[ript][!] [file] | |
73 | * | |
74 | * Switch to script mode. | |
75 | */ | |
76 | int | |
77 | ex_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 | */ | |
109 | static int | |
110 | sscr_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"); | |
157 | err: 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 | */ | |
221 | static int | |
222 | sscr_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. */ | |
256 | more: 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)) { | |
306 | prompterr: 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 | */ | |
317 | int | |
318 | sscr_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) { | |
361 | empty: 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) { | |
374 | err2: 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)) | |
384 | err1: 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 | */ | |
395 | int | |
396 | sscr_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; | |
420 | more: 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 | ||
476 | ret: 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 | */ | |
486 | static int | |
487 | sscr_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 | */ | |
513 | static int | |
514 | sscr_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 | */ | |
552 | int | |
553 | sscr_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 | } |