[project @ 1997-05-18 03:58:13 by sof]
[ghc-hetmet.git] / ghc / runtime / main / Signals.lc
1 %
2 % (c) The AQUA Project, Glasgow University, 1995
3 %
4 %************************************************************************
5 %*                                                                      *
6 \section[Signals.lc]{Signal Handlers}
7 %*                                                                      *
8 %************************************************************************
9
10 There are two particular signals that we find interesting in the RTS:
11 segmentation faults (for cheap stack overflow checks) and virtual
12 timer alarms (for profiling and thread context switching).  POSIX
13 compliance is supposed to make this kind of thing easy, but it
14 doesn't.  Expect every new target platform to require gory hacks to
15 get this stuff to work.
16
17 Then, there are the user-specified signal handlers to cope with.
18 Since they're pretty rudimentary, they shouldn't actually cause as
19 much pain.
20
21 \begin{code}
22 #include "config.h"
23
24 /* Treat nexttep3 and sunos4 alike. CaS */
25 #if defined(nextstep3_TARGET_OS)
26 # define NON_POSIX_SOURCE
27 #endif
28  
29 #if defined(sunos4_TARGET_OS)
30     /* The sigaction in SunOS 4.1.X does not grok SA_SIGINFO */
31 # define NON_POSIX_SOURCE
32 #endif
33
34 #if defined(freebsd_TARGET_OS) || defined(aix_TARGET_OS)
35 # define NON_POSIX_SOURCE
36 #endif
37
38 #if defined(osf1_TARGET_OS)
39     /* The include files for OSF1 do not normally define SA_SIGINFO */
40 # define _OSF_SOURCE 1
41 #endif
42
43 #if irix_TARGET_OS
44 /* SIGVTALRM not avail w/ POSIX_SOURCE, but worse things happen without */
45 /* SIGH: triple SIGH (WDP 95/07) */
46 # define SIGVTALRM 28
47 #endif
48
49 #include "rtsdefs.h"
50
51 #if defined(HAVE_SYS_TYPES_H)
52 # include <sys/types.h>
53 #endif
54
55         /* This is useful with the particular set of header files on my NeXT.
56          * CaS
57          */
58 #if defined(HAVE_SYS_SIGNAL_H)
59 # include <sys/signal.h>
60 #endif
61
62 #if defined(HAVE_SIGNAL_H)
63 # include <signal.h>
64 #endif
65
66 #if defined(linux_TARGET_OS) || defined(linuxaout_TARGET_OS)
67 /* to look *inside* sigcontext... 
68
69   sigcontext has moved and been protected from the General Public,
70   in later versions (>2), the sigcontext decl is protected by
71   a __KERNEL__ #ifdef. As ever, we workaround by trying to
72   be version savvy - the version numbers are currently just a guess!
73   (ToDo: determine at what version no. the sigcontext move
74    was made).
75 */
76 # ifndef LINUX_VERSION_CODE
77 #  include <linux/version.h>
78 # endif
79 # if (LINUX_VERSION_CODE < 0x020000)
80 #  include <asm/signal.h>
81 # else
82 #  include <asm/sigcontext.h>
83 # endif
84
85 #endif
86
87 #if defined(HAVE_SIGINFO_H)
88     /* DEC OSF1 seems to need this explicitly.  Maybe others do as well? */
89 # include <siginfo.h>
90 #endif
91
92 #if defined(cygwin32_TARGET_OS)
93 # include <windows.h>
94 #endif
95
96 \end{code}
97
98 %************************************************************************
99 %*                                                                      *
100 \subsection{Stack-check by protected-memory-faulting}
101 %*                                                                      *
102 %************************************************************************
103
104 If we are checking stack overflow by page faulting, then we need to be
105 able to install a @SIGSEGV@ handler, preferably one which can
106 determine where the fault occurred, so that we can satisfy ourselves
107 that it really was a stack overflow and not some random segmentation
108 fault.
109
110 \begin{code}
111 #if STACK_CHECK_BY_PAGE_FAULT
112         /* NB: At the moment, this is always false on nextstep3. CaS. */
113
114 extern P_ stks_space;       /* Where the stacks live, from SMstacks.lc */
115 \end{code}
116
117 SunOS 4.x is too old to have @SA_SIGINFO@ as a flag to @sigaction@, so
118 we use the older @signal@ call instead.  This means that we also have
119 to set up the handler to expect a different collection of arguments.
120 Fun, eh?
121
122 \begin{code}
123 # if defined(sunos4_TARGET_OS) || defined(freebsd_TARGET_OS) \
124   || defined(linux_TARGET_OS)  || defined(linuxaout_TARGET_OS) \
125   || defined(aix_TARGET_OS)
126
127 static void
128 segv_handler(int sig,
129     /* NB: all except first argument are "implementation defined" */
130 #  if defined(sunos4_TARGET_OS) || defined(freebsd_TARGET_OS)
131         int code, struct sigcontext *scp, caddr_t addr)
132 #  else /* linux || aix */
133 #    if defined(aix_TARGET_OS)
134         int code, struct sigcontext *scp)
135 #    else /* linux */
136         struct sigcontext_struct scp)
137 #    endif 
138 #  endif
139 {
140     extern void StackOverflow(STG_NO_ARGS) STG_NORETURN;
141
142 #  if defined(linux_TARGET_OS)  || defined(linuxaout_TARGET_OS)
143     caddr_t addr = scp.cr2;
144     /* Magic info from Tommy Thorn! */
145 #  endif
146 #  if defined(aix_TARGET_OS)
147     caddr_t addr = scp->sc_jmpbuf.jmp_context.o_vaddr;
148     /* Magic guess by andre */
149 #  endif
150     if (addr >= (caddr_t) stks_space
151       && addr < (caddr_t) (stks_space + RTSflags.GcFlags.stksSize))
152         StackOverflow();
153
154     fflush(stdout);
155     fprintf(stderr, "Segmentation fault caught, address = %lx\n", (W_) addr);
156     abort();
157 }
158
159 int
160 install_segv_handler(void)
161 {
162 #if freebsd_TARGET_OS
163     /* FreeBSD seems to generate SIGBUS for stack overflows */
164     if (signal(SIGBUS, segv_handler) == SIG_ERR)
165         return -1;
166     return ((int) signal(SIGSEGV, segv_handler));
167 #else
168     return ((int) signal(SIGSEGV, segv_handler) == SIG_ERR);
169     /* I think the "== SIG_ERR" is saying "there was no
170        handler for SIGSEGV before this one".  WDP 95/12
171     */
172 #endif
173 }
174
175 # else  /* Not SunOS 4, FreeBSD, or Linux(a.out) */
176
177 #  if defined(irix_TARGET_OS)
178      /* certainly BOGUS (WDP 94/05) -- copied from /usr/include/sys/siginfo.h */
179 #   define si_addr _data._fault._addr
180 #  endif
181
182 #if defined(cygwin32_TARGET_OS)
183 /*
184  The signal handlers in cygwin32 (beta14) are only passed the signal
185  number, no sigcontext/siginfo is passed as event data..sigh. For
186  SIGSEGV, to get at the violating address, we need to use the Win32's
187  WaitForDebugEvent() to get out any status information. 
188 */
189 static void
190 segv_handler(sig)
191  int sig;
192 {
193     /* From gdb/win32-nat.c */
194     DEBUG_EVENT event;
195     BOOL t = WaitForDebugEvent (&event, INFINITE);
196
197     fflush(stdout);
198     if (t == FALSE) {
199         fprintf(stderr, "Segmentation fault caught, address unknown\n");
200     } else {
201         void *si_addr = event.u.Exception.ExceptionRecord.ExceptionAddress;
202         if (si_addr >= (void *) stks_space
203           && si_addr < (void *) (stks_space + RTSflags.GcFlags.stksSize))
204             StackOverflow();
205
206         fprintf(stderr, "Segmentation fault caught, address = %08lx\n", (W_)si_addr);
207     }
208     abort();
209 }
210
211 int
212 install_segv_handler()
213 {
214     return (int) signal(SIGSEGV, segv_handler) == -1;
215 }
216
217
218 #else /* !defined(cygwin32_TARGET_OS) */
219
220 static void
221 segv_handler(int sig, siginfo_t *sip)
222   /* NB: the second "siginfo_t" argument is not really standard */
223 {
224     fflush(stdout);
225     if (sip == NULL) {
226         fprintf(stderr, "Segmentation fault caught, address unknown\n");
227     } else {
228         if (sip->si_addr >= (caddr_t) stks_space
229           && sip->si_addr < (caddr_t) (stks_space + RTSflags.GcFlags.stksSize))
230             StackOverflow();
231
232         fprintf(stderr, "Segmentation fault caught, address = %08lx\n", (W_) sip->si_addr);
233     }
234     abort();
235 }
236
237 int
238 install_segv_handler(STG_NO_ARGS)
239 {
240     struct sigaction action;
241
242     action.sa_handler = segv_handler;
243     sigemptyset(&action.sa_mask);
244     action.sa_flags = SA_SIGINFO;
245
246     return sigaction(SIGSEGV, &action, NULL);
247 }
248
249 #endif /* not cygwin32_TARGET_OS */
250
251 # endif    /* not SunOS 4 */
252
253 #endif  /* STACK_CHECK_BY_PAGE_FAULT */
254
255 \end{code}
256
257 %************************************************************************
258 %*                                                                      *
259 \subsection{Virtual-timer alarm (for profiling, etc.)}
260 %*                                                                      *
261 %************************************************************************
262
263 The timer interrupt is somewhat simpler, and we could probably use
264 sigaction across the board, but since we have committed ourselves to
265 the non-POSIX signal under SunOS 4.1.X, we adopt the same approach
266 here.
267
268 \begin{code}
269 #if defined(PROFILING) || defined(CONCURRENT) /* && !defined(GRAN) */
270
271 # ifdef CONCURRENT
272
273 extern I_ delayTicks;
274
275 #  ifdef PAR
276 extern P_ CurrentTSO;
277 #  endif
278
279 /*
280  cygwin32 does not support VTALRM (sigh) - to do anything
281  sensible here we use the underlying Win32 calls.
282  (will this work??)
283 */
284 #   if defined(cygwin32_TARGET_OS)
285 /* windows.h already included */
286 static VOID CALLBACK 
287 vtalrm_handler(uID,uMsg,dwUser,dw1,dw2)
288 int uID;
289 unsigned int uMsg;
290 unsigned int dwUser;
291 unsigned int dw1;
292 unsigned int dw2;
293 #   else
294 static void
295 vtalrm_handler(int sig)
296 #   endif
297 {
298 /*
299    For the parallel world, currentTSO is set if there is any work
300    on the current PE.  In this case we DO want to context switch,
301    in case other PEs have sent us messages which must be processed.
302 */
303
304 #  if defined(PROFILING) || defined(PAR)
305     static I_ csTicks = 0, pTicks = 0;
306
307     if (time_profiling) {
308         if (++pTicks % RTSflags.CcFlags.profilerTicks == 0) {
309 #   if ! defined(PROFILING)
310             handle_tick_serial();
311 #   else
312             if (RTSflags.CcFlags.doCostCentres >= COST_CENTRES_VERBOSE
313              || RTSflags.ProfFlags.doHeapProfile)
314                 handle_tick_serial();
315             else
316                 handle_tick_noserial();
317 #   endif
318         }
319         if (++csTicks % RTSflags.CcFlags.ctxtSwitchTicks != 0)
320             return;
321     }
322 #  endif
323
324        /*
325          Handling a tick for threads blocked waiting for file
326          descriptor I/O or time.
327
328          This requires some care since virtual time alarm ticks
329          can occur when we are in the GC. If that is the case,
330          we just increment a delayed timer tick counter, but do
331          not check to see if any TSOs have been made runnable
332          as a result. (Do a bulk update of their status once
333          the GC has completed).
334
335          If the vtalrm does not occur within GC, we try to promote
336          any of the waiting threads to the runnable list (see awaitEvent)
337
338          4/96 SOF
339        */
340
341     if (delayTicks != 0) /* delayTicks>0 => don't handle timer expiry (in GC) */
342        delayTicks++;
343     else if (WaitingThreadsHd != PrelBase_Z91Z93_closure)
344              AwaitEvent(RTSflags.ConcFlags.ctxtSwitchTime);
345
346 #  ifdef PAR
347     if (PendingSparksTl[REQUIRED_POOL] == PendingSparksLim[REQUIRED_POOL] ||
348       PendingSparksTl[ADVISORY_POOL] == PendingSparksLim[ADVISORY_POOL]) {
349         PruneSparks();
350         if (PendingSparksTl[REQUIRED_POOL] == PendingSparksLim[REQUIRED_POOL]) 
351             PendingSparksTl[REQUIRED_POOL] = PendingSparksBase[REQUIRED_POOL] +
352               SparkLimit[REQUIRED_POOL] / 2;
353         if (PendingSparksTl[ADVISORY_POOL] == PendingSparksLim[ADVISORY_POOL]) {
354             PendingSparksTl[ADVISORY_POOL] = PendingSparksBase[ADVISORY_POOL] +
355               SparkLimit[ADVISORY_POOL] / 2;
356             sparksIgnored += SparkLimit[REQUIRED_POOL] / 2; 
357         }
358     }
359
360     if (CurrentTSO != NULL ||
361 #  else
362     if (RunnableThreadsHd != PrelBase_Z91Z93_closure ||
363 #  endif
364       PendingSparksHd[REQUIRED_POOL] < PendingSparksTl[REQUIRED_POOL] ||
365       PendingSparksHd[ADVISORY_POOL] < PendingSparksTl[ADVISORY_POOL]) {
366         /* ToDo: anything else for GRAN? WDP */
367         context_switch = 1;
368     }
369 }
370
371 # endif
372
373
374 #if defined(cygwin32_TARGET_OS) /* really just Win32 */
375 /* windows.h already included for the segv_handling above */
376
377 I_ vtalrm_id;
378 TIMECALLBACK *vtalrm_cback;
379
380 #ifndef CONCURRENT
381 void (*tick_handle)(STG_NO_ARGS);
382
383 static VOID CALLBACK 
384 tick_handler(uID,uMsg,dwUser,dw1,dw2)
385 int uID;
386 unsigned int uMsg;
387 unsigned int dwUser;
388 unsigned int dw1;
389 unsigned int dw2;
390 {
391  (*tick_handle)();
392 }
393 #endif
394
395 int install_vtalrm_handler()
396 {
397 #  ifdef CONCURRENT
398     vtalrm_cback = vtalrm_handler;
399 #  else
400      /*
401         Only turn on ticking 
402      */
403     vtalrm_cback = tick_handler;
404     if (RTSflags.CcFlags.doCostCentres >= COST_CENTRES_VERBOSE
405      || RTSflags.ProfFlags.doHeapProfile)
406         tick_handle = handle_tick_serial;
407     else
408         tick_handle = handle_tick_noserial;
409 #  endif
410     return (int)0;
411 }  
412
413 void
414 blockVtAlrmSignal(STG_NO_ARGS)
415 {
416  timeKillEvent(vtalrm_id);
417 }
418
419 void
420 unblockVtAlrmSignal(STG_NO_ARGS)
421 {
422 #ifdef CONCURRENT
423  timeSetEvent(RTSflags.ConcFlags.ctxtSwitchTime,5,vtalrm_cback,NULL,TIME_PERIODIC);
424 #else
425  timeSetEvent(RTSflags.CcFlags.msecsPerTick,5,vtalrm_cback,NULL,TIME_PERIODIC);
426 #endif
427 }
428
429 #elif defined(sunos4_TARGET_OS)
430
431 int
432 install_vtalrm_handler(void)
433 {
434     void (*old)();
435
436 #  ifdef CONCURRENT
437     old = signal(SIGVTALRM, vtalrm_handler);
438 #  else
439     if (RTSflags.CcFlags.doCostCentres >= COST_CENTRES_VERBOSE
440      || RTSflags.ProfFlags.doHeapProfile)
441         old = signal(SIGVTALRM, handle_tick_serial);
442     else
443         old = signal(SIGVTALRM, handle_tick_noserial);
444 #  endif
445     return ((int) old == SIG_ERR);
446 }
447
448 static int vtalrm_mask;
449
450 void
451 blockVtAlrmSignal(STG_NO_ARGS)
452 {
453     vtalrm_mask = sigblock(sigmask(SIGVTALRM));
454 }
455
456 void
457 unblockVtAlrmSignal(STG_NO_ARGS)
458 {
459     (void) sigsetmask(vtalrm_mask);
460 }
461
462 # else  /* Not SunOS 4 */
463
464 int
465 install_vtalrm_handler(STG_NO_ARGS)
466 {
467     struct sigaction action;
468
469 #  ifdef CONCURRENT
470     action.sa_handler = vtalrm_handler;
471 #  else
472     if (RTSflags.CcFlags.doCostCentres >= COST_CENTRES_VERBOSE
473      || RTSflags.ProfFlags.doHeapProfile)
474         action.sa_handler = handle_tick_serial;
475     else
476         action.sa_handler = handle_tick_noserial;
477 #  endif
478
479     sigemptyset(&action.sa_mask);
480     action.sa_flags = 0;
481
482     return sigaction(SIGVTALRM, &action, NULL);
483 }
484
485 void
486 blockVtAlrmSignal(STG_NO_ARGS)
487 {
488     sigset_t signals;
489     
490     sigemptyset(&signals);
491     sigaddset(&signals, SIGVTALRM);
492
493     (void) sigprocmask(SIG_BLOCK, &signals, NULL);
494 }
495
496 void
497 unblockVtAlrmSignal(STG_NO_ARGS)
498 {
499     sigset_t signals;
500     
501     sigemptyset(&signals);
502     sigaddset(&signals, SIGVTALRM);
503
504     (void) sigprocmask(SIG_UNBLOCK, &signals, NULL);
505 }
506
507 # endif /* ! SunOS 4 */
508
509 #endif /* PROFILING || CONCURRENT (but not GRAN) */
510
511 \end{code}
512
513 Signal handling support for user-specified signal handlers.  Since we
514 need stable pointers to do this properly, we just refuse to try in the
515 parallel world.  Sorry.
516
517 \begin{code}
518
519 #if defined(PAR) /* || defined(GRAN) */
520
521 void
522 blockUserSignals(void)
523 {
524     return;
525 }
526
527 void
528 unblockUserSignals(void)
529 {
530     return;
531 }
532
533 I_ 
534 # ifdef _POSIX_SOURCE
535 sig_install(sig, spi, mask)
536   sigset_t *mask;
537 # else
538   sig_install(sig, spi)
539 # endif
540   I_ sig;
541   I_ spi;
542 {
543     fflush(stdout);
544     fprintf(stderr,"No signal handling support in a parallel implementation.\n");
545     EXIT(EXIT_FAILURE);
546 }
547
548 #else   /* !PAR */
549
550 # include <setjmp.h>
551
552 extern StgPtr deRefStablePointer PROTO((StgStablePtr));
553 extern void freeStablePointer PROTO((I_));
554 extern jmp_buf restart_main;
555
556 static I_ *handlers = NULL; /* Dynamically grown array of signal handlers */
557 static I_ nHandlers = 0;    /* Size of handlers array */
558
559 static void
560 more_handlers(I_ sig)
561 {
562     I_ i;
563
564     if (sig < nHandlers)
565         return;
566
567     if (handlers == NULL)
568         handlers = (I_ *) malloc((sig + 1) * sizeof(I_));
569     else
570         handlers = (I_ *) realloc(handlers, (sig + 1) * sizeof(I_));
571
572     if (handlers == NULL) {
573         fflush(stdout);
574         fprintf(stderr, "VM exhausted (in more_handlers)\n");
575         EXIT(EXIT_FAILURE);
576     }
577     for(i = nHandlers; i <= sig; i++)
578         /* Fill in the new slots with default actions */
579         handlers[i] = STG_SIG_DFL;
580
581     nHandlers = sig + 1;
582 }
583
584 I_ nocldstop = 0;
585
586 # ifdef _POSIX_SOURCE
587
588 static void
589 generic_handler(int sig)
590 {
591     sigset_t signals;
592
593     SAVE_Hp = SAVE_HpLim;       /* Just to be safe */
594     if (! initStacks(&StorageMgrInfo)) {
595         fflush(stdout);
596         fprintf(stderr, "initStacks failed!\n");
597         EXIT(EXIT_FAILURE);
598     }
599     TopClosure = deRefStablePointer(handlers[sig]);
600     sigemptyset(&signals);
601     sigaddset(&signals, sig);
602     sigprocmask(SIG_UNBLOCK, &signals, NULL);
603     longjmp(restart_main, sig);
604 }
605
606 static sigset_t userSignals;
607 static sigset_t savedSignals;
608
609 void
610 initUserSignals(void)
611 {
612     sigemptyset(&userSignals);
613 }
614
615 void
616 blockUserSignals(void)
617 {
618     sigprocmask(SIG_SETMASK, &userSignals, &savedSignals);
619 }
620
621 void
622 unblockUserSignals(void)
623 {
624     sigprocmask(SIG_SETMASK, &savedSignals, NULL);
625 }
626
627
628 I_ 
629 sig_install(sig, spi, mask)
630   I_ sig;
631   I_ spi;
632   sigset_t *mask;
633 {
634     sigset_t signals;
635     struct sigaction action;
636     I_ previous_spi;
637
638     /* Block the signal until we figure out what to do */
639     /* Count on this to fail if the signal number is invalid */
640     if(sig < 0 || sigemptyset(&signals) || sigaddset(&signals, sig) ||
641        sigprocmask(SIG_BLOCK, &signals, NULL))
642         return STG_SIG_ERR;
643
644     more_handlers(sig);
645
646     previous_spi = handlers[sig];
647
648     switch(spi) {
649     case STG_SIG_IGN:
650         handlers[sig] = STG_SIG_IGN;
651         sigdelset(&userSignals, sig);
652         action.sa_handler = SIG_IGN;
653         break;
654         
655     case STG_SIG_DFL:
656         handlers[sig] = STG_SIG_DFL;
657         sigdelset(&userSignals, sig);
658         action.sa_handler = SIG_DFL;
659         break;
660     default:
661         handlers[sig] = spi;
662         sigaddset(&userSignals, sig);
663         action.sa_handler = generic_handler;
664         break;
665     }
666
667     if (mask != NULL)
668         action.sa_mask = *mask;
669     else
670         sigemptyset(&action.sa_mask);
671
672     action.sa_flags = sig == SIGCHLD && nocldstop ? SA_NOCLDSTOP : 0;
673
674     if (sigaction(sig, &action, NULL) || sigprocmask(SIG_UNBLOCK, &signals, NULL)) {
675         if (previous_spi)
676           freeStablePointer(handlers[sig]);
677         return STG_SIG_ERR;
678     }
679
680     return previous_spi;
681 }
682
683 # else  /* !POSIX */
684
685 static void
686 generic_handler(sig)
687 {
688     SAVE_Hp = SAVE_HpLim;       /* Just to be safe */
689     if (! initStacks(&StorageMgrInfo)) {
690         fflush(stdout);
691         fprintf(stderr, "initStacks failed!\n");
692         EXIT(EXIT_FAILURE);
693     }
694     TopClosure = deRefStablePointer(handlers[sig]);
695     sigsetmask(0);
696     longjmp(restart_main, sig);
697 }
698
699 static int userSignals;
700 static int savedSignals;
701
702 void
703 initUserSignals(void)
704 {
705     userSignals = 0;
706 }
707
708 void
709 blockUserSignals(void)
710 {
711     savedSignals = sigsetmask(userSignals);
712 }
713
714 void
715 unblockUserSignals(void)
716 {
717     sigsetmask(savedSignals);
718 }
719
720 I_ 
721 sig_install(sig, spi)
722   I_ sig;
723   I_ spi;
724 {
725     I_ previous_spi;
726     int mask;
727     void (*handler)(int);
728
729     /* Block the signal until we figure out what to do */
730     /* Count on this to fail if the signal number is invalid */
731     if(sig < 0 || (mask = sigmask(sig)) == 0)
732         return STG_SIG_ERR;
733
734     mask = sigblock(mask);
735
736     more_handlers(sig);
737
738     previous_spi = handlers[sig];
739
740     switch(spi) {
741     case STG_SIG_IGN:
742         handlers[sig] = STG_SIG_IGN;
743         userSignals &= ~sigmask(sig);
744         handler = SIG_IGN;
745         break;
746         
747     case STG_SIG_DFL:
748         handlers[sig] = STG_SIG_DFL;
749         userSignals &= ~sigmask(sig);
750         handler = SIG_DFL;
751         break;
752     default:
753         handlers[sig] = spi;
754         userSignals |= sigmask(sig);
755         handler = generic_handler;
756         break;
757     }
758
759     if (signal(sig, handler) < 0) {
760         if (previous_spi)
761           freeStablePointer(handlers[sig]);
762         sigsetmask(mask);
763         return STG_SIG_ERR;
764     }
765
766     sigsetmask(mask);
767     return previous_spi;
768 }
769
770 # endif    /* !POSIX */
771
772 #endif  /* PAR */
773
774 \end{code}