[comp.unix.questions] Timed reading in C

pepper@dasys1.UUCP (Angelique Wahlstedt) (02/08/88)

I'm working on a project in the C language, and want to ask the net
readers out there a question:

What I want my program to do is wait a short while (like, say, 5 seconds
or so) for an user to type in something. If the user doesn't type in
anything after that time period, the program goes ahead and does something
else. But if the user has already started to type, the program waits and
lets him finish typing.

What I'd like to know is how to do this in C. (By the way, I'm on BSD
Unix 4.3.) I want the user's terminal to stay in the canonical mode,
if possible.

Post replies here, or send replies to my accounts listed below.

Pepperfully yours,
   Angeli "Ms. Pepper" Wahlstedt


Internet: wahlsted@handel.colostate.edu (I prefer this the most)
          ms_pepper@cup.portal.com

UUCP: ...!hao!handel!wahlsted -- or --
      ...!hplabs!hpfcla!handel!wahlsted

Compuserve: 70206,321

chris@trantor.umd.edu (Chris Torek) (02/09/88)

In article <2890@dasys1.UUCP> pepper@dasys1.UUCP (Angelique Wahlstedt) writes:
>What I want my program to do is wait a short while (like, say, 5 seconds
>or so) for an user to type in something. If the user doesn't type in
>anything after that time period, the program goes ahead and does something
>else. But if the user has already started to type, the program waits and
>lets him finish typing.
>
>What I'd like to know is how to do this in C. (By the way, I'm on BSD
>Unix 4.3.) I want the user's terminal to stay in the canonical mode,
>if possible.

You will have to use CBREAK mode.  Once you have given in that far,
the rest is easy; use select() or alarm()+longjmp() (the former preferred).

If the typist is slow enough and/or the system fast enough, you could
get just the first character, then take the terminal out of CBREAK
and use TIOCSTI to put that character back in the input queue; but
if the user manages to type something else, the character put with
TIOCSTI will insert after that something else.  (UCBMail does this
when editing header lines; I often find myself with a partial Cc:
header because of this.)
In-Real-Life: Chris Torek, Univ of MD Computer Science, +1 301 454 7163
(hiding out on trantor.umd.edu until mimsy is reassembled in its new home)
Domain: chris@mimsy.umd.edu		Path: not easily reachable

ip@ecf.toronto.edu (Bevis Ip) (02/13/88)

>What I want my program to do is wait a short while (like, say, 5 seconds
>or so) for an user to type in something. If the user doesn't type in
>anything after that time period, the program goes ahead and does something
>else. But if the user has already started to type, the program waits and
>lets him finish typing.
>
>What I'd like to know is how to do this in C. (By the way, I'm on BSD
>Unix 4.3.) I want the user's terminal to stay in the canonical mode,
>if possible.

Took me a while to crank the following program up, seems like it's doing
what you've asked. There're probably better ways in doing this, and I'm
open for criticism. The code is pretty straight forward (I hope), so
comments are minimal.

bevis

====================
/*
 *  Written by:	 Bevis Ip		UofT	 88/02/12
 *
 *  Known problem:
 *	There's no input editing.
 *	This works on BSD4.[23] systems only, not sure about other systems.
 */
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <sgtty.h>
#include <setjmp.h>

jmp_buf context;

main()
{
	struct sgttyb tty, ntty;
	struct sigvec sv1, sv2;
	int doinput(), dosomething();

	fcntl(0, F_SETFL, FASYNC);

	ioctl(fileno(stdin), TIOCGETP, &tty);
	ntty = tty;
	ntty.sg_flags |= CBREAK;

	sv1.sv_handler = doinput;
	sv2.sv_handler = dosomething;
	sv1.sv_mask = sv2.sv_mask = 0;
	sv1.sv_flags = sv2.sv_flags = 0;	/* sv_onstack for BSD4.2 */

    	setjmp(context);
	sigvec(SIGIO, &sv1, (struct sigvec *) 0);
	sigvec(SIGALRM, &sv2, (struct sigvec *) 0);
	alarm(5);	/* timeout for reading input */
	puts("Waiting for input: ");
	ioctl(fileno(stdin), TIOCSETN, &ntty);
	getchar();	/* wait for signal, never get executed */
	_exit(1);
}

dosomething()
{
	sigblock(sigmask(SIGIO));
	printf("\nI'm getting impatience! ");
	printf("Please type in something...\n\n");
	fflush(stdin);
	longjmp(context, 1);
}

doinput()
{
	char s[BUFSIZ];
	register char *cp = s;
	struct sigvec sv;

	alarm(0);
	sigblock(sigmask(SIGIO));

	/*	Have to read input char by char!!
	 */
	while (read(fileno(stdin), cp, 1) != EOF && *cp != '\n')
		cp++;
	*cp = '\0';
	puts(s);

	sv.sv_handler = SIG_IGN;
	sv.sv_mask = 0;
	sv.sv_flags = 0;
	sigvec(SIGIO, &sv, (struct sigvec *) 0);
	longjmp(context, 1);
}
-- 
Bevis Ip	Department of Mechanical Engineering, University of Toronto
UUCP  :	{linus,ihnp4,allegra,decvax,floyd}!utcsri!me!ip
ARPA  :	ip%me.toronto.edu@csnet-relay.arpa
BitNet:	ip@me.UTORONTO			CSNet :	ip@me.toronto.edu

larry@hcr.UUCP (Larry Philps) (02/17/88)

In article <469@mv02.ecf.toronto.edu> ip@mv02.ecf.UUCP (Bevis Ip) writes:
>>What I want my program to do is wait a short while (like, say, 5 seconds
>>or so) for an user to type in something. If the user doesn't type in
>>anything after that time period, the program goes ahead and does something
>>else. But if the user has already started to type, the program waits and
>>lets him finish typing.
>>
>>What I'd like to know is how to do this in C. (By the way, I'm on BSD
>>Unix 4.3.) I want the user's terminal to stay in the canonical mode,
>>if possible.
>
>Took me a while to crank the following program up, seems like it's doing
>what you've asked. There're probably better ways in doing this, and I'm
>open for criticism. The code is pretty straight forward (I hope), so
>comments are minimal.
>
>bevis
>
>  Program using signals, setjmp, longjmp ...

This can be done a little easier.  The following two incomplete programs
should show you how.  Since I did not write complete programs, I have
obviously not tested them, but you should get the idea.

/*
 * Solution 1: Use select to wait for a specified period for input.
 * When select returns, either the timeout has expired, or there is
 * data to read.  React appropriately.
 */

	int		fd, nbytes, nfound;
	char		buf[BUFFER_SIZE];
	fdset		readfds;
	struct	timeval	timeout;

	/*
	 * either this if reading standard input, or
	 * open the appropriate file and get a file
	 * descriptor for it.
	 */
	fd = 0;

	FD_ZERO(&readfds);
	FD_SET(fd, &readfds);
	timeout.tv_sec = SLEEP_TIME;
	timeout.tv_usec = 0;

	nbytes = 1;
	while (nbytes != 0) { /* End of File test */
		nfound = select (1, &readfds, (int *)0, (int *)0, &timeout);
		if (nfound < 0) {
			perror("select");
			exit(1);
		}
		if (nfound == 0) {
			/*
			 * The timeout expired.  There is no data to read
			 * so do what needs to be done in this case.
			 */
			.....
			continue;
		}

		/*
		 * Select did not return an error, and the timeout did not
		 * expire.  There must be data to read on the only file
		 * descriptor we told it to watch.
		 */
		ASSERT(FD_ISSET(fd, &readfds));
		nbytes = read(fd, buf, sizeof(buf));
		/*
		 * We have the data.  Do something.
		 */
		...
	}

/*
 * Solution 2: Use non-blocking I/O so that the reads never hang.
 * This has the disadvantage that if there is no data to read, then
 * you sleep before trying again, thus creating a possible lag time
 * between the time that the user types his input and the program sees
 * it.  The advantage is that it does not use select (an advantage you say!)
 * and thus should even work on System V.
 */

	int	fd, nbytes;
	char	buf[BUFFER_SIZE];

	/*
	 * either this if reading standard input, or
	 * open the appropriate file and get a file
	 * descriptor for it.
	 */
	fd = 0;

	if (fcntl (fd, F_SETFL, FNDELAY) < 0) {
		perror("fcntl: F_SETFL");
		exit(1);
	}

	/*
	 * Give the user a chance to type something first.
	 * This is grotty.
	 */
	sleep(SLEEP_TIME);

	/*
	 * Now non-blocking I/O is enabled on the file descriptor.
	 * Reads will not hang, so try one and if there is no data
	 * just sleep for a bit, then try again.
	 */
	while ((nbytes = read(fd, buf, sizeof(buf)) != 0) {
		if (nbytes < 0 && errno == EWOULDBLOCK) {
			/*
			 * No data for us now, do what needs to be done
			 * when the user has entered no input, then
			 * try again later.
			 */
			...
			sleep(SLEEP_TIME);
			continue;
		}
		/*
		 * We have the data.  Do something.
		 */
		...
	}
----
Larry Philps                             HCR Corporation
130 Bloor St. West, 10th floor           Toronto, Ontario.  M5S 1N5
(416) 922-1937                           {utzoo,utcsri,decvax,ihnp4}!hcr!larry