longjmp + signal may cause problems

Martin Minow minow at decvax.UUCP
Tue Dec 6 12:35:22 AEST 1983


Several people have suggested in net.unix-wizards that longjmp
could be used together with signal handlers to escape from
loops on, for example, input timeouts.

While this may work in certain cases if you are exceedingly
careful how you write your programs, the asynchronous nature
of signals will probably result in program bugs that have a
very low probability and which are very difficult to locate.

Consider a program such as the following (note -- I don't program
for Unix frequently so please don't nitpick the code):

	#include <stdio.h>
	#include <signal.h>
	#include <setjmp.h>
	jmp_buf	fail;

	main()
	{
	    int		c;
	    extern int	trapper();

	    signal(SIGINT, trapper);
	    if (setjmp(fail) == 0) {
		while ((c = getchar()) != EOF)
		    putchar(c);
	    }
	    else {
		printf("Interrupt termination\n");
	    }
	}

	trapper()
	{
	    longjmp(fail, 1);
	}

Looks simple -- just copy from stdin to stdout and exit on EOF.
If the interrupt signal appears, print a message and exit.

Unfortunately, there is a slight probability that the signal will
occur within the putchar macro.  This will result in the buffer
pointer and buffer count getting out of synchronization with
each other.  The printf() call may subsequently generate pure
garbage.  (The sample program is the simplest I could come up
with to illustrate the problem -- real programs would be somewhat
more complex.)

Because buffered I/O in Unix is not interlocked you cannot use
signal/setjmp in this naive fashion.  It may work in the lab,
but will surely fail in production.

One way to work around this failing that should work in most
instances would be to limit the action of the trap handler:


	#include <stdio.h>
	#include <signal.h>
	int	interrupt = 0;

	main()
	{
	    int		c;
	    extern int	trapper();

	    signal(SIGINT, trapper);
	    while (interrupt == 0 && (c = getchar()) != EOF)
		putchar(c);
	    if (interrupt != 0)
		printf("Interrupt termination\n");
	    }
	}

	trapper()
	{
	    interrupt = 1;
	}

While this won't cause strange crashes, it may not be useful --
SIGINT is usually used to pull programs out of input wait states
and (if I understand the recent discussion of 4.2bsd), a signal
does not terminate pending reads.

This last is a problem for me -- I have an input routine that
runs on Vax native, RSX-11M, and RSTS/E that does timed,
"CBREAK" input that I would like to have running on Unix.
In all three systems, there is a way to have the "trapper" routine
abort any pending I/O, but I haven't found a reasonable solution
for Unix.  (The routine is part of a large software library that
is public-domain and will be distributed by Decus in a month or so.)
The current Unix version of the routine is as follows.  The DECTALK
structure contains a pending input buffer.  The global dt_abort
flag is set by a signal(SIGINT, ...) handler as shown above.
Suggestions for improving this routine -- or making it work on
other versions of Unix would be most appreciated.

#include	<errno.h>
#include	<signal.h>

int
dt_ioget(dt, sec)
register DECTALK	*dt;	/* DECtalk device		*/
int			sec;	/* Wait time, 0 == forever	*/
/*
 * UNIX:  Fill the input buffer, return the next (first) character.
 */
{
	register int	incount;	/* Count and error code	*/
	extern int	errno;

	/*
	 * Return buffered character (if any)
	 */
	if (dt->in_ptr < dt->in_end) 
	    return (*dt->in_ptr++ & 0xFF);
	/*
	 * We must refill the buffer
	 */
	dt->in_ptr = dt->in_end = &dt->in_buff[0];
	dt_ioput(dt, 0);		/* Flush output		*/
	if (dt_abort)
	    return (DT_ERROR);
	/*
	 * Unix Version 7 requires "magical" manipulation
	 * of operating system event signals.
	 */
	signal(SIGALRM, SIG_IGN);	/* Ignore signals	*/
	alarm(sec);			/* Start timeout	*/
	errno = 0;			/* Clear error flag	*/
	incount = read(dt->unit, dt->in_buff, IN_BUFLEN);
	alarm(0);			/* Cancel timeout	*/
	if (errno == EINTR)		/* Did it timeout?	*/
	    return (DT_TIMEOUT);	/* Return failure	*/
	else if (dt_abort || incount <= 0)  /* Other error?	*/
	    return (DT_ERROR);		/* Return bad failure	*/
	dt->in_end = &dt->in_buff[incount];
	return (*dt->in_ptr++ & 0xFF);
}

Sorry about the length of this note.  It is a difficult problem
that needs extensive discussion.

Martin Minow
decvax!minow



More information about the Comp.unix.wizards mailing list