xref: /netbsd-src/external/bsd/nvi/dist/ex/ex_script.c (revision 6a493d6bc668897c91594964a732d38505b70cbb)
1 /*	$NetBSD: ex_script.c,v 1.3 2013/11/25 22:43:46 christos Exp $ */
2 /*-
3  * Copyright (c) 1992, 1993, 1994
4  *	The Regents of the University of California.  All rights reserved.
5  * Copyright (c) 1992, 1993, 1994, 1995, 1996
6  *	Keith Bostic.  All rights reserved.
7  *
8  * This code is derived from software contributed to Berkeley by
9  * Brian Hirt.
10  *
11  * See the LICENSE file for redistribution information.
12  */
13 
14 #include "config.h"
15 
16 #ifndef lint
17 static const char sccsid[] = "Id: ex_script.c,v 10.38 2001/06/25 15:19:19 skimo Exp  (Berkeley) Date: 2001/06/25 15:19:19 ";
18 #endif /* not lint */
19 
20 #include <sys/types.h>
21 #include <sys/ioctl.h>
22 #include <sys/queue.h>
23 #ifdef HAVE_SYS_SELECT_H
24 #include <sys/select.h>
25 #endif
26 #include <sys/stat.h>
27 #if defined(HAVE_SYS5_PTY) && !defined(__NetBSD__)
28 #include <sys/stropts.h>
29 #endif
30 #include <sys/time.h>
31 #include <sys/wait.h>
32 
33 #include <bitstring.h>
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <stdio.h>		/* XXX: OSF/1 bug: include before <grp.h> */
37 #include <grp.h>
38 #include <limits.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <termios.h>
42 #include <unistd.h>
43 
44 #include "../common/common.h"
45 #include "../vi/vi.h"
46 #include "script.h"
47 #include "pathnames.h"
48 
49 static void	sscr_check __P((SCR *));
50 static int	sscr_getprompt __P((SCR *));
51 static int	sscr_init __P((SCR *));
52 static int	sscr_insert __P((SCR *));
53 static int	sscr_matchprompt __P((SCR *, CHAR_T *, size_t, size_t *));
54 static int	sscr_pty __P((int *, int *, char *, struct termios *, void *));
55 static int	sscr_setprompt __P((SCR *, CHAR_T *, size_t));
56 
57 /*
58  * ex_script -- : sc[ript][!] [file]
59  *	Switch to script mode.
60  *
61  * PUBLIC: int ex_script __P((SCR *, EXCMD *));
62  */
63 int
64 ex_script(SCR *sp, EXCMD *cmdp)
65 {
66 	/* Vi only command. */
67 	if (!F_ISSET(sp, SC_VI)) {
68 		msgq(sp, M_ERR,
69 		    "150|The script command is only available in vi mode");
70 		return (1);
71 	}
72 
73 	/* Switch to the new file. */
74 	if (cmdp->argc != 0 && ex_edit(sp, cmdp))
75 		return (1);
76 
77 	/* Create the shell, figure out the prompt. */
78 	if (sscr_init(sp))
79 		return (1);
80 
81 	return (0);
82 }
83 
84 /*
85  * sscr_init --
86  *	Create a pty setup for a shell.
87  */
88 static int
89 sscr_init(SCR *sp)
90 {
91 	SCRIPT *sc;
92 	const char *sh, *sh_path;
93 
94 	/* We're going to need a shell. */
95 	if (opts_empty(sp, O_SHELL, 0))
96 		return (1);
97 
98 	MALLOC_RET(sp, sc, SCRIPT *, sizeof(SCRIPT));
99 	sp->script = sc;
100 	sc->sh_prompt = NULL;
101 	sc->sh_prompt_len = 0;
102 
103 	/*
104 	 * There are two different processes running through this code.
105 	 * They are the shell and the parent.
106 	 */
107 	sc->sh_master = sc->sh_slave = -1;
108 
109 	if (tcgetattr(STDIN_FILENO, &sc->sh_term) == -1) {
110 		msgq(sp, M_SYSERR, "tcgetattr");
111 		goto err;
112 	}
113 
114 	/*
115 	 * Turn off output postprocessing and echo.
116 	 */
117 	sc->sh_term.c_oflag &= ~OPOST;
118 	sc->sh_term.c_cflag &= ~(ECHO|ECHOE|ECHONL|ECHOK);
119 
120 #ifdef TIOCGWINSZ
121 	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &sc->sh_win) == -1) {
122 		msgq(sp, M_SYSERR, "tcgetattr");
123 		goto err;
124 	}
125 
126 	if (sscr_pty(&sc->sh_master,
127 	    &sc->sh_slave, sc->sh_name, &sc->sh_term, &sc->sh_win) == -1) {
128 		msgq(sp, M_SYSERR, "pty");
129 		goto err;
130 	}
131 #else
132 	if (sscr_pty(&sc->sh_master,
133 	    &sc->sh_slave, sc->sh_name, &sc->sh_term, NULL) == -1) {
134 		msgq(sp, M_SYSERR, "pty");
135 		goto err;
136 	}
137 #endif
138 
139 	/*
140 	 * __TK__ huh?
141 	 * Don't use vfork() here, because the signal semantics differ from
142 	 * implementation to implementation.
143 	 */
144 	switch (sc->sh_pid = fork()) {
145 	case -1:			/* Error. */
146 		msgq(sp, M_SYSERR, "fork");
147 err:		if (sc->sh_master != -1)
148 			(void)close(sc->sh_master);
149 		if (sc->sh_slave != -1)
150 			(void)close(sc->sh_slave);
151 		return (1);
152 	case 0:				/* Utility. */
153 		/*
154 		 * XXX
155 		 * So that shells that do command line editing turn it off.
156 		 */
157 		(void)setenv("TERM", "emacs", 1);
158 		(void)setenv("TERMCAP", "emacs:", 1);
159 		(void)setenv("EMACS", "t", 1);
160 
161 		(void)setsid();
162 #ifdef TIOCSCTTY
163 		/*
164 		 * 4.4BSD allocates a controlling terminal using the TIOCSCTTY
165 		 * ioctl, not by opening a terminal device file.  POSIX 1003.1
166 		 * doesn't define a portable way to do this.  If TIOCSCTTY is
167 		 * not available, hope that the open does it.
168 		 */
169 		(void)ioctl(sc->sh_slave, TIOCSCTTY, 0);
170 #endif
171 		(void)close(sc->sh_master);
172 		(void)dup2(sc->sh_slave, STDIN_FILENO);
173 		(void)dup2(sc->sh_slave, STDOUT_FILENO);
174 		(void)dup2(sc->sh_slave, STDERR_FILENO);
175 		(void)close(sc->sh_slave);
176 
177 		/* Assumes that all shells have -i. */
178 		sh_path = O_STR(sp, O_SHELL);
179 		if ((sh = strrchr(sh_path, '/')) == NULL)
180 			sh = sh_path;
181 		else
182 			++sh;
183 		execl(sh_path, sh, "-i", NULL);
184 		msgq_str(sp, M_SYSERR, sh_path, "execl: %s");
185 		_exit(127);
186 	default:			/* Parent. */
187 		break;
188 	}
189 
190 	if (sscr_getprompt(sp))
191 		return (1);
192 
193 	F_SET(sp, SC_SCRIPT);
194 	F_SET(sp->gp, G_SCRWIN);
195 	return (0);
196 }
197 
198 /*
199  * sscr_getprompt --
200  *	Eat lines printed by the shell until a line with no trailing
201  *	carriage return comes; set the prompt from that line.
202  */
203 static int
204 sscr_getprompt(SCR *sp)
205 {
206 	struct timeval tv;
207 	CHAR_T *endp, *p, *t, buf[1024];
208 	SCRIPT *sc;
209 	fd_set fdset;
210 	db_recno_t lline;
211 	size_t llen, len;
212 	e_key_t value;
213 	int nr;
214 
215 	FD_ZERO(&fdset);
216 	endp = buf;
217 	len = sizeof(buf);
218 
219 	/* Wait up to a second for characters to read. */
220 	tv.tv_sec = 5;
221 	tv.tv_usec = 0;
222 	sc = sp->script;
223 	FD_SET(sc->sh_master, &fdset);
224 	switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
225 	case -1:		/* Error or interrupt. */
226 		msgq(sp, M_SYSERR, "select");
227 		goto prompterr;
228 	case  0:		/* Timeout */
229 		msgq(sp, M_ERR, "Error: timed out");
230 		goto prompterr;
231 	case  1:		/* Characters to read. */
232 		break;
233 	}
234 
235 	/* Read the characters. */
236 more:	len = sizeof(buf) - (endp - buf);
237 	switch (nr = read(sc->sh_master, endp, len)) {
238 	case  0:			/* EOF. */
239 		msgq(sp, M_ERR, "Error: shell: EOF");
240 		goto prompterr;
241 	case -1:			/* Error or interrupt. */
242 		msgq(sp, M_SYSERR, "shell");
243 		goto prompterr;
244 	default:
245 		endp += nr;
246 		break;
247 	}
248 
249 	/* If any complete lines, push them into the file. */
250 	for (p = t = buf; p < endp; ++p) {
251 		value = KEY_VAL(sp, *p);
252 		if (value == K_CR || value == K_NL) {
253 			if (db_last(sp, &lline) ||
254 			    db_append(sp, 0, lline, t, p - t))
255 				goto prompterr;
256 			t = p + 1;
257 		}
258 	}
259 	if (p > buf) {
260 		MEMMOVE(buf, t, endp - t);
261 		endp = buf + (endp - t);
262 	}
263 	if (endp == buf)
264 		goto more;
265 
266 	/* Wait up 1/10 of a second to make sure that we got it all. */
267 	tv.tv_sec = 0;
268 	tv.tv_usec = 100000;
269 	switch (select(sc->sh_master + 1, &fdset, NULL, NULL, &tv)) {
270 	case -1:		/* Error or interrupt. */
271 		msgq(sp, M_SYSERR, "select");
272 		goto prompterr;
273 	case  0:		/* Timeout */
274 		break;
275 	case  1:		/* Characters to read. */
276 		goto more;
277 	}
278 
279 	/* Timed out, so theoretically we have a prompt. */
280 	llen = endp - buf;
281 	endp = buf;
282 
283 	/* Append the line into the file. */
284 	if (db_last(sp, &lline) || db_append(sp, 0, lline, buf, llen)) {
285 prompterr:	sscr_end(sp);
286 		return (1);
287 	}
288 
289 	return (sscr_setprompt(sp, buf, llen));
290 }
291 
292 /*
293  * sscr_exec --
294  *	Take a line and hand it off to the shell.
295  *
296  * PUBLIC: int sscr_exec __P((SCR *, db_recno_t));
297  */
298 int
299 sscr_exec(SCR *sp, db_recno_t lno)
300 {
301 	SCRIPT *sc;
302 	db_recno_t last_lno;
303 	size_t blen, len, last_len, tlen;
304 	int isempty, matchprompt, rval;
305 	ssize_t nw;
306 	CHAR_T *bp = NULL;
307 	CHAR_T *p;
308 
309 	/* If there's a prompt on the last line, append the command. */
310 	if (db_last(sp, &last_lno))
311 		return (1);
312 	if (db_get(sp, last_lno, DBG_FATAL, &p, &last_len))
313 		return (1);
314 	if (sscr_matchprompt(sp, p, last_len, &tlen) && tlen == 0) {
315 		matchprompt = 1;
316 		GET_SPACE_RETW(sp, bp, blen, last_len + 128);
317 		MEMMOVEW(bp, p, last_len);
318 	} else
319 		matchprompt = 0;
320 
321 	/* Get something to execute. */
322 	if (db_eget(sp, lno, &p, &len, &isempty)) {
323 		if (isempty)
324 			goto empty;
325 		goto err1;
326 	}
327 
328 	/* Empty lines aren't interesting. */
329 	if (len == 0)
330 		goto empty;
331 
332 	/* Delete any prompt. */
333 	if (sscr_matchprompt(sp, p, len, &tlen)) {
334 		if (tlen == len) {
335 empty:			msgq(sp, M_BERR, "151|No command to execute");
336 			goto err1;
337 		}
338 		p += (len - tlen);
339 		len = tlen;
340 	}
341 
342 	/* Push the line to the shell. */
343 	sc = sp->script;
344 	if ((size_t)(nw = write(sc->sh_master, p, len)) != len)
345 		goto err2;
346 	rval = 0;
347 	if (write(sc->sh_master, "\n", 1) != 1) {
348 err2:		if (nw == 0)
349 			errno = EIO;
350 		msgq(sp, M_SYSERR, "shell");
351 		goto err1;
352 	}
353 
354 	if (matchprompt) {
355 		ADD_SPACE_RETW(sp, bp, blen, last_len + len);
356 		MEMMOVEW(bp + last_len, p, len);
357 		if (db_set(sp, last_lno, bp, last_len + len))
358 err1:			rval = 1;
359 	}
360 	if (matchprompt)
361 		FREE_SPACEW(sp, bp, blen);
362 	return (rval);
363 }
364 
365 /*
366  * sscr_check_input -
367  *	Check whether any input from shell or passed set.
368  *
369  * PUBLIC: int sscr_check_input __P((SCR *sp, fd_set *rdfd, int maxfd));
370  */
371 int
372 sscr_check_input(SCR *sp, fd_set *fdset, int maxfd)
373 {
374 	fd_set rdfd;
375 	SCR *tsp;
376 	WIN *wp;
377 
378 	wp = sp->wp;
379 
380 loop:	memcpy(&rdfd, fdset, sizeof(fd_set));
381 
382 	TAILQ_FOREACH(tsp, &wp->scrq, q)
383 		if (F_ISSET(sp, SC_SCRIPT)) {
384 			FD_SET(sp->script->sh_master, &rdfd);
385 			if (sp->script->sh_master > maxfd)
386 				maxfd = sp->script->sh_master;
387 		}
388 	switch (select(maxfd + 1, &rdfd, NULL, NULL, NULL)) {
389 	case 0:
390 		abort();
391 	case -1:
392 		return 1;
393 	default:
394 		break;
395 	}
396 	TAILQ_FOREACH(tsp, &wp->scrq, q)
397 		if (F_ISSET(sp, SC_SCRIPT) &&
398 		    FD_ISSET(sp->script->sh_master, &rdfd)) {
399 			if (sscr_input(sp))
400 				return 1;
401 			goto loop;
402 		}
403 	return 0;
404 }
405 
406 /*
407  * sscr_input --
408  *	Read any waiting shell input.
409  *
410  * PUBLIC: int sscr_input __P((SCR *));
411  */
412 int
413 sscr_input(SCR *sp)
414 {
415 	WIN *wp;
416 	struct timeval poll;
417 	fd_set rdfd;
418 	int maxfd;
419 
420 	wp = sp->wp;
421 
422 loop:	maxfd = 0;
423 	FD_ZERO(&rdfd);
424 	poll.tv_sec = 0;
425 	poll.tv_usec = 0;
426 
427 	/* Set up the input mask. */
428 	TAILQ_FOREACH(sp, &wp->scrq, q)
429 		if (F_ISSET(sp, SC_SCRIPT)) {
430 			FD_SET(sp->script->sh_master, &rdfd);
431 			if (sp->script->sh_master > maxfd)
432 				maxfd = sp->script->sh_master;
433 		}
434 
435 	/* Check for input. */
436 	switch (select(maxfd + 1, &rdfd, NULL, NULL, &poll)) {
437 	case -1:
438 		msgq(sp, M_SYSERR, "select");
439 		return (1);
440 	case 0:
441 		return (0);
442 	default:
443 		break;
444 	}
445 
446 	/* Read the input. */
447 	TAILQ_FOREACH(sp, &wp->scrq, q)
448 		if (F_ISSET(sp, SC_SCRIPT) &&
449 		    FD_ISSET(sp->script->sh_master, &rdfd) &&
450 		    sscr_insert(sp))
451 			return (1);
452 	goto loop;
453 }
454 
455 /*
456  * sscr_insert --
457  *	Take a line from the shell and insert it into the file.
458  */
459 static int
460 sscr_insert(SCR *sp)
461 {
462 	struct timeval tv;
463 	CHAR_T *endp, *p, *t;
464 	SCRIPT *sc;
465 	fd_set rdfd;
466 	db_recno_t lno;
467 	size_t blen, len = 0, tlen;
468 	e_key_t value;
469 	int nr, rval;
470 	CHAR_T *bp;
471 
472 	/* Find out where the end of the file is. */
473 	if (db_last(sp, &lno))
474 		return (1);
475 
476 #define	MINREAD	1024
477 	GET_SPACE_RETW(sp, bp, blen, MINREAD);
478 	endp = bp;
479 
480 	/* Read the characters. */
481 	rval = 1;
482 	sc = sp->script;
483 more:	switch (nr = read(sc->sh_master, endp, MINREAD)) {
484 	case  0:			/* EOF; shell just exited. */
485 		sscr_end(sp);
486 		rval = 0;
487 		goto ret;
488 	case -1:			/* Error or interrupt. */
489 		msgq(sp, M_SYSERR, "shell");
490 		goto ret;
491 	default:
492 		endp += nr;
493 		break;
494 	}
495 
496 	/* Append the lines into the file. */
497 	for (p = t = bp; p < endp; ++p) {
498 		value = KEY_VAL(sp, *p);
499 		if (value == K_CR || value == K_NL) {
500 			len = p - t;
501 			if (db_append(sp, 1, lno++, t, len))
502 				goto ret;
503 			t = p + 1;
504 		}
505 	}
506 	if (p > t) {
507 		len = p - t;
508 		/*
509 		 * If the last thing from the shell isn't another prompt, wait
510 		 * up to 1/10 of a second for more stuff to show up, so that
511 		 * we don't break the output into two separate lines.  Don't
512 		 * want to hang indefinitely because some program is hanging,
513 		 * confused the shell, or whatever.
514 		 */
515 		if (!sscr_matchprompt(sp, t, len, &tlen) || tlen != 0) {
516 			tv.tv_sec = 0;
517 			tv.tv_usec = 100000;
518 			FD_ZERO(&rdfd);
519 			FD_SET(sc->sh_master, &rdfd);
520 			if (select(sc->sh_master + 1,
521 			    &rdfd, NULL, NULL, &tv) == 1) {
522 				MEMMOVE(bp, t, len);
523 				endp = bp + len;
524 				goto more;
525 			}
526 		}
527 		if (sscr_setprompt(sp, t, len))
528 			return (1);
529 		if (db_append(sp, 1, lno++, t, len))
530 			goto ret;
531 	}
532 
533 	/* The cursor moves to EOF. */
534 	sp->lno = lno;
535 	sp->cno = len ? len - 1 : 0;
536 	rval = vs_refresh(sp, 1);
537 
538 ret:	FREE_SPACEW(sp, bp, blen);
539 	return (rval);
540 }
541 
542 /*
543  * sscr_setprompt --
544  *
545  * Set the prompt to the last line we got from the shell.
546  *
547  */
548 static int
549 sscr_setprompt(SCR *sp, CHAR_T *buf, size_t len)
550 {
551 	SCRIPT *sc;
552 	const char *np;
553 	size_t nlen;
554 
555 	sc = sp->script;
556 	if (sc->sh_prompt)
557 		free(sc->sh_prompt);
558 	MALLOC(sp, sc->sh_prompt, char *, len + 1);
559 	if (sc->sh_prompt == NULL) {
560 		sscr_end(sp);
561 		return (1);
562 	}
563 	INT2CHAR(sp, buf, len, np, nlen);
564 	memmove(sc->sh_prompt, np, nlen);
565 	sc->sh_prompt_len = len;
566 	sc->sh_prompt[len] = '\0';
567 	return (0);
568 }
569 
570 /*
571  * sscr_matchprompt --
572  *	Check to see if a line matches the prompt.  Nul's indicate
573  *	parts that can change, in both content and size.
574  */
575 static int
576 sscr_matchprompt(SCR *sp, CHAR_T *lp, size_t line_len, size_t *lenp)
577 {
578 	SCRIPT *sc;
579 	size_t prompt_len;
580 	char *pp;
581 
582 	sc = sp->script;
583 	if (line_len < (prompt_len = sc->sh_prompt_len))
584 		return (0);
585 
586 	for (pp = sc->sh_prompt;
587 	    prompt_len && line_len; --prompt_len, --line_len) {
588 		if (*pp == '\0') {
589 			for (; prompt_len && *pp == '\0'; --prompt_len, ++pp);
590 			if (!prompt_len)
591 				return (0);
592 			for (; line_len && *lp != *pp; --line_len, ++lp);
593 			if (!line_len)
594 				return (0);
595 		}
596 		if (*pp++ != *lp++)
597 			break;
598 	}
599 
600 	if (prompt_len)
601 		return (0);
602 	if (lenp != NULL)
603 		*lenp = line_len;
604 	return (1);
605 }
606 
607 /*
608  * sscr_end --
609  *	End the pipe to a shell.
610  *
611  * PUBLIC: int sscr_end __P((SCR *));
612  */
613 int
614 sscr_end(SCR *sp)
615 {
616 	SCRIPT *sc;
617 
618 	if ((sc = sp->script) == NULL)
619 		return (0);
620 
621 	/* Turn off the script flags. */
622 	F_CLR(sp, SC_SCRIPT);
623 	sscr_check(sp);
624 
625 	/* Close down the parent's file descriptors. */
626 	if (sc->sh_master != -1)
627 	    (void)close(sc->sh_master);
628 	if (sc->sh_slave != -1)
629 	    (void)close(sc->sh_slave);
630 
631 	/* This should have killed the child. */
632 	(void)proc_wait(sp, (long)sc->sh_pid, "script-shell", 0, 0);
633 
634 	/* Free memory. */
635 	free(sc->sh_prompt);
636 	free(sc);
637 	sp->script = NULL;
638 
639 	return (0);
640 }
641 
642 /*
643  * sscr_check --
644  *	Set/clear the global scripting bit.
645  */
646 static void
647 sscr_check(SCR *sp)
648 {
649 	GS *gp;
650 	WIN *wp;
651 
652 	gp = sp->gp;
653 	wp = sp->wp;
654 	TAILQ_FOREACH(sp, &wp->scrq, q)
655 		if (F_ISSET(sp, SC_SCRIPT)) {
656 			F_SET(gp, G_SCRWIN);
657 			return;
658 		}
659 	F_CLR(gp, G_SCRWIN);
660 }
661 
662 #ifdef HAVE_SYS5_PTY
663 static int ptys_open __P((int, char *));
664 static int ptym_open __P((char *));
665 
666 static int
667 sscr_pty(int *amaster, int *aslave, char *name, struct termios *termp, void *winp)
668 {
669 	int master, slave;
670 
671 	/* open master terminal */
672 	if ((master = ptym_open(name)) < 0)  {
673 		errno = ENOENT;	/* out of ptys */
674 		return (-1);
675 	}
676 
677 	/* open slave terminal */
678 	if ((slave = ptys_open(master, name)) >= 0) {
679 		*amaster = master;
680 		*aslave = slave;
681 	} else {
682 		errno = ENOENT;	/* out of ptys */
683 		return (-1);
684 	}
685 
686 	if (termp)
687 		(void) tcsetattr(slave, TCSAFLUSH, termp);
688 #ifdef TIOCSWINSZ
689 	if (winp != NULL)
690 		(void) ioctl(slave, TIOCSWINSZ, (struct winsize *)winp);
691 #endif
692 	return (0);
693 }
694 
695 /*
696  * ptym_open --
697  *	This function opens a master pty and returns the file descriptor
698  *	to it.  pts_name is also returned which is the name of the slave.
699  */
700 static int
701 ptym_open(char *pts_name)
702 {
703 	int fdm;
704 	char *ptr;
705 
706 	strcpy(pts_name, _PATH_SYSV_PTY);
707 	if ((fdm = open(pts_name, O_RDWR)) < 0 )
708 		return (-1);
709 
710 	if (grantpt(fdm) < 0) {
711 		close(fdm);
712 		return (-2);
713 	}
714 
715 	if (unlockpt(fdm) < 0) {
716 		close(fdm);
717 		return (-3);
718 	}
719 
720 	if (unlockpt(fdm) < 0) {
721 		close(fdm);
722 		return (-3);
723 	}
724 
725 	/* get slave's name */
726 	if ((ptr = ptsname(fdm)) == NULL) {
727 		close(fdm);
728 		return (-3);
729 	}
730 	strcpy(pts_name, ptr);
731 	return (fdm);
732 }
733 
734 /*
735  * ptys_open --
736  *	This function opens the slave pty.
737  */
738 static int
739 ptys_open(int fdm, char *pts_name)
740 {
741 	int fds;
742 
743 	if ((fds = open(pts_name, O_RDWR)) < 0) {
744 		close(fdm);
745 		return (-5);
746 	}
747 
748 #ifdef I_PUSH
749 	if (ioctl(fds, I_PUSH, "ptem") < 0) {
750 		close(fds);
751 		close(fdm);
752 		return (-6);
753 	}
754 
755 	if (ioctl(fds, I_PUSH, "ldterm") < 0) {
756 		close(fds);
757 		close(fdm);
758 		return (-7);
759 	}
760 
761 	if (ioctl(fds, I_PUSH, "ttcompat") < 0) {
762 		close(fds);
763 		close(fdm);
764 		return (-8);
765 	}
766 #endif /* I_PUSH */
767 
768 	return (fds);
769 }
770 
771 #else /* !HAVE_SYS5_PTY */
772 
773 static int
774 sscr_pty(amaster, aslave, name, termp, winp)
775 	int *amaster, *aslave;
776 	char *name;
777 	struct termios *termp;
778 	void *winp;
779 {
780 	static char line[] = "/dev/ptyXX";
781 	const char *cp1, *cp2;
782 	int master, slave, ttygid;
783 	struct group *gr;
784 
785 	if ((gr = getgrnam("tty")) != NULL)
786 		ttygid = gr->gr_gid;
787 	else
788 		ttygid = -1;
789 
790 	for (cp1 = "pqrs"; *cp1; cp1++) {
791 		line[8] = *cp1;
792 		for (cp2 = "0123456789abcdef"; *cp2; cp2++) {
793 			line[5] = 'p';
794 			line[9] = *cp2;
795 			if ((master = open(line, O_RDWR, 0)) == -1) {
796 				if (errno == ENOENT)
797 					return (-1);	/* out of ptys */
798 			} else {
799 				line[5] = 't';
800 				(void) chown(line, getuid(), ttygid);
801 				(void) chmod(line, S_IRUSR|S_IWUSR|S_IWGRP);
802 #ifdef HAVE_REVOKE
803 				(void) revoke(line);
804 #endif
805 				if ((slave = open(line, O_RDWR, 0)) != -1) {
806 					*amaster = master;
807 					*aslave = slave;
808 					if (name)
809 						strcpy(name, line);
810 					if (termp)
811 						(void) tcsetattr(slave,
812 							TCSAFLUSH, termp);
813 #ifdef TIOCSWINSZ
814 					if (winp)
815 						(void) ioctl(slave, TIOCSWINSZ,
816 							(char *)winp);
817 #endif
818 					return (0);
819 				}
820 				(void) close(master);
821 			}
822 		}
823 	}
824 	errno = ENOENT;	/* out of ptys */
825 	return (-1);
826 }
827 #endif /* HAVE_SYS5_PTY */
828