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