[net.unix] Need Non-Blocking Terminal Input Function For Berkeley 4.2

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