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