Relay-Version: version B 2.10 5/3/83; site utzoo.UUCP Posting-Version: version B 2.10.1 6/24/83; site decvax.UUCP Path: utzoo!linus!decvax!minow From: minow@decvax.UUCP (Martin Minow) Newsgroups: net.unix-wizards Subject: longjmp + signal may cause problems Message-ID: <304@decvax.UUCP> Date: Mon, 5-Dec-83 21:35:22 EST Article-I.D.: decvax.304 Posted: Mon Dec 5 21:35:22 1983 Date-Received: Wed, 7-Dec-83 00:09:12 EST Organization: DEC UNIX Engineering Group Lines: 144 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 #include #include 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 #include 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 #include 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