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