xref: /illumos-gate/usr/src/cmd/enhance/enhance.c (revision 0ccfe5834c3b867c1b6c273cae02ec8922bc0fd2)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <signal.h>
32 #include <locale.h>
33 
34 #include <unistd.h>
35 #include <termios.h>
36 
37 #ifdef HAVE_SELECT
38 #ifdef HAVE_SYS_SELECT_H
39 #include <sys/select.h>
40 #endif
41 #endif
42 
43 #include <fcntl.h>
44 #include <sys/time.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #include <dirent.h>
48 
49 #if HAVE_SYSV_PTY
50 #include <stropts.h>    /* System-V stream I/O */
51 char *ptsname(int fd);
52 int grantpt(int fd);
53 int unlockpt(int fd);
54 #endif
55 
56 #include "libtecla.h"
57 
58 /*
59  * Pseudo-terminal devices are found in the following directory.
60  */
61 #define PTY_DEV_DIR "/dev/"
62 
63 /*
64  * Pseudo-terminal controller device file names start with the following
65  * prefix.
66  */
67 #define PTY_CNTRL "pty"
68 
69 /*
70  * Pseudo-terminal subsidiary device file names start with the following
71  * prefix.
72  */
73 #define PTY_SUBSID "tty"
74 
75 /*
76  * Specify the maximum suffix length for the control and subsidiary device
77  * names.
78  */
79 #define PTY_MAX_SUFFIX 10
80 
81 /*
82  * Set the maximum length of the manager and subsidiary terminal device
83  * filenames, including space for a terminating '\0'.
84  */
85 #define PTY_MAX_NAME (sizeof(PTY_DEV_DIR)-1 + \
86 		      (sizeof(PTY_SUBSID) > sizeof(PTY_CNTRL) ? \
87 		       sizeof(PTY_SUBSID) : sizeof(PTY_CNTRL))-1 \
88 		      + PTY_MAX_SUFFIX + 1)
89 /*
90  * Set the maximum length of an input line.
91  */
92 #define PTY_MAX_LINE 4096
93 
94 /*
95  * Set the size of the buffer used for accumulating bytes written by the
96  * user's terminal to its stdout.
97  */
98 #define PTY_MAX_READ 1000
99 
100 /*
101  * Set the amount of memory used to record history.
102  */
103 #define PTY_HIST_SIZE 10000
104 
105 /*
106  * Set the timeout delay used to check for quickly arriving
107  * sequential output from the application.
108  */
109 #define PTY_READ_TIMEOUT 100000    /* micro-seconds */
110 
111 static int pty_open_manager(const char *prog, int *cntrl, char *subsid_name);
112 static int pty_open_subsid(const char *prog, char *subsid_name);
113 static int pty_child(const char *prog, int subsid, char *argv[]);
114 static int pty_parent(const char *prog, int cntrl);
115 static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff);
116 static GL_FD_EVENT_FN(pty_read_from_program);
117 static int pty_write_to_fd(int fd, const char *string, int n);
118 static void pty_child_exited(int sig);
119 static int pty_manager_readable(int fd, long usec);
120 
121 /*.......................................................................
122  * Run a program with enhanced terminal editing facilities.
123  *
124  * Usage:
125  *  enhance program [args...]
126  */
127 int main(int argc, char *argv[])
128 {
129   int cntrl = -1;  /* The fd of the pseudo-terminal controller device */
130   int subsid = -1;  /* The fd of the pseudo-terminal subsidiary device */
131   pid_t pid;       /* The return value of fork() */
132   int status;      /* The return statuses of the parent and child functions */
133   char subsid_name[PTY_MAX_NAME]; /* The filename of the subsidiary end of */
134 				 /*  the pseudo-terminal. */
135   char *prog;      /* The name of the program (ie. argv[0]) */
136 /*
137  * Check the arguments.
138  */
139   if(argc < 2) {
140     fprintf(stderr, "Usage: %s <program> [arguments...]\n", argv[0]);
141     return 1;
142   };
143 /*
144  * Get the name of the program.
145  */
146   prog = argv[0];
147 /*
148  * If the user has the LC_CTYPE or LC_ALL environment variables set,
149  * enable display of characters corresponding to the specified locale.
150  */
151   (void) setlocale(LC_CTYPE, "");
152 /*
153  * If the program is taking its input from a pipe or a file, or
154  * sending its output to something other than a terminal, run the
155  * program without tecla.
156  */
157   if(!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) {
158     if(execvp(argv[1], argv + 1) < 0) {
159       fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[1],
160 	      strerror(errno));
161       fflush(stderr);
162       _exit(1);
163     };
164   };
165 /*
166  * Open the manager side of a pseudo-terminal pair, and return
167  * the corresponding file descriptor and the filename of the
168  * subsidiary end of the pseudo-terminal.
169  */
170   if(pty_open_manager(prog, &cntrl, subsid_name))
171     return 1;
172 /*
173  * Set up a signal handler to watch for the child process exiting.
174  */
175   signal(SIGCHLD, pty_child_exited);
176 /*
177  * The above signal handler sends the parent process a SIGINT signal.
178  * This signal is caught by gl_get_line(), which resets the terminal
179  * settings, and if the application signal handler for this signal
180  * doesn't abort the process, gl_get_line() returns NULL with errno
181  * set to EINTR. Arrange to ignore the signal, so that gl_get_line()
182  * returns and we have a chance to cleanup.
183  */
184   signal(SIGINT, SIG_IGN);
185 /*
186  * We will read user input in one process, and run the user's program
187  * in a child process.
188  */
189   pid = fork();
190   if(pid < 0) {
191     fprintf(stderr, "%s: Unable to fork child process (%s).\n", prog,
192 	    strerror(errno));
193     return 1;
194   };
195 /*
196  * Are we the parent?
197  */
198   if(pid!=0) {
199     status = pty_parent(prog, cntrl);
200     close(cntrl);
201   } else {
202     close(cntrl); /* The child doesn't use the subsidiary device */
203     signal(SIGCHLD, pty_child_exited);
204     if((subsid = pty_open_subsid(prog, subsid_name)) >= 0) {
205       status = pty_child(prog, subsid, argv + 1);
206       close(subsid);
207     } else {
208       status = 1;
209     };
210   };
211   return status;
212 }
213 
214 /*.......................................................................
215  * Open the manager side of a pseudo-terminal pair, and return
216  * the corresponding file descriptor and the filename of the
217  * subsidiary end of the pseudo-terminal.
218  *
219  * Input/Output:
220  *  prog  const char *  The name of this program.
221  *  cntrl        int *  The file descriptor of the pseudo-terminal
222  *                      controller device will be assigned tp *cntrl.
223  *  subsid_name  char *  The file-name of the pseudo-terminal subsidiary device
224  *                      will be recorded in subsid_name[], which must have
225  *                      at least PTY_MAX_NAME elements.
226  * Output:
227  *  return       int    0 - OK.
228  *                      1 - Error.
229  */
230 static int pty_open_manager(const char *prog, int *cntrl, char *subsid_name)
231 {
232   char manager_name[PTY_MAX_NAME]; /* The filename of the manager device */
233   DIR *dir;                       /* The directory iterator */
234   struct dirent *file;            /* A file in "/dev" */
235 /*
236  * Mark the controller device as not opened yet.
237  */
238   *cntrl = -1;
239 /*
240  * On systems with the Sys-V pseudo-terminal interface, we don't
241  * have to search for a free manager terminal. We just open /dev/ptmx,
242  * and if there is a free manager terminal device, we are given a file
243  * descriptor connected to it.
244  */
245 #if HAVE_SYSV_PTY
246   *cntrl = open("/dev/ptmx", O_RDWR);
247   if(*cntrl >= 0) {
248 /*
249  * Get the filename of the subsidiary side of the pseudo-terminal.
250  */
251     char *name = ptsname(*cntrl);
252     if(name) {
253       if(strlen(name)+1 > PTY_MAX_NAME) {
254 	fprintf(stderr, "%s: Subsidiary pty filename too long.\n", prog);
255 	return 1;
256       };
257       strlcpy(subsid_name, name, PTY_MAX_NAME);
258 /*
259  * If unable to get the subsidiary name, discard the controller file
260  * descriptor, ready to try a search instead.
261  */
262     } else {
263       close(*cntrl);
264       *cntrl = -1;
265     };
266   } else {
267 #endif
268 /*
269  * On systems without /dev/ptmx, or if opening /dev/ptmx failed,
270  * we open one manager terminal after another, until one that isn't
271  * in use by another program is found.
272  *
273  * Open the devices directory.
274  */
275     dir = opendir(PTY_DEV_DIR);
276     if(!dir) {
277       fprintf(stderr, "%s: Couldn't open %s (%s)\n", prog, PTY_DEV_DIR,
278 	      strerror(errno));
279       return 1;
280     };
281 /*
282  * Look for pseudo-terminal controller device files in the devices
283  * directory.
284  */
285     while(*cntrl < 0 && (file = readdir(dir))) {
286       if(strncmp(file->d_name, PTY_CNTRL, sizeof(PTY_CNTRL)-1) == 0) {
287 /*
288  * Get the common extension of the control and subsidiary filenames.
289  */
290 	const char *ext = file->d_name + sizeof(PTY_CNTRL)-1;
291 	if(strlen(ext) > PTY_MAX_SUFFIX)
292 	  continue;
293 /*
294  * Attempt to open the control file.
295  */
296 	strlcpy(manager_name, PTY_DEV_DIR, sizeof(manager_name));
297 	strlcat(manager_name, PTY_CNTRL, sizeof(manager_name));
298 	strlcat(manager_name, ext, sizeof(manager_name));
299 	*cntrl = open(manager_name, O_RDWR);
300 	if(*cntrl < 0)
301 	  continue;
302 /*
303  * Attempt to open the matching subsidiary file.
304  */
305 	strlcpy(subsid_name, PTY_DEV_DIR, PTY_MAX_NAME);
306 	strlcat(subsid_name, PTY_SUBSID, PTY_MAX_NAME);
307 	strlcat(subsid_name, ext, PTY_MAX_NAME);
308       };
309     };
310     closedir(dir);
311 #if HAVE_SYSV_PTY
312   };
313 #endif
314 /*
315  * Did we fail to find a pseudo-terminal pair that we could open?
316  */
317   if(*cntrl < 0) {
318     fprintf(stderr, "%s: Unable to find a free pseudo-terminal.\n", prog);
319     return 1;
320   };
321 /*
322  * System V systems require the program that opens the manager to
323  * grant access to the subsidiary side of the pseudo-terminal.
324  */
325 #ifdef HAVE_SYSV_PTY
326   if(grantpt(*cntrl) < 0 ||
327      unlockpt(*cntrl) < 0) {
328     fprintf(stderr, "%s: Unable to unlock terminal (%s).\n", prog,
329 	    strerror(errno));
330     return 1;
331   };
332 #endif
333 /*
334  * Success.
335  */
336   return 0;
337 }
338 
339 /*.......................................................................
340  * Open the subsidiary end of a pseudo-terminal.
341  *
342  * Input:
343  *  prog   const char *  The name of this program.
344  *  subsid_name   char *  The filename of the subsidiary device.
345  * Output:
346  *  return        int    The file descriptor of the successfully opened
347  *                       subsidiary device, or < 0 on error.
348  */
349 static int pty_open_subsid(const char *prog, char *subsid_name)
350 {
351   int fd;  /* The file descriptor of the subsidiary device */
352 /*
353  * Place the process in its own process group. In system-V based
354  * OS's, this ensures that when the pseudo-terminal is opened, it
355  * becomes the controlling terminal of the process.
356  */
357   if(setsid() < 0) {
358     fprintf(stderr, "%s: Unable to form new process group (%s).\n", prog,
359 	    strerror(errno));
360     return -1;
361   };
362 /*
363  * Attempt to open the specified device.
364  */
365   fd = open(subsid_name, O_RDWR);
366   if(fd < 0) {
367     fprintf(stderr, "%s: Unable to open pty subsidiary device (%s).\n",
368 	    prog, strerror(errno));
369     return -1;
370   };
371 /*
372  * On system-V streams based systems, we need to push the stream modules
373  * that implement pseudo-terminal and termio interfaces. At least on
374  * Solaris, which pushes these automatically when a subsidiary is opened,
375  * this is redundant, so ignore errors when pushing the modules.
376  */
377 #if HAVE_SYSV_PTY
378   (void) ioctl(fd, I_PUSH, "ptem");
379   (void) ioctl(fd, I_PUSH, "ldterm");
380 /*
381  * On BSD based systems other than SunOS 4.x, the following makes the
382  * pseudo-terminal the controlling terminal of the child process.
383  * According to the pseudo-terminal example code in Steven's
384  * Advanced programming in the unix environment, the !defined(CIBAUD)
385  * part of the clause prevents this from being used under SunOS. Since
386  * I only have his code with me, and won't have access to the book,
387  * I don't know why this is necessary.
388  */
389 #elif defined(TIOCSCTTY) && !defined(CIBAUD)
390   if(ioctl(fd, TIOCSCTTY, (char *) 0) < 0) {
391     fprintf(stderr, "%s: Unable to establish controlling terminal (%s).\n",
392 	    prog, strerror(errno));
393     close(fd);
394     return -1;
395   };
396 #endif
397   return fd;
398 }
399 
400 /*.......................................................................
401  * Read input from the controlling terminal of the program, using
402  * gl_get_line(), and feed it to the user's program running in a child
403  * process, via the controller side of the pseudo-terminal. Also pass
404  * data received from the user's program via the conroller end of
405  * the pseudo-terminal, to stdout.
406  *
407  * Input:
408  *  prog  const char *  The name of this program.
409  *  cntrl        int    The file descriptor of the controller end of the
410  *                      pseudo-terminal.
411  * Output:
412  *  return       int    0 - OK.
413  *                      1 - Error.
414  */
415 static int pty_parent(const char *prog, int cntrl)
416 {
417   GetLine *gl = NULL;  /* The gl_get_line() resource object */
418   char *line;          /* An input line read from the user */
419   char *rbuff=NULL;    /* A buffer for reading from the pseudo terminal */
420 /*
421  * Allocate the gl_get_line() resource object.
422  */
423   gl = new_GetLine(PTY_MAX_LINE, PTY_HIST_SIZE);
424   if(!gl)
425     return pty_stop_parent(1, cntrl, gl, rbuff);
426 /*
427  * Allocate a buffer to use to accumulate bytes read from the
428  * pseudo-terminal.
429  */
430   rbuff = (char *) malloc(PTY_MAX_READ+1);
431   if(!rbuff)
432     return pty_stop_parent(1, cntrl, gl, rbuff);
433   rbuff[0] = '\0';
434 /*
435  * Register an event handler to watch for data appearing from the
436  * user's program on the controller end of the pseudo terminal.
437  */
438   if(gl_watch_fd(gl, cntrl, GLFD_READ, pty_read_from_program, rbuff))
439     return pty_stop_parent(1, cntrl, gl, rbuff);
440 /*
441  * Read input lines from the user and pass them on to the user's program,
442  * by writing to the controller end of the pseudo-terminal.
443  */
444   while((line=gl_get_line(gl, rbuff, NULL, 0))) {
445     if(pty_write_to_fd(cntrl, line, strlen(line)))
446        return pty_stop_parent(1, cntrl, gl, rbuff);
447     rbuff[0] = '\0';
448   };
449   return pty_stop_parent(0, cntrl, gl, rbuff);
450 }
451 
452 /*.......................................................................
453  * This is a private return function of pty_parent(), used to release
454  * dynamically allocated resources, close the controller end of the
455  * pseudo-terminal, and wait for the child to exit. It returns the
456  * exit status of the child process, unless the caller reports an
457  * error itself, in which case the caller's error status is returned.
458  *
459  * Input:
460  *  waserr   int    True if the caller is calling this function because
461  *                  an error occured.
462  *  cntrl    int    The file descriptor of the controller end of the
463  *                  pseudo-terminal.
464  *  gl   GetLine *  The resource object of gl_get_line().
465  *  rbuff   char *  The buffer used to accumulate bytes read from
466  *                  the pseudo-terminal.
467  * Output:
468  *  return  int    The desired exit status of the program.
469  */
470 static int pty_stop_parent(int waserr, int cntrl, GetLine *gl, char *rbuff)
471 {
472   int status;  /* The return status of the child process */
473 /*
474  * Close the controller end of the terminal.
475  */
476   close(cntrl);
477 /*
478  * Delete the resource object.
479  */
480   gl = del_GetLine(gl);
481 /*
482  * Delete the read buffer.
483  */
484   if(rbuff)
485     free(rbuff);
486 /*
487  * Wait for the user's program to end.
488  */
489   (void) wait(&status);
490 /*
491  * Return either our error status, or the return status of the child
492  * program.
493  */
494   return waserr ? 1 : status;
495 }
496 
497 /*.......................................................................
498  * Run the user's program, with its stdin and stdout connected to the
499  * subsidiary end of the psuedo-terminal.
500  *
501  * Input:
502  *  prog  const char *   The name of this program.
503  *  subsid        int     The file descriptor of the subsidiary end of the
504  *                       pseudo terminal.
505  *  argv        char *[] The argument vector to pass to the user's program,
506  *                       where argv[0] is the name of the user's program,
507  *                       and the last argument is followed by a pointer
508  *                       to NULL.
509  * Output:
510  *  return   int         If this function returns at all, an error must
511  *                       have occured when trying to overlay the process
512  *                       with the user's program. In this case 1 is
513  *                       returned.
514  */
515 static int pty_child(const char *prog, int subsid, char *argv[])
516 {
517   struct termios attr; /* The terminal attributes */
518 /*
519  * We need to stop the pseudo-terminal from echoing everything that we send it.
520  */
521   if(tcgetattr(subsid, &attr)) {
522     fprintf(stderr, "%s: Can't get pseudo-terminal attributes (%s).\n", prog,
523 	    strerror(errno));
524     return 1;
525   };
526   attr.c_lflag &= ~(ECHO);
527   while(tcsetattr(subsid, TCSADRAIN, &attr)) {
528     if(errno != EINTR) {
529       fprintf(stderr, "%s: tcsetattr error: %s\n", prog, strerror(errno));
530       return 1;
531     };
532   };
533 /*
534  * Arrange for stdin, stdout and stderr to be connected to the subsidiary
535  * device, ignoring errors that imply that either stdin or stdout is closed.
536  */
537   while(dup2(subsid, STDIN_FILENO) < 0 && errno==EINTR)
538     ;
539   while(dup2(subsid, STDOUT_FILENO) < 0 && errno==EINTR)
540     ;
541   while(dup2(subsid, STDERR_FILENO) < 0 && errno==EINTR)
542     ;
543 /*
544  * Run the user's program.
545  */
546   if(execvp(argv[0], argv) < 0) {
547     fprintf(stderr, "%s: Unable to execute %s (%s).\n", prog, argv[0],
548 	    strerror(errno));
549     fflush(stderr);
550     _exit(1);
551   };
552   return 0;  /* This should never be reached */
553 }
554 
555 /*.......................................................................
556  * This is the event-handler that is called by gl_get_line() whenever
557  * there is tet waiting to be read from the user's program, via the
558  * controller end of the pseudo-terminal. See libtecla.h for details
559  * about its arguments.
560  */
561 static GL_FD_EVENT_FN(pty_read_from_program)
562 {
563   char *nlptr;   /* A pointer to the last newline in the accumulated string */
564   char *crptr;   /* A pointer to the last '\r' in the accumulated string */
565   char *nextp;   /* A pointer to the next unprocessed character */
566 /*
567  * Get the read buffer in which we are accumulating a line to be
568  * forwarded to stdout.
569  */
570   char *rbuff = (char *) data;
571 /*
572  * New data may arrive while we are processing the current read, and
573  * it is more efficient to display this here than to keep returning to
574  * gl_get_line() and have it display the latest prefix as a prompt,
575  * followed by the current input line, so we loop, delaying a bit at
576  * the end of each iteration to check for more data arriving from
577  * the application, before finally returning to gl_get_line() when
578  * no more input is available.
579  */
580   do {
581 /*
582  * Get the current length of the output string.
583  */
584     int len = strlen(rbuff);
585 /*
586  * Read the text from the program.
587  */
588     int nnew = read(fd, rbuff + len, PTY_MAX_READ - len);
589     if(nnew < 0)
590       return GLFD_ABORT;
591     len += nnew;
592 /*
593  * Nul terminate the accumulated string.
594  */
595     rbuff[len] = '\0';
596 /*
597  * Find the last newline and last carriage return in the buffer, if any.
598  */
599     nlptr = strrchr(rbuff, '\n');
600     crptr = strrchr(rbuff, '\r');
601 /*
602  * We want to output up to just before the last newline or carriage
603  * return. If there are no newlines of carriage returns in the line,
604  * and the buffer is full, then we should output the whole line. In
605  * all cases a new output line will be started after the latest text
606  * has been output. The intention is to leave any incomplete line
607  * in the buffer, for (perhaps temporary) use as the current prompt.
608  */
609     if(nlptr) {
610       nextp = crptr && crptr < nlptr ? crptr : nlptr;
611     } else if(crptr) {
612       nextp = crptr;
613     } else if(len >= PTY_MAX_READ) {
614       nextp = rbuff + len;
615     } else {
616       nextp = NULL;
617     };
618 /*
619  * Do we have any text to output yet?
620  */
621     if(nextp) {
622 /*
623  * If there was already some text in rbuff before this function
624  * was called, then it will have been used as a prompt. Arrange
625  * to rewrite this prefix, plus the new suffix, by moving back to
626  * the start of the line.
627  */
628       if(len > 0)
629 	(void) pty_write_to_fd(STDOUT_FILENO, "\r", 1);
630 /*
631  * Write everything up to the last newline to stdout.
632  */
633       (void) pty_write_to_fd(STDOUT_FILENO, rbuff, nextp - rbuff);
634 /*
635  * Start a new line.
636  */
637       (void) pty_write_to_fd(STDOUT_FILENO, "\r\n", 2);
638 /*
639  * Skip trailing carriage returns and newlines.
640  */
641       while(*nextp=='\n' || *nextp=='\r')
642 	nextp++;
643 /*
644  * Move any unwritten text following the newline, to the start of the
645  * buffer.
646  */
647       memmove(rbuff, nextp, len - (nextp - rbuff) + 1);
648     };
649   } while(pty_manager_readable(fd, PTY_READ_TIMEOUT));
650 /*
651  * Make the incomplete line in the output buffer the current prompt.
652  */
653   gl_replace_prompt(gl, rbuff);
654   return GLFD_REFRESH;
655 }
656 
657 /*.......................................................................
658  * Write a given string to a specified file descriptor.
659  *
660  * Input:
661  *  fd             int     The file descriptor to write to.
662  *  string  const char *   The string to write (of at least 'n' characters).
663  *  n              int     The number of characters to write.
664  * Output:
665  *  return         int     0 - OK.
666  *                         1 - Error.
667  */
668 static int pty_write_to_fd(int fd, const char *string, int n)
669 {
670   int ndone = 0;  /* The number of characters written so far */
671 /*
672  * Do as many writes as are needed to write the whole string.
673  */
674   while(ndone < n) {
675     int nnew = write(fd, string + ndone, n - ndone);
676     if(nnew > 0)
677       ndone += nnew;
678     else if(errno != EINTR)
679       return 1;
680   };
681   return 0;
682 }
683 
684 /*.......................................................................
685  * This is the signal handler that is called when the child process
686  * that is running the user's program exits for any reason. It closes
687  * the subsidiary end of the terminal, so that gl_get_line() in the parent
688  * process sees an end of file.
689  */
690 static void pty_child_exited(int sig)
691 {
692   raise(SIGINT);
693 }
694 
695 /*.......................................................................
696  * Return non-zero after a given amount of time if there is data waiting
697  * to be read from a given file descriptor.
698  *
699  * Input:
700  *  fd        int  The descriptor to watch.
701  *  usec     long  The number of micro-seconds to wait for input to
702  *                 arrive before giving up.
703  * Output:
704  *  return    int  0 - No data is waiting to be read (or select isn't
705  *                     available).
706  *                 1 - Data is waiting to be read.
707  */
708 static int pty_manager_readable(int fd, long usec)
709 {
710 #if HAVE_SELECT
711   fd_set rfds;             /* The set of file descriptors to check */
712   struct timeval timeout;  /* The timeout */
713   FD_ZERO(&rfds);
714   FD_SET(fd, &rfds);
715   timeout.tv_sec = 0;
716   timeout.tv_usec = usec;
717   return select(fd+1, &rfds, NULL, NULL, &timeout) == 1;
718 #else
719   return 0;
720 #endif
721 }
722