* * * * *

                            It's off to the Races

I was right, there were race conditions [1] in monnet. Mark [2] helped me in
locating them and my only comment on the whole thing is: Unix signal
semantics suck. Although Mark assures me that any form synchronization is
nasty, although I still don't see why it has to be so difficult.

To be portable, the only thing you can do in a signal handler is do a simple
assignment to a variable declared as volatile sig_atomic_t. Anything else
could lead to problems. So, in monnet I now have:

-----[ C ]-----
volatile sig_atomic_t g_sigint  = 0;
volatile sig_atomic_t g_sighup  = 0;
volatile sig_atomic_t g_sigchld = 0;

static void handler_int()
{
 g_sigint = 1;
}

static void handler_hup()
{
 g_sighup = 1;
}

static void handler_chld()
{
 int status;

 g_sigchld = 1;
 wait(&status);
}
-----[ END OF LINE ]-----

Granted, that isn't proper ANSI C function headers, but there is no real
consensus as to what signal handlers take (on some systems, a single integer
parameter, others, no parameters, others several parameters) so that's about
the best you can do. I am taking a risk with handler_chld() in doing the
wait() but POSIX lists wait() as being callable via a signal handler so hey,
why not live on the edge here. Now, elsewhere, the main code:

-----[ C ]-----
while(1)
{
 while(1)
 {
   s = read( /* ... */ )
   if (s <= 0) break;
   /* ... */
 }

 if (g_sighup)
 {
   g_sighup = 0;
   generate_report();
 }

 if (g_sigchld)
 {
   g_sigchld = 0;
   g_child   = 0;
 }

 if (g_sigint)
   break;
}

/* ... */

static void generate_report(void)
{
 if (g_child != 0)
   return;

 g_child = fork();

 if (g_child > 0)              /* parent resumes       */
   return;
 else if (g_child < 0)         /* error?  just resume  */
 {
   g_child = 0;
   return;
 }

 /* ... */
}
-----[ END OF LINE ]-----

SIGINT just breaks out of the main loop and terminates the program (with some
cleanup). SIGHUP generates a call to generate_report() which creates a new
process (if one hasn't already been created) to generate the actual report.

If I didn't handle SIGCHLD, I would end up with zombie processes (lovely in
that even if I don't care about the child, I still have to wait() for it).
Now, it is conceivable that a SIGHUP sent at the right time would fail to
create a report file, but would only happen if a previous SIGHUP had been
given to generate a report file and was just finishing up. But I can live
with that.

[1] gopher://gopher.conman.org/0Phlog:2000/05/15.1
[2] http://www.conman.org/people/myg/

Email author at [email protected]