minow@decvax.UUCP (Martin Minow) (12/06/83)
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
mjs@rabbit.UUCP (12/06/83)
There is a small error in the DECTALK code as posted. It is invalid to test the value of errno unless { a) a system call has returned a failure indication (-1); or b) errno was set to 0 before the system call of interest } AND no other system calls have been invoked since the system call of interest. -- Marty Shannon UUCP: {alice,rabbit,research}!mjs Phone: 201-582-3199
steveh@hammer.UUCP (Stephen Hemminger) (12/07/83)
--------
The obvious way around the problem with 4.2/4.1c is to use a select
and not use a signal at all!
/* NOT TESTED */
#include <sys/types.h>
#include <time.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 */
int fdmask; /* file descriptor mask for select() */
struct timeval timeout; /* timeout value for select() */
/*
* 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);
/* Berkeley Unix 4.2 has a nice way of doing this */
fdmask = 1<<dt->unit);
timeout.tv_usec = 0;
timeout.tv_sec = sec;
if(select(1, &fdmask, 0, 0, 0, &timeout) < 0)
return (DT_ERROR);
if(fdmask == 0)
return (DT_TIMEOUT)
incount = read(dt->unit, dt->in_buff, IN_BUFLEN);
dt->in_end = &dt->in_buff[incount];
return (*dt->in_ptr++ & 0xFF);
}
steveg@hammer.UUCP (Steve Glaser) (12/07/83)
Actually, a somewhat more serious race condition in the first example (where the signal handler did a longjmp to exit things) is that you could get the signal before the setjmp ever finished executing and thus end up doing a longjmp to somewhere random. If you make the assumptions that there's a human on the other end of things and that computers are relatively fast, it's not very likely to cause a problem, but relatively easy to fix - just make sure you don't enable the signal catcher until after the setjmp. Steve Glaser [tektronix|teklabs]!steveg UUCP steveg.tektronix@rand-relay ARPANET/CSNET