home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #16 / NN_1992_16.iso / spool / comp / lang / c / 11605 < prev    next >
Encoding:
Internet Message Format  |  1992-07-25  |  7.4 KB

  1. Xref: sparky comp.lang.c:11605 comp.unix.programmer:3916
  2. Path: sparky!uunet!cis.ohio-state.edu!pacific.mps.ohio-state.edu!linac!att!ucbvax!dog.ee.lbl.gov!horse.ee.lbl.gov!torek
  3. From: torek@horse.ee.lbl.gov (Chris Torek)
  4. Newsgroups: comp.lang.c,comp.unix.programmer
  5. Subject: Re: trapping "control C"
  6. Keywords: setjmp longjmp signal
  7. Message-ID: <24897@dog.ee.lbl.gov>
  8. Date: 26 Jul 92 01:10:23 GMT
  9. References: <1992Jul17.082746.13375@ibmpcug.co.uk> <1992Jul17.141248.4837@mdd.comm.mot.com> <1992Jul17.232515.6365@athena.mit.edu> <1992Jul20.235307.23706@seas.smu.edu>
  10. Reply-To: torek@horse.ee.lbl.gov (Chris Torek)
  11. Followup-To: comp.unix.programmer
  12. Organization: Lawrence Berkeley Laboratory, Berkeley
  13. Lines: 174
  14. NNTP-Posting-Host: 128.3.112.15
  15.  
  16. (I am moving this to comp.unix.programmer, since most of this has
  17. nothing to do with the C language itself, but rather with details of
  18. its use under Unix systems.)
  19.  
  20. In article <1992Jul17.232515.6365@athena.mit.edu> scs@adam.mit.edu
  21. (Steve Summit) notes that
  22. >>This bug (catching SIGINT unilaterally, without first testing if
  23. >>it is being ignored) is rampant, because it is immaterial if you
  24. >>don't have multitasking and background processes, and because
  25. >>even on Unix it (the bug) is not noticed under the C shell.
  26.  
  27. This is correct: Unix programs should first verify that SIGINT is
  28. *supposed* to be caught or defaulted before `taking it over'.  This
  29. applies also to SIGHUP and SIGQUIT (these three are the only `old
  30. shell' compatible `tty generated' signals).
  31.  
  32. In article <1992Jul20.235307.23706@seas.smu.edu> mustafa@seas.smu.edu
  33. (Mustafa Kocaturk) writes:
  34. >I have tried to write the following `usleep' function by using
  35. >the SIGALRM signal and some library functions. It works even when it is
  36. >run in the background ...
  37.  
  38. Since SIGALRM is not a `tty signal' it can be caught or defaulted
  39. without first testing its disposition, provided you control the rest
  40. of the source.  (A library function should be more careful.)
  41.  
  42. (Steve Summit again:)
  43. >>Signal handling is really one of the cases where portability and
  44. >>pragmatism collide head-on. ...
  45.  
  46. Indeed.
  47.  
  48. >In my humble opinion, this lack of guarantee should not prevent some
  49. >questioning programmers to use longjmp inside a signal handler function.
  50.  
  51. It should at the least discourage casual usage of signal functions.
  52. Writing correct signal functions is difficult (as the example below
  53. will illustrate).
  54.  
  55. >After all, most of the uses for a `longjmp', in my opinion, are for
  56. >returning from signal handlers and low-level interrupt routines.
  57.  
  58. On the contrary, longjmp is most likely to be used portably to `escape'
  59. from several levels of function call.  For instance, a program that
  60. writes to files (using stdio) may use longjmp to `escape out' of an
  61. inner output-routine all the way back to setup code that can erase a
  62. temporary file and abort, thus recovering from, or at least failing
  63. gracefully under, an overfull disk.
  64.  
  65. >The object `sigcontext' contains largely system-dependent data that
  66. >the signal handler can, if necessary, examine and modify before it
  67. >returns or terminates.
  68.  
  69. In fact, *having* a `sigcontext' structure is in violation of ANSI C.
  70. A signal handler must be called with exactly one `int' argument, that
  71. being the signal number.  (Unix systems can handle this safely simply
  72. by not delivering sigcontexts to *signal* functions, but sending them
  73. on to *sigaction* functions.  A small `wrapper' function in the C
  74. library will handle this, at some cost in efficiency.  Currently,
  75. though, most Unix systems simply live dangerously and pass `extra'
  76. parameters to signal functions, hoping that this causes no trouble.)
  77.  
  78. >#include <stdio.h>
  79. >#include <signal.h>
  80. >#include <sys/time.h>
  81. >#include <setjmp.h>
  82. >
  83. >jmp_buf env;
  84. >
  85. >int inthand(int sig, int code, struct sigcontext *scp)
  86. > {
  87. > longjmp(env,1);
  88. > }
  89. >
  90. >int usleep(int usecs)
  91. > {
  92. > struct itimerval value, ovalue;
  93. > void (*prevh)();
  94. > value.it_interval.tv_sec=value.it_interval.tv_usec=0;
  95. > value.it_value.tv_sec=usecs/1000000;
  96. > value.it_value.tv_usec=usecs%1000000;
  97. > setitimer(ITIMER_REAL,&value,&ovalue);
  98. > if(setjmp(env)!=0)
  99. >  {
  100. >  signal(SIGALRM,prevh);
  101. >  return 0;
  102. >  }
  103. > prevh=signal(SIGALRM,inthand);
  104. > sigpause(0);
  105. > }
  106.  
  107. There are at least two bugs in this code.  First (and somewhat
  108. irrelevant due to Unix systems `living dangerously' as noted above),
  109. inthand() should take only its `int sig' argument, or the code
  110. should use sigaction() to set up the catching function.
  111.  
  112. The most important bug here is a race condition.  This may cause the
  113. program to fail `mysteriously'.  In particular, if the system is
  114. heavily loaded, and/or the time passed to usleep() is sufficiently
  115. short, the setitimer call may succeed and then the alarm may occur
  116. before the setjmp() or the textually-second signal() call finishes.  In
  117. this case, the SIGALRM will be handled according to the previous
  118. disposition.  If the signal was previously ignored, the program will
  119. then hang at the sigpause() until some other signal occurs.  If the
  120. signal was previously caught, the old catcher will run (what effect
  121. this has obviously depends on that function).  If the signal was
  122. previously defaulted, the program will terminate.
  123.  
  124. Even if we do get far enough to catch the signal, the sigpause() may
  125. finish due to some *other* caught signal.  The sigpause(), or in POSIX,
  126. sigsuspend(), waits until a signal occurs *and then returns*.  (It may
  127. `grab' more than one signal if several pile up `all at once', but as
  128. soon as it gets one it can proceed.)  In this case, the alarm may
  129. eventually occur *after usleep has returned*, and the longjmp() will
  130. then jump to a nonexistent or overwritten function activation.
  131.  
  132. (All of the above is lumped under one `race condition' bug.)
  133.  
  134. As side bugs, the routine restores the previous SIGALRM handler but
  135. does not restore any previous interval timer, and it ignores possible
  136. error returns from setitimer and signal.  Restoring previous intervals
  137. is difficult and probably not worthwhile, as is attempting to handle
  138. the case where an existing value would cause the alarm to fire *before*
  139. the time to usleep is up.
  140.  
  141. The usleep() function *can* be implemented using something like the
  142. code above, provided that:
  143.  
  144.     A. You set and arrange to catch SIGALRM `atomically';
  145.     B. You loop calling sigpause().
  146.  
  147. Provision A can be handled with sigblock and sigsetmask or their POSIX
  148. equivalents (sigprocmask with SIG_BLOCK and SIG_UNBLOCK or
  149. SIG_SETMASK).  Once you combine this with B, however, you can drop the
  150. setjmp and longjmp and simply test a variable, looping until it is set
  151. or cleared by your alarm handler.  Thus:
  152.  
  153. static volatile sig_atomic_t caught;
  154.  
  155. static void
  156. catch(int sig)
  157. {
  158.  
  159.     caught = 1;
  160. }
  161.  
  162. void
  163. usleep(unsigned usecs)
  164. {
  165.     struct itimerval value;
  166.     sigset_t mask, omask, pausemask;
  167.     void (*prevh)(int);
  168.  
  169.     value.it_interval.tv_sec = value.it_interval.tv_usec = 0;
  170.     value.it_value.tv_sec = usecs / 1000000;
  171.     value.it_value.tv_usec = usecs % 1000000;
  172.     (void) sigemptyset(&mask);
  173.     (void) sigaddset(&mask, SIGALRM);
  174.     (void) sigprocmask(SIG_BLOCK, &mask, &omask);
  175.     pausemask = omask;    /* in case SIGALRM was already blocked... */
  176.     (void) sigdelset(&pausemask, SIGALRM);
  177.     (void) setitimer(ITIMER_REAL, &value, (struct itimerval *)NULL);
  178.     prevh = signal(SIGALRM, catch);
  179.     while (!caught)
  180.         sigsuspend(&pausemask);
  181.     (void) signal(SIGALRM, prevh);
  182.     (void) sigprocmask(SIG_SETMASK, &omask, (sigmask_t *)NULL);
  183. }
  184.  
  185. This still has plenty of room for improvement, but at least the races
  186. are gone and the types all match up with various standards.
  187. -- 
  188. In-Real-Life: Chris Torek, Lawrence Berkeley Lab CSE/EE (+1 510 486 5427)
  189. Berkeley, CA        Domain:    torek@ee.lbl.gov
  190.