From: torek@horse.ee.lbl.gov (Chris Torek)
Newsgroups: comp.lang.c,comp.unix.programmer
Subject: Re: trapping "control C"
Keywords: setjmp longjmp signal
Date: 26 Jul 92 01:10:23 GMT
Reply-To: torek@horse.ee.lbl.gov (Chris Torek)
Followup-To: comp.unix.programmer
Organization: Lawrence Berkeley Laboratory, Berkeley
NNTP-Posting-Host: 128.3.112.15

(I am moving this to comp.unix.programmer, since most of this has
nothing to do with the C language itself, but rather with details of
its use under Unix systems.)

In article <1992Jul17.232515.6365@athena.mit.edu> scs@adam.mit.edu
(Steve Summit) notes that
>>This bug (catching SIGINT unilaterally, without first testing if
>>it is being ignored) is rampant, because it is immaterial if you
>>don't have multitasking and background processes, and because
>>even on Unix it (the bug) is not noticed under the C shell.

This is correct: Unix programs should first verify that SIGINT is
*supposed* to be caught or defaulted before `taking it over'.  This
applies also to SIGHUP and SIGQUIT (these three are the only `old
shell' compatible `tty generated' signals).

In article <1992Jul20.235307.23706@seas.smu.edu> mustafa@seas.smu.edu
(Mustafa Kocaturk) writes:
>I have tried to write the following `usleep' function by using
>the SIGALRM signal and some library functions. It works even when it is
>run in the background ...

Since SIGALRM is not a `tty signal' it can be caught or defaulted
without first testing its disposition, provided you control the rest
of the source.  (A library function should be more careful.)

(Steve Summit again:)
>>Signal handling is really one of the cases where portability and
>>pragmatism collide head-on. ...

Indeed.

>In my humble opinion, this lack of guarantee should not prevent some
>questioning programmers to use longjmp inside a signal handler function.

It should at the least discourage casual usage of signal functions.
Writing correct signal functions is difficult (as the example below
will illustrate).

>After all, most of the uses for a `longjmp', in my opinion, are for
>returning from signal handlers and low-level interrupt routines.

On the contrary, longjmp is most likely to be used portably to `escape'
from several levels of function call.  For instance, a program that
writes to files (using stdio) may use longjmp to `escape out' of an
inner output-routine all the way back to setup code that can erase a
temporary file and abort, thus recovering from, or at least failing
gracefully under, an overfull disk.

>The object `sigcontext' contains largely system-dependent data that
>the signal handler can, if necessary, examine and modify before it
>returns or terminates.

In fact, *having* a `sigcontext' structure is in violation of ANSI C.
A signal handler must be called with exactly one `int' argument, that
being the signal number.  (Unix systems can handle this safely simply
by not delivering sigcontexts to *signal* functions, but sending them
on to *sigaction* functions.  A small `wrapper' function in the C
library will handle this, at some cost in efficiency.  Currently,
though, most Unix systems simply live dangerously and pass `extra'
parameters to signal functions, hoping that this causes no trouble.)

>#include <stdio.h>
>#include <signal.h>
>#include <sys/time.h>
>#include <setjmp.h>
>
>jmp_buf env;
>
>int inthand(int sig, int code, struct sigcontext *scp)
> {
> longjmp(env,1);
> }
>
>int usleep(int usecs)
> {
> struct itimerval value, ovalue;
> void (*prevh)();
> value.it_interval.tv_sec=value.it_interval.tv_usec=0;
> value.it_value.tv_sec=usecs/1000000;
> value.it_value.tv_usec=usecs%1000000;
> setitimer(ITIMER_REAL,&value,&ovalue);
> if(setjmp(env)!=0)
>  {
>  signal(SIGALRM,prevh);
>  return 0;
>  }
> prevh=signal(SIGALRM,inthand);
> sigpause(0);
> }

There are at least two bugs in this code.  First (and somewhat
irrelevant due to Unix systems `living dangerously' as noted above),
inthand() should take only its `int sig' argument, or the code
should use sigaction() to set up the catching function.

The most important bug here is a race condition.  This may cause the
program to fail `mysteriously'.  In particular, if the system is
heavily loaded, and/or the time passed to usleep() is sufficiently
short, the setitimer call may succeed and then the alarm may occur
before the setjmp() or the textually-second signal() call finishes.  In
this case, the SIGALRM will be handled according to the previous
disposition.  If the signal was previously ignored, the program will
then hang at the sigpause() until some other signal occurs.  If the
signal was previously caught, the old catcher will run (what effect
this has obviously depends on that function).  If the signal was
previously defaulted, the program will terminate.

Even if we do get far enough to catch the signal, the sigpause() may
finish due to some *other* caught signal.  The sigpause(), or in POSIX,
sigsuspend(), waits until a signal occurs *and then returns*.  (It may
`grab' more than one signal if several pile up `all at once', but as
soon as it gets one it can proceed.)  In this case, the alarm may
eventually occur *after usleep has returned*, and the longjmp() will
then jump to a nonexistent or overwritten function activation.

(All of the above is lumped under one `race condition' bug.)

As side bugs, the routine restores the previous SIGALRM handler but
does not restore any previous interval timer, and it ignores possible
error returns from setitimer and signal.  Restoring previous intervals
is difficult and probably not worthwhile, as is attempting to handle
the case where an existing value would cause the alarm to fire *before*
the time to usleep is up.

The usleep() function *can* be implemented using something like the
code above, provided that:

	A. You set and arrange to catch SIGALRM `atomically';
	B. You loop calling sigpause().

Provision A can be handled with sigblock and sigsetmask or their POSIX
equivalents (sigprocmask with SIG_BLOCK and SIG_UNBLOCK or
SIG_SETMASK).  Once you combine this with B, however, you can drop the
setjmp and longjmp and simply test a variable, looping until it is set
or cleared by your alarm handler.  Thus:

static volatile sig_atomic_t caught;

static void
catch(int sig)
{

	caught = 1;
}

void
usleep(unsigned usecs)
{
	struct itimerval value;
	sigset_t mask, omask, pausemask;
	void (*prevh)(int);

	value.it_interval.tv_sec = value.it_interval.tv_usec = 0;
	value.it_value.tv_sec = usecs / 1000000;
	value.it_value.tv_usec = usecs % 1000000;
	(void) sigemptyset(&mask);
	(void) sigaddset(&mask, SIGALRM);
	(void) sigprocmask(SIG_BLOCK, &mask, &omask);
	pausemask = omask;	/* in case SIGALRM was already blocked... */
	(void) sigdelset(&pausemask, SIGALRM);
	(void) setitimer(ITIMER_REAL, &value, (struct itimerval *)NULL);
	prevh = signal(SIGALRM, catch);
	while (!caught)
		sigsuspend(&pausemask);
	(void) signal(SIGALRM, prevh);
	(void) sigprocmask(SIG_SETMASK, &omask, (sigmask_t *)NULL);
}

This still has plenty of room for improvement, but at least the races
are gone and the types all match up with various standards.
-- 
In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 510 486 5427)
Berkeley, CA		Domain:	torek@ee.lbl.gov