From: scs@adam.mit.edu
Newsgroups: comp.lang.c
Date: 18 Jul 92 03:25 MDT
Subject: Re: trapping "control C"
Nf-ID: #R:13t9osINNqam@darkstar.ucsc.edu:-1109617974:1992Jul17.232515.6365@athena.mit:617269070:001:4652
Nf-From: adam.mit.edu!scs    Jul 18 03:25:00 1992


In articles <1992Jul14.084225.20933@seas.smu.edu>,
<1992Jul14.084757.21838@seas.smu.edu>, and
<1992Jul14.085932.22581@seas.smu.edu>,
Mustafa Kocaturk (mustafa@seas.smu.edu) writes:
>  fprintf(stderr,
>   "Installing signal handler. Previous value=%p\n",signal(SIGINT,hand));

Ignoring the curious printout of signal()'s return value (which
is obviously for demonstration purposes only), there is a subtle
bug here.

In article <1992Jul17.082746.13375@ibmpcug.co.uk>,
Matthew Farwell (dylan@ibmpcug.co.uk) correctly writes:
> 	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
> 		signal(SIGINT, handler);

As Kernighan and Ritchie say, in an unfortunately fairly obscure
paper [1],

	Why the test and the double call to signal?  Recall that
	signals like interrupt are sent to *all* processes
	started from a particular terminal.  Accordingly, when a
	program is to be run non-interactively (started by & ),
	the shell turns off interrupts for it so it won't be
	stopped by interrupts intended for foreground processes.
	If this program began by announcing that all interrupts
	were to be sent to the [signal handling] routine
	regardless, that would undo the shell's effort to protect
	it when run in the background.

	The solution, shown above, is to test the state of
	interrupt handling, and to continue to ignore interrupts
	if they are already being ignored.  The code as written
	depends on the fact that signal returns the previous
	state of a particular signal.  If signals were already
	being ignored, the process should continue to ignore
	them; otherwise, they should be caught.

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.
(csh, in conjunction with the "new" terminal driver, arranges
that keyboard interrupt signals are sent only to the foreground
process group, not to "all processes started from a particular
terminal.")

Readers of this group are excused for not having known this, and
for not having read the above-quoted paper, since this is not
comp.unix.anything .

Finally, in article <1992Jul17.141248.4837@mdd.comm.mot.com>,
Bill Mitchell (mitchell@mdd.comm.mot.com) writes:
> Having been recently bitten by a signal handler, I thought I'd comment here.
> [quote about synchronization and other problems which can occur
> in handling signals, including the fact that a portable program
> can safely take very few actions within a signal handler.]
> The case above is pretty vanilla, except for the as-yet undefined cleanup,
> since the program terminates from within the handler.

(Actually, "the case above" was not really vanilla, in that it
included a printf in a signal handler, one of the more worrisome
cases, although it was to stderr, which is less dangerous.)

Signal handling is really one of the cases where portability and
pragmatism collide head-on.  As Bill noted, it is generally
agreed that the only truly portable thing to do in a signal
handler is to set a flag (of a special type, sig_atomic_t, at
that) and return.  (Although one part of the ANSI C Standard
seems to say that longjmp must work correctly from within any
signal handler, other parts say that virtually no library
functions are guaranteed to work if called from asynchronous
signal handlers [2].)

The problem, of course, with simply setting a flag in a signal
handler and returning, and then testing the flag synchronously in
the mainline of the program, is that the tests may have to be
repeated in any number of loops, and if (as it evolves) the
program ever happens to have a loop which simultaneously (a)
contains no test on the interrupt flag and (b) can be made to run
infinitely, the program will be uninterruptible.  (A common
strategy is to have the interrupt handler detect if the flag is
already set, i.e. if control-C has been pressed two or more times
in succession without the flag having been noticed by the
mainline, and abort in that case.)

I don't have a pious moral to end with this time -- portable
signal handling is just plain hard.  It's helpful, however, to
avoid the problem if possible by not putting any control-C
handling at all into programs which don't have a real need for
it.

					Steve Summit
					scs@adam.mit.edu

[1]	Brian W. Kernighan and Dennis M. Ritchie, "UNIX
	Programming -- Second Edition", Bell Labs Technical
	Memorandum 78-1273-9, October 2, 1978 [widely reprinted
	in volume 2 of the Unix Programmer's Manual].

[2]	ANSI X3.159 [the ANSI C Standard], sections 4.6.2.1 and
	4.7.1.1, respectively.