[net.unix] non-blocking read

johnl@proper.UUCP (John A. Limpert ) (02/03/84)

Is there any clean way for a program to determine whether or not the
user has typed a character on the keyboard without blocking the
program?  The only scheme I have been able to come up with is to fork
another process that reads characters from the tty and writes them
into a file.  The main process could then check the size of the file
to determine if there were any new characters to process.  I know
that the kernel I/O drivers have this information, is there any way
of getting it without spying on some magic location in /dev/kmem?

mark@elsie.UUCP (02/06/84)

If you have the Berkeley new tty driver you can do the following:

#include <sgtty.h>

foo()
{
int	nchars = 0;

for (;;) {
	..loop code..

	ioctl(0,FIONREAD,(struct sgttyb *) &nchars);
	if (nchars) {
		/* Something has been typed at the keyboard */
		..anything else..
		}
	}
}

The FIONREAD causes the number of chars waiting at the terminal to be placed
in nchars. NOTE: if you want to process single chars as they come into the 
terminal you must be in RAW or CBREAK mode.

This is not terribly portable (I try to protect such code with
"#ifdef berkeley"'s). I would be interested in knowing if there is a truly
portable way of doing this.

-- 
Mark J. Miller
NIH/NCI/DCE/LEC
UUCP:	decvax!harpo!seismo!rlgvax!cvl!elsie!mark
Phone:	(301) 496-5688

guy@rlgvax.UUCP (Guy Harris) (02/06/84)

The closes thing to a portable non-blocking read is:

#include <fcntl.h>

frobozz(...)
{
	register int i;

	i = fcntl(fdes, F_GETFL, 0);
	fcntl(fdes, F_SETFL, i | O_NDELAY);

	i = read(fdes, buf, count);
#ifdef BSD4_2
	if (i < 0 && errno == EWOULDBLOCK) {
#else
	if (i == 0) {
#endif
		/*
		 * No characters currently available to be read.
		 */
	} else {
		/*
		 * "i" contains the number of characters available to
		 * be read.
		 */
	}

This will work on System III (*if* you fix a bug in the terminal driver),
System V, and 4.2BSD.  *Warning*: the implementation of "fcntl" is 4.2BSD
has a botch, in that the F_SETFL call is *supposed* to set the no-delay
flag on the *file descriptor*, and is *only* supposed to affect *that*
file descriptor (and, on S3/S5/other USG UNIX versions, it does do this);
however, on 4.2BSD, it passes the no-delay flag to the driver which sets
no-delay mode on the terminal port that the file descriptor refers to, so
that *all* file descriptors referring to that terminal port are, in
effect, set to no-delay mode.

	Guy Harris
	{seismo,ihnp4,allegra}!rlgvax!guy

ka@hou3c.UUCP (Kenneth Almquist) (02/06/84)

Various versions of UNIX provide a FIONREAD ioctl call which checks
for pending input and/or an O_NDELAY flag which can be set by fcntl
to provide nonblocking reads.  Check your manual to see whether you
have either of these.
				Kenneth Almquist

geoff@proper.UUCP (Geoff Kuenning) (02/07/84)

A request was made on the net for information on how to do a non-blocking
read from a terminal.  Under 4BSD and UniSoft ports, the FIONREAD 'ioctl'
can be used to check for characters, albeit at a high cost in CPU time.
Other solutions have been posted to the net.

A related question is how to do asynchronous disk I/O for purposes of
double buffering.  System V provides an open/fcntl option for non-blocking
(read asynchronous) I/O, but there is a *VERY* nasty catch:  there is no
way to find out when the I/O is finished!  This makes the non-blocking feature
pretty useless.

Indeed, when I examined the double-buffering code in "volcopy"
to find out how they did it, I discovered that they fork a copy of themselves
and then write "r" and "w" characters across pipes to synchronize the two
copies.  Each copy is responsible for working with one of the two buffers,
and writes a single character to the other copy when the buffer is complete.
YUCCCCCCH!  KLUDGE KLUDGE KLUDGE!

How about it, Bell Labs?  If you are going to provide non-blocking I/O, I
really think you should give us a way to find out when the I/O is complete.
I fully realize the difficulty of integrating such a feature into the Unix
design;  but I still think you should do it the right way.

moss%brl-vld@sri-unix.UUCP (02/07/84)

From:      Gary S Moss ~Software Development Team~ <moss@brl-vld>

The following is a way to do non-blocking reads on System V.  Actually,
I implemented it on Doug Gwyn's System V emulation on top of Berkeley
4.2 so its portable in the sense that System V is a standard of sorts
and with Doug's emulation it travels still further.

Let me mention that it is really frustrating that in order to figure
out how to do this I had to jump around from fcntl(2) to ioctl(2) to
read(2) in the User's Manual - System V and then to termio(7) in the
Administrator's Manual - System V.

#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>

static struct termio	termBuf;	/* Termio buffer.		*/
static unsigned short	localModes;	/* Save local modes.		*/
static int		filStat;	/* Save file status flags.	*/

main() {
	int	c, done;

	(void) ioctl( 0, TCGETA, &termBuf ); /* Get termio parameters.	*/
	localModes = termBuf.c_lflag;	     /* Save local modes.	*/
	termBuf.c_lflag &= ~ICANON;	     /* Raw mode ON.		*/
	termBuf.c_lflag &= ~ECHO;	     /* Echo mode OFF.		*/
	(void) ioctl( 0, TCSETA, &termBuf ); /* Set local modes.	*/

	filStat = fcntl( 0, F_GETFL, 0 );    /* Save file status flags.	*/
	(void) fcntl( 0, F_SETFL, O_NDELAY );/* Set non-blocking read.	*/

	for( done = 0; ! done ; ) {
		if( read( 0, &c, 1 ) )
			done = ! do_something_with( c );
		else
			update_screen();
	}

	termBuf.c_lflag = localModes;	     /* Retrieve old setting.	*/
	(void) ioctl( 0, TCSETA, &termBuf ); /* Reset local modes.	*/
	(void) fcntl( 0, F_SETFL, filStat ); /* Restore file status.	*/
	exit( 0 );
}

- Moss.

54394gt@hocda.UUCP (G.TOMASEVICH) (02/09/84)

OK, Geoff!  Let's hear it for non-blocking disk (and tape) reads for which
you can find out when they are done.  I read that 'volcopy' kludge, too.

moss%brl-vld@sri-unix.UUCP (02/09/84)

From:      Gary S Moss ~Software Development Team~ <moss@brl-vld>

> if the program somehow exits after the "fcntl( 0, F_SETFL, O_NDELAY )",
> you will get spontaneously logged off.

Good point Dave.
When I first implemented the code, I didn't save the file status flags,
and got logged out immediately upon exiting.  Trapping interrupts is
a good idea in any program that does some sort of clean-up, I may have
left other details out in the attempt to be concise.  Opening /dev/tty
sounds like a good idea, I'll give it a try.

One thing I neglected to mention, is that after clearing the ICANON bit
in the termio structure, you will want to mask off the sign bit :

#define CMASK	0377
int	c;	/* Good idea using int if comparing to constants.	*/
	
	if( read( 0, (char *)&c, 1 ) )
		switch( c & CMASK )
		UP: ...
- Moss.

stroyan@hpfcra.UUCP (02/12/84)

You can do non-blocking reads by using fcntl (in reference manual sections
2 and 7) do set the O_NDELAY status flag.  Or you can open a file with
O_NDELAY set if you aren't using stdin.  I should warn you that if your
program dies or absent-mindedly quits with O_NDELAY set for stdin, then
the shell will mistake the return without delay for an EOF and log you off
immediately.

	Mike Stroyan
	Hewlett Packard, Fort Collins Systems Division
	hpfcla!stroyan

edhall%rand-unix@sri-unix.UUCP (02/15/84)

From:  Ed_Hall <edhall@rand-unix>

Dup(0) will give you a new kernel file-structure to play with as
well, but doesn't depend upon the terminal being attached to fd 0.
I usually use /dev/tty either only as a last resort, or when I
specifically want to override any file redirection.

		-Ed Hall
		edhall@rand-unix        (ARPA)
		decvax!randvax!edhall   (UUCP)

dbj%rice@sri-unix.UUCP (02/17/84)

From:  Dave Johnson <dbj@rice>

        "Dup(0) will give you a new kernel file-structure to play with ..."

WRONG!  Dup(0) will only make another reference to the file structure that
already exists for that file descriptor by incrementing the reference count
in f_count and pointing to it by an element of the array u.u_ofile.  It does
NOT allocate a new "struct file" which is where the file's modes such as
O_NDELAY are kept.  Doing a dup(0) will not help the problem at all.  The
only solution (short of doing something gross to the kernel) is to either
re-open the specific terminal by name (e.g., /dev/tty01) or (better yet)
to open /dev/tty to get the new file structure to set into non-blocking
mode.


                                        Dave Johnson
                                        Dept. of Math Science
                                        Rice University
                                        dbj@rice

edhall%rand-unix@sri-unix.UUCP (02/17/84)

From:  Ed_Hall <edhall@rand-unix>

I just looked it up in my 4.2 source and you're absolutely right.
The only thing created separately for a dup()'d file descriptor is
the close-on-exec flag.

I can think of occasions where sharing a single offset pointer across
dup's is desirable behavior, especially in implementing the shell.
But not being able to set flags on a per-descriptor basis is a loss.
Perhaps the FNDELAY flag should behave the same as the close-on-exec
flag.  Any comments on this?

Anyone for a reopen() system call?

		-Ed Hall
		edhall@rand-unix        (ARPA)
		decvax!randvax!edhall   (UUCP)

guy@rlgvax.UUCP (Guy Harris) (02/19/84)

> One thing I neglected to mention, is that after clearing the ICANON bit
> in the termio structure, you will want to mask off the sign bit :

> #define CMASK	0377
> int	c;	/* Good idea using int if comparing to constants.	*/
> 	
> 	if( read( 0, (char *)&c, 1 ) )
> 		switch( c & CMASK )
> 		UP: ...

False.  Turning on the ICANON bit has *no effect* on the width of the terminal-
to-computer path in bits.  That is affected by the various ISTRIP bits in
c_iflag and the character width stuff in c_cflag.  You may be confusing this
with RAW mode, which was the only was in V6 UNIX to get characters as they
were typed and which *did* turn off parity checking and parity-bit-stripping
(at least in the later V6-based UNIXes, like PWB/UNIX).  RAW turned off all
that stuff in V7, but in V7 the way to get characters as they are typed is
to turn on CBREAK which has no effect on output (modulo a small Berkeley UNIX
hack) and which only affects canonicalization on input.  ICANON has the same
intent; disable canonicalization and don't do anything else.

Furthermore, declaring "c" as an "int" is a very *bad* idea.  This code
will only work on a machine like those from the 11 family which store the
bytes of a word from the bottom up, i.e.

	+--------+--------+
	| Byte 1 | Byte 0 |	a word
	+--------+--------+
       MSB               LSB

"(char *)&c" always points to "byte 0" of a word.  In the case of an 11 family-
type machine, this means that filling in whatever "(char *)&c" points to will
fill in the lower bits of the word, so if byte 1 is zero this means that "c"
is the zero-extended value of the byte stuffed into "byte 0" of "c".

However, on a machine like most of the other machines in the world (Motorola
M68000, for instance; also note that the DOT Internet standard byte order is
*not* the 11 family byte order!), the bytes of a word are stored from the top
down, i.e.

	+--------+--------+
	| Byte 0 | Byte 1 |	a word
	+--------+--------+
       MSB               LSB

"(char *)&c" still points to byte 0, but this is now the eight *most*
significant bits of the word.  Storing a value where "(char *)&c" points,
assuming byte 1 is still zero, will now produce an integer value which is
the value of the character times 256!

This bug used to exist in early versions of UUCP, and *did* cause us a problem
when putting that UUCP on a non-11 family machine.  The System III UUCP fixed
it by the simple expedient of declaring the variable to be a "char" rather than
an "int".

(For those of you on machines with 4-byte "int"s, this argument still holds,
the diagrams just look a little different.)

Furthermore, the reason it was being suggested was that "char"s may be sign-
extended when compared with "int"s, so a "char" containing the bit pattern
0377 (all bits on) would be sign-extended so that on a machine with 16-bit
"int"s which sign-extends "char"s it actually would be treated as if it had
the value 0177777.  As such, that "char" would *not* be equal to 0377 in
a comparison.  It *would*, however, be equal to '\377', because that
constant is a "char" and would be sign-extended.  The proper solution, unless
you have an older compiler (like, I believe, the V7 PDP-11 C compiler), is
to declare "c" to be an *unsigned* char.

If what you wanted was a variable which holds a small unsigned number (0 to
255), declare it as an "unsigned char".  If what you wanted was a variable
which holds a small *signed* number (-128 to 127), declare it as a "char"
*but* be forewarned that C does *not* guarantee that "char"s are signed.
On the AT&T Technologies 3B series, "char"s are unsigned, so there is no
way to declare something to be an 8-bit signed integer.  If what you wanted
was a variable which holds an ASCII (7-bit) character, declare it as a
"char" because the sign bit will never be on and the question is moot anyway.

	Guy Harris
	{seismo,ihnp4,allegra}!rlgvax!guy