[comp.std.c] how to exit from a signal routine

mark@cbnews.ATT.COM (Mark Horton) (07/06/89)

Suppose you're writing an application, like ed or vi, that runs
interactively at the terminal, but when the user hits DEL, no
matter what is going on, it aborts and comes back for another
user command.  Suppose this program has to be very portable; it
must run on System V, Berkeley, ANSI, POSIX, and V7.

In the old days, you would catch SIGINT and have the signal
handler do a longjmp back to the main loop.  Another reasonable
approach was to have the signal handler just return, and the
read from the tty that was probably interrupted could check to
see if read exited with EINTR, and if so print another prompt
and go back for another command.

Then Berkeley 4.2BSD changed how signal worked, and if the handler
returned, the read would just resume.  It became accepted wisdom
among authors of highly portable programs that the longjmp approach
was the most portable method.  (Checking, this is true of 4.2BSD
and 4.3BSD, but SunOS 4.0 does it the System V way.)

Now ANSI C has decreed that you can't call longjmp, or any other
function except signal, from inside a signal handler for any signal
except those raised by abort, raise, or SIGFPE.  The reason given
for this is that you might be in the middle of calling that function
already when the interrupt comes in, and C library routine are not
required to be reentrant.  You can't call longjmp to get out of a
signal handler because maybe the code was in the middle of calling
longjmp when the signal came in.  Obscure, but they're right.  (On
the other hand, I can't see the harm in reentering longjmp, you'll
never get back to the first call.)

So where does this leave the developer?  ANSI C says the only way
to exit from a SIGINT handler is to return.  The routine is supposed
to pick up where it left off, ala 4.2BSD (although the System V
behavior of having the read return EINTR seems to be allowed too.)
Even if you pretend 4.*BSD doesn't exist, you can imagine the
complexity of checking for EINTR after every terminal read, if
you call handy stdio routines like getchar, scanf, and fgets.  (Nobody
calls gets anymore, right? :-)

Has anybody found a portable solution to this problem?  Is there
any conceivable implementation of longjmp that will have problems
when it gets called 1 1/2 times?

There is a similar problem with trying to call exit from a signal
handler.  exit flushes all stdio buffers, and you might have been
in the middle of an I/O operation when the signal came in.  This
could cause a core dump in exit!

Is this something that should be fixed in the ANSI C standard?

	Mark Horton

blarson@basil.usc.edu (bob larson) (07/07/89)

In article <7997@cbnews.ATT.COM> mark@cbnews.ATT.COM (Mark Horton) writes:
>Is there
>any conceivable implementation of longjmp that will have problems
>when it gets called 1 1/2 times?

On the Prime 50 series, longjump includes backtracking through the
stack for cleanup$ condition handlers (none will be present in
portable C) and for restoring registers.  I don't think longjump from
within longjump would cause problems unless your cleanup$ condition
handler was non-reentrant, but I wouldn't guarentee this.

Also, you would have to guarentee your second longjump call would have
to be to the same or an earlier level, or you could have a real mess.

-- 
Bob Larson	Arpa:	blarson@basil.usc.edu
Uucp: {uunet,cit-vax}!usc!basil!blarson
Prime mailing list:	info-prime-request%ais1@ecla.usc.edu
			usc!ais1!info-prime-request

strouckn@nvpna1.prl.philips.nl (Louis Stroucken 42720) (07/07/89)

In article <7997@cbnews.ATT.COM> mark@cbnews.ATT.COM (Mark Horton) writes:

[example on the handling of interrupts deleted]

>Then Berkeley 4.2BSD changed how signal worked, and if the handler
>returned, the read would just resume.  It became accepted wisdom
>among authors of highly portable programs that the longjmp approach
>was the most portable method.  (Checking, this is true of 4.2BSD
>and 4.3BSD, but SunOS 4.0 does it the System V way.)
>
>Now ANSI C has decreed that you can't call longjmp, or any other
>function except signal, from inside a signal handler for any signal
>except those raised by abort, raise, or SIGFPE.

Sounds like my programs will not be as portable as I would like them to
be :(.

Can anybody quote the standard's exact wording on this issue?
Especially, will the following trick be legal?

	jmp_buf env;

	void handle_by_jump (int sig) {
		longjmp (env, sig);
	}

	void handle_by_raise (int sig) {
		(void) signal (sig, handle_by_jump);
		raise (sig);
	}


>  The reason given
>for this is that you might be in the middle of calling that function
>already when the interrupt comes in, and C library routine are not
>required to be reentrant.  You can't call longjmp to get out of a
>signal handler because maybe the code was in the middle of calling
>longjmp when the signal came in.  Obscure, but they're right.  (On
>the other hand, I can't see the harm in reentering longjmp, you'll
>never get back to the first call.)

Sounds like above trick wont work...

>So where does this leave the developer?  ANSI C says the only way
>to exit from a SIGINT handler is to return.  The routine is supposed
>to pick up where it left off, ala 4.2BSD (although the System V
>behavior of having the read return EINTR seems to be allowed too.)
>Even if you pretend 4.*BSD doesn't exist, you can imagine the
>complexity of checking for EINTR after every terminal read, if
>you call handy stdio routines like getchar, scanf, and fgets.  (Nobody
>calls gets anymore, right? :-)

Yeah, where does this leave the developer? In our case, we are
developing an application consisting of several cooperating processes.
We use handle_by_jump type signal handlers to ensure that a process will
not get into serious trouble without at least notifying its partners
it's going to die. What other approaches are available?


> Mark Horton

Louis Stroucken
strouckn@nvpna1.prl.philips.nl

gwyn@smoke.BRL.MIL (Doug Gwyn) (07/07/89)

In article <7997@cbnews.ATT.COM> mark@cbnews.ATT.COM (Mark Horton) writes:
>Is this something that should be fixed in the ANSI C standard?

No -- The Standard doesn't create the problem, it merely documents it.

There is no prohibition against C implementations doing a better job
than the minimum guaranteed by the C Standard.  In fact the C Standard
guarantees very little about signal behavior, because of the wide
variety of signal() implementations in existence.  Remember that the
C Standard is not UNIX specific, but rather applies to all operating
systems.

IEEE Std 1003.1 (POSIX) provides specifications for a much more
reliable signal mechanism.  Presumably UNIX-like implementations will
provide the extended POSIX signal support rather than merely a minimal
ANSI C facility for signals.

dfp@cbnewsl.ATT.COM (david.f.prosser) (07/07/89)

In article <7997@cbnews.ATT.COM> mark@cbnews.ATT.COM (Mark Horton) writes:
}Suppose you're writing an application, like ed or vi, that runs
}interactively at the terminal, but when the user hits DEL, no
}matter what is going on, it aborts and comes back for another
}user command.  Suppose this program has to be very portable; it
}must run on System V, Berkeley, ANSI, POSIX, and V7.

The version of C (K&R or ANSI) is orthogonal to the operating
system.  I assume you mean portable to various different UNIX(tm)
systems, with old or new C implementations.

}
}In the old days, you would catch SIGINT and have the signal
}handler do a longjmp back to the main loop.  Another reasonable
}approach was to have the signal handler just return, and the
}read from the tty that was probably interrupted could check to
}see if read exited with EINTR, and if so print another prompt
}and go back for another command.

This almost always worked, but you could always interrupt at certain
points that caused subsequent code to misbehave.

}
}Then Berkeley 4.2BSD changed how signal worked, and if the handler
}returned, the read would just resume.  It became accepted wisdom
}among authors of highly portable programs that the longjmp approach
}was the most portable method.  (Checking, this is true of 4.2BSD
}and 4.3BSD, but SunOS 4.0 does it the System V way.)
}
}Now ANSI C has decreed that you can't call longjmp, or any other
}function except signal, from inside a signal handler for any signal
}except those raised by abort, raise, or SIGFPE.  The reason given
}for this is that you might be in the middle of calling that function
}already when the interrupt comes in, and C library routine are not
}required to be reentrant.  You can't call longjmp to get out of a
}signal handler because maybe the code was in the middle of calling
}longjmp when the signal came in.  Obscure, but they're right.  (On
}the other hand, I can't see the harm in reentering longjmp, you'll
}never get back to the first call.)

It's more than just a nonreentrant longjmp(): it's that no library
function other than signal() is guaranteed not to be mucking around
with static data.  If you are in the middle of a stdio function,
there are windows during which data structures are inconsistent.
Short of wrapping all such critical regions with signal-preventing
code, the "no functions can be called in an asynchronous signal handler
for a portable program.

}
}So where does this leave the developer?  ANSI C says the only way
}to exit from a SIGINT handler is to return.  The routine is supposed
}to pick up where it left off, ala 4.2BSD (although the System V
}behavior of having the read return EINTR seems to be allowed too.)
}Even if you pretend 4.*BSD doesn't exist, you can imagine the
}complexity of checking for EINTR after every terminal read, if
}you call handy stdio routines like getchar, scanf, and fgets.  (Nobody
}calls gets anymore, right? :-)

The recommended approach is for your signal handler to set an external
volatile sig_atomic_t object and return.  The main loop should check the
value of this object at appropriate points.  The 4.*BSD behavior for
certain system calls does get in the way, unfortunately.  For such
systems, the nonportable longjmp() approach may be the only choice.

}
}Has anybody found a portable solution to this problem?  Is there
}any conceivable implementation of longjmp that will have problems
}when it gets called 1 1/2 times?
}
}There is a similar problem with trying to call exit from a signal
}handler.  exit flushes all stdio buffers, and you might have been
}in the middle of an I/O operation when the signal came in.  This
}could cause a core dump in exit!

If you are using POSIX-based systems, you should be able to call _exit()
without any problems.  This, of course, is not highly portable--it is
generally available only on UNIX systems.

}
}Is this something that should be fixed in the ANSI C standard?

The only "fix" to the standard would be either to completely remove any
notion of signal handling, or to standardize on a "secure" signal
function set.  There is nothing that can be done that makes the standard
libraries signal-proof.  There were enough proponents for the
availability of some sort of asynchronous signal handling that signal
remained in the standard, despite the technical problems with any sort
of guarantees.

}
}	Mark Horton

Dave Prosser	...not an official X3J11 answer...

rml@hpfcdc.HP.COM (Bob Lenk) (07/08/89)

> No -- The Standard doesn't create the problem, it merely documents it.

Yes!  As a common example, if the signal interrupts a malloc() call in
most implementations, malloc() may have static data structures in an
inconsistent state.  If the signal handler calls longjmp(), and some
code later calls malloc() or one of its friends, the standard's
description of "undefined behavior" applies quite well on any UN*X
implementation I know of.  Programs that work in these environments may
be avoiding malloc() and other troublesome routines by design (based on
knowledge of what routines are safe in this regard) or by luck, or they
may really have the problem but never (or rarely) encounter it.

		Bob Lenk
		rml@hpfcla.hp.com
		hplabs!hpfcla!rml