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