cramer@kontron.UUCP (Clayton Cramer) (01/22/86)
Does anyone know of a way to do read from a terminal under Berkeley 4.2 UNIX so that after a specified number of seconds control is returned to the program if there is no response? Right now I'm forking off of the main program, and having the child process do the read, with the parent setting an alarm to interrupt the child after a certain number of seconds. This works, but it's devilishly difficult to debug using dbx. Alternate techniques would be appreciated. Please don't suggest using alarm within the same process to interrupt a read -- to quote SIGNAL(3C): If a caught signal occurs during certain system calls, causing the call to terminate prematurely, the call is automatically restarted. In particular, this can occur during a _read_ or _write_(2) on a slow device (such as a terminal; but not a file) and during a _wait_(2). In short, the one time it would be most useful!
tom@puff.wisc.edu (Thomas Scott Christiansen) (01/24/86)
> Does anyone know of a way to do read from a terminal under Berkeley 4.2 UNIX > so that after a specified number of seconds control is returned to the > program if there is no response? Right now I'm forking off of the main > program, and having the child process do the read, with the parent setting > an alarm to interrupt the child after a certain number of seconds. This > works, but it's devilishly difficult to debug using dbx. Alternate > techniques would be appreciated. Yes the read will restart, *IF* you return to it. The trick is not to return to it. You should use setjmp() to remember where you were. The following module should do what you want. It contains the function readt() which works just as read does except that it accepts a fourth argument indicating the number of seconds at which a timeout is to be triggered. If this does occur, errno will contain EINTR. A sample main() is included to test the function. I have tested it on a Gould under 4.2, a Pyramid under 4.2/SYSV, and a Vax 780 under 4.3. ----------------------------------------------------------------------- #include <setjmp.h> #include <signal.h> #include <errno.h> #define reg register #define global #define local static #define ERROR (-1) local jmp_buf Context; local int timetrap(); extern int alarm(); extern int errno; /* * readt(): a read function that accepts a * fourth parm indicating the number of seconds * at which to trigger the timeout. other parms are * the same as read. * * returns number of bytes read or -1 on error. * a timeout causes errno to be set to EINTR. */ global int readt ( channel, buffer, count, timeout ) reg int channel, count; reg unsigned timeout; reg char *buffer; { reg int retval; reg int (*alrm_vec)(); alrm_vec = signal (SIGALRM, timetrap); (void) alarm ( timeout ); retval = setjmp (Context) ? (errno = EINTR, ERROR) : read ( channel, buffer, count ); (void) alarm ( 0 ); (void) signal (SIGALRM, alrm_vec); return retval; } local int timetrap() { longjmp ( Context, 1 ); } #include <stdio.h> #define TIMEOUT 3 main() { char line[100]; printf ("string (no timeout): "); fflush (stdout); if ( read (fileno(stdin), line, sizeof(line)) < 0) perror("read"); else printf ( "The string was %s",line); printf ("string (timeout == %d): ",TIMEOUT); fflush (stdout); if ( readt (fileno(stdin), line, sizeof(line), TIMEOUT) < 0) perror("readt"); else printf ( "The string was %s",line); } /* lint output: /usr/staff/tom/src/readt.c: fflush returns value which is always ignored */
mo@wgivax.UUCP (01/24/86)
If you are willing to write a simple input manager, you could use getch() with alarm() and longjmp/setjmp() to manage your problem. For instance: #include <stdio.h> #include <setjmp.h> #include <signal.h> jmp_buf xyz; main() { int trapalarm(); char string[80]; do { fprintf(stderr,"Please enter string: "); signal(SIGALRM, trapalarm); alarm(5); if(setjmp(xyz) == 0) { get_input(string); fprintf(stderr,"%s",string); } else { fprintf(stderr,"no input\n"); continue; } } while(strcmp(string,"end")); } get_input(str) char *str; { register char *c; c = str; do { *c = getchar(); alarm(0); } while(*c++ != '\n'); *c = 0; } trapalarm() { alarm(0); longjmp(xyz,1); } =============================================================================== Mike O'Shea (decvax!mcnc!unccvax!wgivax!mo)
ji@garfield.columbia.edu (John Ioannidis) (01/26/86)
In article <482@kontron.UUCP>, cramer@kontron.UUCP (Clayton Cramer) writes: > Does anyone know of a way to do read from a terminal under Berkeley 4.2 UNIX > so that after a specified number of seconds control is returned to the > program if there is no response? Right now I'm forking off of the main > program, and having the child process do the read, with the parent setting > an alarm to interrupt the child after a certain number of seconds. This > works, but it's devilishly difficult to debug using dbx. Alternate > techniques would be appreciated. > The idea is to use select(2) to do the waiting for you. The following program should be obvious. See the documentation for select(2) for more details. #include <stdio.h> #include <sgtty.h> #define EVER ;; main() { int rfds, sret, c; struct timeval { long tv_sec; long tv_usec; } tout; struct sgttyb b; /* * Prepare tout structure, as required by select(2) */ printf( "Enter time to wait, in seconds: " ); scanf( "%d", &tout.tv_sec ); tout.tv_usec = 0; /* * Some cosmetics... */ ioctl( 0, TIOCGETP, &b ); b.sg_flags |= CBREAK; /* Turn on CBreak mode */ b.sg_flags &= ~ ECHO; /* Turn off echoing */ ioctl( 0, TIOCSETP, &b ); setbuf( stdout, NULL ); /* Used to avoid fflush(stdout) later */ for(EVER) { printf( "Press any key to continue..." ); rfds = 1 << 0; /* really 1 but shown for clarity */ if( ( sret=select( 20, &rfds, 0, 0, &tout ) ) > 0 ) { /* something was pressed */ c=getchar(); printf( "\tYou pressed %c\r\n\n", c ); } else if( sret==0 ) printf( "\t*TIMEOUT WARNING*\r\n\n" ); else perror( "\r\nselect" ); } } The code should be obvious :-) If you specify 0 seconds, select will timeout immediately. To 'block' if no input is available, specify (char *)0 instead of &tout in the select call. This is more elegant than using setjmp/sigalrm and far more elegant than spawning another process just to read! I hope this settles the question, #include <appropriate_disclaimers> VOICE: +1 212 280 5510 ARPA: ioannidis@cs.columbia.EDU USnail: John Ioannidis ji@garfield.columbia.EDU 450 Computer Science Columbia University, USENET: ...{seismo|topaz}! New York, NY 10027 columbia!garfield!ji ... It's all Greek to me!
steve@umcp-cs.UUCP (Steve D. Miller) (01/26/86)
In article <482@kontron.UUCP> cramer@kontron.UUCP writes: >Does anyone know of a way to do read from a terminal under Berkeley 4.2 UNIX >so that after a specified number of seconds control is returned to the >program if there is no response? ... Methinks that what you want to use is select(2). Something similar to what I think you want to do would probably look like: #include <sys/time.h> /* for struct timeval */ ... struct timeval timeout; int s, readfds, nfound; /* s = fd we want to look at */ ... timeout.tv_sec = <number of secs to wait>; timeout.tv_usec = <number of microseconds to wait in addition to above> readfds = 1 << s; /* form bit mask */ /* * This will time out after the period specified in timeout * (and return zero in &readfds), or return before that with * nfound == 1 and (1 << s) set in readfds). See the man entry * for more details; select() falls into the category of * "really massively useful system calls that you want to * know about". */ nfound = select(32, &readfds, (int *) 0, (int *) 0, &timeout); if (nfound < 0) { /* error */ } if (nfound > 0) { /* got something */ read(s, buf, <whatever you want>); ... } else { /* whatever happens when you don't get input */ } In general, select() will let you multiplex input over a number of file descriptors in which you are interested in reading from, writing to, or knowing about "exceptional conditions" (means nothing in 4.2, though I could be wrong, but means at least "out-of-band data" in 4.3) on. If you're select()ing on more than one descriptor, then you want to check the returned bit masks (i.e., "if (readfds & (1 << s)) { .. }) for each descriptor you're interested in; the above example is simplified since it deals with only one descriptor. Note (for those of you who are interested) that all this changes in 4.3BSD. The above code will still work, but is no longer correct. Since there are more than 32 fds available, a more general scheme has been devised, using struct fd_set's (which are really just arrays of integer bit masks, though you don't need to know that; they're defined in <sys/types.h>, and can also be referred to as fd_sets, since they're typedef'ed). Four macros are defined for use in manipulating fd_sets: FD_SET(n, p), which adds fd n to the fd_set pointed to by p; FD_CLR(n, p), which removes fd n from the fd_set pointed to by p; FD_ISSET(n, p), which returns an indication of whether or not fd n is a member of the fd_set pointed to by p; and FD_ZERO(p), which clears the fd_set pointed to by p. Therefore, the code above would, under 4.3BSD, look like: #include <sys/types.h> #include <sys/time.h> /* for struct timeval */ ... struct timeval timeout; int s, nfound; /* s = fd we want to look at */ fd_set readfds; ... timeout.tv_sec = <number of secs to wait>; timeout.tv_usec = <number of microseconds to wait in addition to above> FD_ZERO(&readfds); FD_SET(s, &readfds); /* * This will time out after the period specified in timeout * (and return readfds with no bits set), or return before that with * nfound == 1 and FD_ISSET(s, &readfds) == true. See the man entry * for more details; select() falls into the category of * "really massively useful system calls that you want to * know about". */ nfound = select(32, &readfds, (fd_set *) 0, (fd_set *) 0, &timeout); if (nfound < 0) { /* error */ } if (nfound > 0) { /* got something */ read(s, buf, <whatever you want>); ... } else { /* whatever happens when you don't get input */ } Again, if we were select()ing on more than one descriptor, we'd want to check the status of the returned fd_set with FD_ISSET, since the number of fds available for reading (as indicated by nfound) would in that case not indicate that we could read off of any given fd. I hope this is of some use (to you or to someone else)... -Steve
mouse@mcgill-vision.UUCP (der Mouse) (01/28/86)
I missed the original, but what's wrong with using the non-blocking mode already provided by the kernel? int on = 1; ioctl(fd,FIONBIO,&on); then if you read() and there are no data available, the read will return with either EOF or error (return value <= 0) and errno will be EWOULDBLOCK. -- der Mouse USA: {ihnp4,decvax,akgua,etc}!utcsri!mcgill-vision!mouse philabs!micomvax!musocs!mcgill-vision!mouse Europe: mcvax!decvax!utcsri!mcgill-vision!mouse mcvax!seismo!cmcl2!philabs!micomvax!musocs!mcgill-vision!mouse Hacker: One who accidentally destroys / Wizard: One who recovers it afterward