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