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