GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/vi/build/../ex/ex_script.c Lines: 0 264 0.0 %
Date: 2017-11-07 Branches: 0 283 0.0 %

Line Branch Exec Source
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
}