/*
**  Process control routines.
*/

#include "portable/system.h"

#include <sys/wait.h>

#include "innd.h"


static PROCESS *PROCtable;
static int PROCtablesize;
static PROCESS PROCnull = {PSfree, 0, 0, 0, 0, 0};


/*
**  Collect dead processes.
*/
static void
PROCreap(void)
{
    int status;
    PROCESS *pp;
    int i;
    pid_t pid;

    for (;;) {
        pid = waitpid(-1, &status, WNOHANG);
        if (pid == 0)
            break;
        if (pid < 0) {
            if (errno != ECHILD)
                syslog(L_ERROR, "%s cant wait %m", LogName);
            break;
        }
        for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
            if (pp->State == PSrunning && pp->Pid == pid) {
                PROCneedscan = true;
                pp->Status = WEXITSTATUS(status);
                pp->State = PSdead;
                pp->Collected = Now.tv_sec;
                break;
            }
    }
}


#ifdef HAVE_SIGACTION
#    define NO_SIGACTION_UNUSED UNUSED
#else
#    define NO_SIGACTION_UNUSED
#endif

/*
**  Signal handler that collects the processes, then resets the signal.
*/
static void
PROCcatchsignal(int s NO_SIGACTION_UNUSED)
{
    PROCreap();

#ifndef HAVE_SIGACTION
    xsignal(s, PROCcatchsignal);
#endif
}


/*
**  Synchronous version that notifies a site when its process went away.
*/
void
PROCscan(void)
{
    PROCESS *pp;
    int i;

    for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
        if (pp->State == PSdead) {
            if (pp->Site >= 0)
                SITEprocdied(&Sites[pp->Site], pp - PROCtable, pp);
            pp->State = PSfree;
        }
    PROCneedscan = false;
}


#if 0
/*
**  Close down all processes.
*/
void
PROCclose(bool Quickly)
{
    int sig;
    PROCESS *pp;
    int i;

    /* What signal are we sending? */
    sig = Quickly ? SIGKILL : SIGTERM;

    /* Send the signal to all living processes. */
    for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++) {
        if (pp->State != PSrunning)
            continue;
        if (kill(pp->Pid, sig) < 0 && errno != ESRCH)
            syslog(L_ERROR, "%s cant kill %s %ld %m", LogName,
                   Quickly ? "KILL" : "TERM", (long) pp->Pid);
    }

    /* Collect any who might have died. */
    PROCreap();
    for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
        if (pp->State == PSdead)
            *pp = PROCnull;
}
#endif /* 0 */


/*
**  Stop watching a process -- we don't care about it any more.
*/
void
PROCunwatch(int process)
{
    if (process < 0 || process >= PROCtablesize) {
        syslog(L_ERROR, "%s internal PROCunwatch %d", LogName, process);
        return;
    }
    PROCtable[process].Site = -1;
}


/*
**  Add a pid to the list of processes we watch.
*/
int
PROCwatch(pid_t pid, int site)
{
    PROCESS *pp;
    int i;

    /* Find a free slot for this process. */
    for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
        if (pp->State == PSfree)
            break;
    if (i < 0) {
        /* Ran out of room -- grow the table. */
        PROCtable =
            xrealloc(PROCtable, (PROCtablesize + 20) * sizeof(PROCESS));
        for (pp = &PROCtable[PROCtablesize], i = 20; --i >= 0; pp++)
            *pp = PROCnull;
        pp = &PROCtable[PROCtablesize];
        PROCtablesize += 20;
    }

    pp->State = PSrunning;
    pp->Pid = pid;
    pp->Started = Now.tv_sec;
    pp->Site = site;
    return pp - PROCtable;
}


/*
**  Setup.
*/
void
PROCsetup(int i)
{
    PROCESS *pp;

    if (PROCtable)
        free(PROCtable);
    PROCtablesize = i;
    PROCtable = xmalloc(PROCtablesize * sizeof(PROCESS));
    for (pp = PROCtable, i = PROCtablesize; --i >= 0; pp++)
        *pp = PROCnull;

#if defined(SIGCHLD)
    xsignal(SIGCHLD, PROCcatchsignal);
#endif /* defined(SIGCHLD) */
    xsignal(SIGPIPE, PROCcatchsignal);
}
