1 /*-
2 * Copyright (c) 1992, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley by
8 * Brian Hirt.
9 *
10 * See the LICENSE file for redistribution information.
11 */
12
13 #include "config.h"
14
15 #include <sys/types.h>
16 #include <sys/ioctl.h>
17 #include <sys/queue.h>
18 #include <sys/select.h>
19 #include <sys/stat.h>
20 #include <sys/wait.h>
21
22 #include <bitstring.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <grp.h>
26 #include <limits.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <termios.h>
32 #include <unistd.h>
33 #ifdef HAVE_LIBUTIL_H
34 #include <libutil.h>
35 #elif defined HAVE_PTY_H
36 #include <pty.h>
37 #else
38 #include <util.h>
39 #endif
40
41 #include "../common/common.h"
42 #include "../vi/vi.h"
43 #include "script.h"
44 #include "pathnames.h"
45
46 static void sscr_check(SCR *);
47 static int sscr_getprompt(SCR *);
48 static int sscr_init(SCR *);
49 static int sscr_insert(SCR *);
50 static int sscr_matchprompt(SCR *, char *, size_t, size_t *);
51 static int sscr_setprompt(SCR *, char *, size_t);
52
53 /*
54 * ex_script -- : sc[ript][!] [file]
55 * Switch to script mode.
56 *
57 * PUBLIC: int ex_script(SCR *, EXCMD *);
58 */
59 int
ex_script(SCR * sp,EXCMD * cmdp)60 ex_script(SCR *sp, EXCMD *cmdp)
61 {
62 /* Vi only command. */
63 if (!F_ISSET(sp, SC_VI)) {
64 msgq(sp, M_ERR,
65 "150|The script command is only available in vi mode");
66 return (1);
67 }
68
69 /* Switch to the new file. */
70 if (cmdp->argc != 0 && ex_edit(sp, cmdp))
71 return (1);
72
73 /* Create the shell, figure out the prompt. */
74 if (sscr_init(sp))
75 return (1);
76
77 return (0);
78 }
79
80 /*
81 * sscr_init --
82 * Create a pty setup for a shell.
83 */
84 static int
sscr_init(SCR * sp)85 sscr_init(SCR *sp)
86 {
87 SCRIPT *sc;
88 char *sh, *sh_path;
89
90 /* We're going to need a shell. */
91 if (opts_empty(sp, O_SHELL, 0))
92 return (1);
93
94 MALLOC_RET(sp, sc, sizeof(SCRIPT));
95 sp->script = sc;
96 sc->sh_prompt = NULL;
97 sc->sh_prompt_len = 0;
98
99 /*
100 * There are two different processes running through this code.
101 * They are the shell and the parent.
102 */
103 sc->sh_master = sc->sh_slave = -1;
104
105 if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
106 msgq(sp, M_SYSERR, "tcgetattr");
107 goto err;
108 }
109
110 /*
111 * Turn off output postprocessing and echo.
112 */
113 sc->sh_term.c_oflag &= ~OPOST;
114 sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
115
116 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
117 msgq(sp, M_SYSERR, "tcgetattr");
118 goto err;
119 }
120
121 if (openpty(&sc->sh_master,
122 &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
123 msgq(sp, M_SYSERR, "openpty");
124 goto err;
125 }
126
127 /*
128 * __TK__ huh?
129 * Don't use vfork() here, because the signal semantics differ from
130 * implementation to implementation.
131 */
132 switch (sc->sh_pid = fork()) {
133 case -1: /* Error. */
134 msgq(sp, M_SYSERR, "fork");
135 err: if (sc->sh_master != -1)
136 (void)close(sc->sh_master);
137 if (sc->sh_slave != -1)
138 (void)close(sc->sh_slave);
139 return (1);
140 case 0: /* Utility. */
141 /*
142 * XXX
143 * So that shells that do command line editing turn it off.
144 */
145 (void)setenv("TERM", "emacs", 1);
146 (void)setenv("TERMCAP", "emacs:", 1);
147 (void)setenv("EMACS", "t", 1);
148
149 (void)setsid();
150 #ifdef TIOCSCTTY
151 /*
152 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
153 * ioctl, not by opening a terminal device file. POSIX 1003.1
154 * doesn't define a portable way to do this. If TIOCSCTTY is
155 * not available, hope that the open does it.
156 */
157 (void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
158 #endif
159 (void)close(sc->sh_master);
160 (void)dup2(sc->sh_slave, STDIN_FILENO);
161 (void)dup2(sc->sh_slave, STDOUT_FILENO);
162 (void)dup2(sc->sh_slave, STDERR_FILENO);
163 (void)close(sc->sh_slave);
164
165 /* Assumes that all shells have -i. */
166 sh_path = O_STR(sp, O_SHELL);
167 if ((sh = strrchr(sh_path, '/')) == NULL)
168 sh = sh_path;
169 else
170 ++sh;
171 execl(sh_path, sh, "-i", NULL);
172 msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
173 _exit(127);
174 default: /* Parent. */
175 break;
176 }
177
178 if (sscr_getprompt(sp))
179 return (1);
180
181 F_SET(sp, SC_SCRIPT);
182 F_SET(sp->gp, G_SCRWIN);
183 return (0);
184 }
185
186 /*
187 * sscr_getprompt --
188 * Eat lines printed by the shell until a line with no trailing
189 * carriage return comes; set the prompt from that line.
190 */
191 static int
sscr_getprompt(SCR * sp)192 sscr_getprompt(SCR *sp)
193 {
194 EX_PRIVATE *exp;
195 struct timeval tv;
196 char *endp, *p, *t, buf[1024];
197 SCRIPT *sc;
198 fd_set fdset;
199 recno_t lline;
200 size_t llen, len;
201 int nr;
202 CHAR_T *wp;
203 size_t wlen;
204
205 exp = EXP(sp);
206
207 FD_ZERO(&fdset);
208 endp = buf;
209 len = sizeof(buf);
210
211 /* Wait up to a second for characters to read. */
212 tv.tv_sec = 5;
213 tv.tv_usec = 0;
214 sc = sp->script;
215 FD_SET(sc->sh_master, &fdset);
216 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
217 case -1: /* Error or interrupt. */
218 msgq(sp, M_SYSERR, "select");
219 goto prompterr;
220 case 0: /* Timeout */
221 msgq(sp, M_ERR, "Error: timed out");
222 goto prompterr;
223 case 1: /* Characters to read. */
224 break;
225 }
226
227 /* Read the characters. */
228 more: len = sizeof(buf) - (endp - buf);
229 switch (nr = read(sc->sh_master, endp, len)) {
230 case 0: /* EOF. */
231 msgq(sp, M_ERR, "Error: shell: EOF");
232 goto prompterr;
233 case -1: /* Error or interrupt. */
234 msgq(sp, M_SYSERR, "shell");
235 goto prompterr;
236 default:
237 endp += nr;
238 break;
239 }
240
241 /* If any complete lines, push them into the file. */
242 for (p = t = buf; p < endp; ++p) {
243 if (*p == '\r' || *p == '\n') {
244 if (CHAR2INT5(sp, exp->ibcw, t, p - t, wp, wlen))
245 goto conv_err;
246 if (db_last(sp, &lline) ||
247 db_append(sp, 0, lline, wp, wlen))
248 goto prompterr;
249 t = p + 1;
250 }
251 }
252 if (p > buf) {
253 memmove(buf, t, endp - t);
254 endp = buf + (endp - t);
255 }
256 if (endp == buf)
257 goto more;
258
259 /* Wait up 1/10 of a second to make sure that we got it all. */
260 tv.tv_sec = 0;
261 tv.tv_usec = 100000;
262 switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
263 case -1: /* Error or interrupt. */
264 msgq(sp, M_SYSERR, "select");
265 goto prompterr;
266 case 0: /* Timeout */
267 break;
268 case 1: /* Characters to read. */
269 goto more;
270 }
271
272 /* Timed out, so theoretically we have a prompt. */
273 llen = endp - buf;
274 endp = buf;
275
276 /* Append the line into the file. */
277 if (CHAR2INT5(sp, exp->ibcw, buf, llen, wp, wlen))
278 goto conv_err;
279 if (db_last(sp, &lline) || db_append(sp, 0, lline, wp, wlen)) {
280 if (0)
281 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated.");
282 prompterr: sscr_end(sp);
283 return (1);
284 }
285
286 return (sscr_setprompt(sp, buf, llen));
287 }
288
289 /*
290 * sscr_exec --
291 * Take a line and hand it off to the shell.
292 *
293 * PUBLIC: int sscr_exec(SCR *, recno_t);
294 */
295 int
sscr_exec(SCR * sp,recno_t lno)296 sscr_exec(SCR *sp, recno_t lno)
297 {
298 SCRIPT *sc;
299 recno_t last_lno;
300 size_t blen, len, last_len, tlen;
301 int isempty, matchprompt, nw, rval;
302 char *bp = NULL, *p;
303 CHAR_T *wp;
304 size_t wlen;
305
306 /* If there's a prompt on the last line, append the command. */
307 if (db_last(sp, &last_lno))
308 return (1);
309 if (db_get(sp, last_lno, DBG_FATAL, &wp, &wlen))
310 return (1);
311 INT2CHAR(sp, wp, wlen, p, last_len);
312 if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
313 matchprompt = 1;
314 GET_SPACE_RETC(sp, bp, blen, last_len + 128);
315 memmove(bp, p, last_len);
316 } else
317 matchprompt = 0;
318
319 /* Get something to execute. */
320 if (db_eget(sp, lno, &wp, &wlen, &isempty)) {
321 if (isempty)
322 goto empty;
323 goto err1;
324 }
325
326 /* Empty lines aren't interesting. */
327 if (wlen == 0)
328 goto empty;
329 INT2CHAR(sp, wp, wlen, p, len);
330
331 /* Delete any prompt. */
332 if (sscr_matchprompt(sp, p, len, &tlen)) {
333 if (tlen == len) {
334 empty: msgq(sp, M_BERR, "151|No command to execute");
335 goto err1;
336 }
337 p += (len - tlen);
338 len = tlen;
339 }
340
341 /* Push the line to the shell. */
342 sc = sp->script;
343 if ((nw = write(sc->sh_master, p, len)) != len)
344 goto err2;
345 rval = 0;
346 if (write(sc->sh_master, "\n", 1) != 1) {
347 err2: if (nw == 0)
348 errno = EIO;
349 msgq(sp, M_SYSERR, "shell");
350 goto err1;
351 }
352
353 if (matchprompt) {
354 ADD_SPACE_RETC(sp, bp, blen, last_len + len);
355 memmove(bp + last_len, p, len);
356 CHAR2INT(sp, bp, last_len + len, wp, wlen);
357 if (db_set(sp, last_lno, wp, wlen))
358 err1: rval = 1;
359 }
360 if (matchprompt)
361 FREE_SPACE(sp, bp, blen);
362 return (rval);
363 }
364
365 /*
366 * sscr_input --
367 * Read any waiting shell input.
368 *
369 * PUBLIC: int sscr_input(SCR *);
370 */
371 int
sscr_input(SCR * sp)372 sscr_input(SCR *sp)
373 {
374 GS *gp;
375 struct timeval poll;
376 fd_set rdfd;
377 int maxfd;
378
379 gp = sp->gp;
380
381 loop: maxfd = 0;
382 FD_ZERO(&rdfd);
383 poll.tv_sec = 0;
384 poll.tv_usec = 0;
385
386 /* Set up the input mask. */
387 TAILQ_FOREACH(sp, gp->dq, q)
388 if (F_ISSET(sp, SC_SCRIPT)) {
389 FD_SET(sp->script->sh_master, &rdfd);
390 if (sp->script->sh_master > maxfd)
391 maxfd = sp->script->sh_master;
392 }
393
394 /* Check for input. */
395 switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
396 case -1:
397 msgq(sp, M_SYSERR, "select");
398 return (1);
399 case 0:
400 return (0);
401 default:
402 break;
403 }
404
405 /* Read the input. */
406 TAILQ_FOREACH(sp, gp->dq, q)
407 if (F_ISSET(sp, SC_SCRIPT) &&
408 FD_ISSET(sp->script->sh_master, &rdfd) &&
409 sscr_insert(sp))
410 return (1);
411 goto loop;
412 }
413
414 /*
415 * sscr_insert --
416 * Take a line from the shell and insert it into the file.
417 */
418 static int
sscr_insert(SCR * sp)419 sscr_insert(SCR *sp)
420 {
421 EX_PRIVATE *exp;
422 struct timeval tv;
423 char *endp, *p, *t;
424 SCRIPT *sc;
425 fd_set rdfd;
426 recno_t lno;
427 size_t blen, len, tlen;
428 int nr, rval;
429 char *bp;
430 CHAR_T *wp;
431 size_t wlen = 0;
432
433 exp = EXP(sp);
434
435
436 /* Find out where the end of the file is. */
437 if (db_last(sp, &lno))
438 return (1);
439
440 #define MINREAD 1024
441 GET_SPACE_RETC(sp, bp, blen, MINREAD);
442 endp = bp;
443
444 /* Read the characters. */
445 rval = 1;
446 sc = sp->script;
447 more: switch (nr = read(sc->sh_master, endp, MINREAD)) {
448 case 0: /* EOF; shell just exited. */
449 sscr_end(sp);
450 rval = 0;
451 goto ret;
452 case -1: /* Error or interrupt. */
453 msgq(sp, M_SYSERR, "shell");
454 goto ret;
455 default:
456 endp += nr;
457 break;
458 }
459
460 /* Append the lines into the file. */
461 for (p = t = bp; p < endp; ++p) {
462 if (*p == '\r' || *p == '\n') {
463 len = p - t;
464 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
465 goto conv_err;
466 if (db_append(sp, 1, lno++, wp, wlen))
467 goto ret;
468 t = p + 1;
469 }
470 }
471 if (p > t) {
472 len = p - t;
473 /*
474 * If the last thing from the shell isn't another prompt, wait
475 * up to 1/10 of a second for more stuff to show up, so that
476 * we don't break the output into two separate lines. Don't
477 * want to hang indefinitely because some program is hanging,
478 * confused the shell, or whatever.
479 */
480 if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
481 tv.tv_sec = 0;
482 tv.tv_usec = 100000;
483 FD_ZERO(&rdfd);
484 FD_SET(sc->sh_master, &rdfd);
485 if (select(sc->sh_master + 1,
486 &rdfd, NULL, NULL, &tv) == 1) {
487 memmove(bp, t, len);
488 endp = bp + len;
489 goto more;
490 }
491 }
492 if (sscr_setprompt(sp, t, len))
493 return (1);
494 if (CHAR2INT5(sp, exp->ibcw, t, len, wp, wlen))
495 goto conv_err;
496 if (db_append(sp, 1, lno++, wp, wlen))
497 goto ret;
498 }
499
500 /* The cursor moves to EOF. */
501 sp->lno = lno;
502 sp->cno = wlen ? wlen - 1 : 0;
503 rval = vs_refresh(sp, 1);
504
505 if (0)
506 conv_err: msgq(sp, M_ERR, "323|Invalid input. Truncated.");
507
508 ret: FREE_SPACE(sp, bp, blen);
509 return (rval);
510 }
511
512 /*
513 * sscr_setprompt --
514 *
515 * Set the prompt to the last line we got from the shell.
516 *
517 */
518 static int
sscr_setprompt(SCR * sp,char * buf,size_t len)519 sscr_setprompt(SCR *sp, char *buf, size_t len)
520 {
521 SCRIPT *sc;
522
523 sc = sp->script;
524 free(sc->sh_prompt);
525 MALLOC(sp, sc->sh_prompt, len + 1);
526 if (sc->sh_prompt == NULL) {
527 sscr_end(sp);
528 return (1);
529 }
530 memmove(sc->sh_prompt, buf, len);
531 sc->sh_prompt_len = len;
532 sc->sh_prompt[len] = '\0';
533 return (0);
534 }
535
536 /*
537 * sscr_matchprompt --
538 * Check to see if a line matches the prompt. Nul's indicate
539 * parts that can change, in both content and size.
540 */
541 static int
sscr_matchprompt(SCR * sp,char * lp,size_t line_len,size_t * lenp)542 sscr_matchprompt(SCR *sp, char *lp, size_t line_len, size_t *lenp)
543 {
544 SCRIPT *sc;
545 size_t prompt_len;
546 char *pp;
547
548 sc = sp->script;
549 if (line_len < (prompt_len = sc->sh_prompt_len))
550 return (0);
551
552 for (pp = sc->sh_prompt;
553 prompt_len && line_len; --prompt_len, --line_len) {
554 if (*pp == '\0') {
555 for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
556 if (!prompt_len)
557 return (0);
558 for (; line_len && *lp != *pp; --line_len, ++lp);
559 if (!line_len)
560 return (0);
561 }
562 if (*pp++ != *lp++)
563 break;
564 }
565
566 if (prompt_len)
567 return (0);
568 if (lenp != NULL)
569 *lenp = line_len;
570 return (1);
571 }
572
573 /*
574 * sscr_end --
575 * End the pipe to a shell.
576 *
577 * PUBLIC: int sscr_end(SCR *);
578 */
579 int
sscr_end(SCR * sp)580 sscr_end(SCR *sp)
581 {
582 SCRIPT *sc;
583
584 if ((sc = sp->script) == NULL)
585 return (0);
586
587 /* Turn off the script flags. */
588 F_CLR(sp, SC_SCRIPT);
589 sscr_check(sp);
590
591 /* Close down the parent's file descriptors. */
592 if (sc->sh_master != -1)
593 (void)close(sc->sh_master);
594 if (sc->sh_slave != -1)
595 (void)close(sc->sh_slave);
596
597 /* This should have killed the child. */
598 (void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
599
600 /* Free memory. */
601 free(sc->sh_prompt);
602 free(sc);
603 sp->script = NULL;
604
605 return (0);
606 }
607
608 /*
609 * sscr_check --
610 * Set/clear the global scripting bit.
611 */
612 static void
sscr_check(SCR * sp)613 sscr_check(SCR *sp)
614 {
615 GS *gp;
616
617 gp = sp->gp;
618 TAILQ_FOREACH(sp, gp->dq, q)
619 if (F_ISSET(sp, SC_SCRIPT)) {
620 F_SET(gp, G_SCRWIN);
621 return;
622 }
623 F_CLR(gp, G_SCRWIN);
624 }
625