#ifndef lint
static const char rccsid[] =
	"$Id: longjmp.c,v 1.2 2001/12/02 08:25:26 andrew Exp $";
#endif

/*
 * This is an example of timing out blocking system calls. The particular call
 * here is a read on stdin...it waits for TIMEOUT seconds for data before
 * giving up.
 *
 * Andrew <andrew@ugh.net.au>
 */

#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>


/*
 * compile time settings
 */
/* maximum amount of data to read */
#define MAXLENGTH	1024
/* seconds to wait for read to return */
#define TIMEOUT	10

/*
 * prototypes
 */
void handle_sigalrm(int signal_number);
int timed_read(int fd, char *const buffer, size_t size, unsigned int timeout, ssize_t *bytes_read);

/*
 * globals
 */
/* this has to be global(ish) so it is accessible from the signal handler */
static sigjmp_buf timeout_env;


int main(void) {
	char buffer[MAXLENGTH];
	unsigned int bytes_read;

	/* display prompt */
	printf("I'm now going to wait for %d seconds for you to type something in...\n", TIMEOUT);

	/* call read */
	if (! timed_read(STDIN_FILENO, buffer, sizeof(buffer), TIMEOUT, &bytes_read)) {
		/* read failed so we see if it timed out */
		if (errno == ETIMEDOUT) {
			printf("Too slow...try again next time.\n");
		} else {
			err(1, "timed_read failed (%s:%d)", __FILE__, __LINE__);
		}
	} else {
		/* read succeeded but we have to test for EOF */
		if (bytes_read == 0) {
			printf("Received EOF.\n");
		} else {
			/* we have a successful read but we have to '\0' terminate for
			 * printf. we actually place the '\0' one spot early so as to
			 * overwrite the trailing '\n'
			 */
			buffer[bytes_read - 1] = '\0';
			printf("You typed \"%s\".\n", buffer);
		}
	}

	return 0;
}

/*
 * try to read(2) "size" bytes from "fd" within "timeout" seconds
 *
 * returns true on success, "bytes_read" will contain the number of bytes
 * read. N.B. this might be 0 if we are at EOF
 *
 * returns false on failure and errno will be set to indicate the error.
 * errno may take on all the values described in read(2), signal(3) plus
 * ETIMEDOUT to indicate "timeout" seconds elapsed before read returned.
 *
 * this function uses SIGALRM without cooperating with any other code that
 * might be doing the same.
 *
 */
int timed_read(int fd, char *const buffer, size_t size, unsigned int timeout, ssize_t *bytes_read) {
	int i;

	/* initialise jump buffer */
	if ((i = sigsetjmp(timeout_env, 0)) != 0) {
		/* we are returning from a siglongjmp */
		errno = ETIMEDOUT;
		return 0;
	}

	/* set up signal handler - must be done after jump buffer initialisation
	 * so there is no possibilty of jumping to an unitialised place */
	if (signal(SIGALRM, handle_sigalrm) == SIG_ERR) {
		return 0;
	}

	/* request SIGALRM to be delivered */
	(void)alarm(timeout);

	/* do the blocking read */
	*bytes_read = read(fd, buffer, size);

	/* cancel the alarm call */
	(void)alarm(0);

	/* check the success or otherwise of read */
	if (*bytes_read == -1) {
		/* read failed */
		return 0;
	} else {
		/* read suceeded */
		return 1;
	}
}

/*
 * handler to the SIGALRM signal
 *
 * long jumps to timeout_env
 */
void handle_sigalrm(int signal_number) {

	/* this causes sigsetjmp to return signal_number */
	siglongjmp(timeout_env, signal_number);
}
