#define _GNU_SOURCE
#include <string.h>

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>


#include "process_watcher.h"
#include "process_watcher_entry.h"

#include "mecha.h"

static ProcessWatcher *g_instance = NULL;
static pthread_once_t g_instanceOnce = PTHREAD_ONCE_INIT;

static void * process_watcher_watchChildren (void *context);
static void process_watcher_setupInstance (void);

static void process_watcher_sigchildHandler (int signum, siginfo_t *siginfo, __attribute__ ((unused)) void *data);

static void process_watcher_setupPipes (ProcessWatcher *instance);
static void process_watcher_installHandlers (void);

struct s_ProcessWatcher
{
    hshTvoid_list *children;
    pthread_mutex_t childrenMutex;
    pthread_t watcherThread;

    int readFD;
    int writeFD;
};

ProcessWatcher *
process_watcher_new (void)
{
    ProcessWatcher *instance = (ProcessWatcher *) malloc (sizeof (ProcessWatcher));

    instance->children = hshFvoid_int_init (process_watcher_entry_destroy);

    pthread_mutex_init (&instance->childrenMutex, NULL);

    process_watcher_setupPipes (instance);

    pthread_create (&instance->watcherThread, NULL, process_watcher_watchChildren, instance);

    process_watcher_installHandlers();

    return (instance);
}

void
process_watcher_destroy (void *ptr)
{
    ProcessWatcher *instance = (ProcessWatcher *) ptr;

    pthread_cancel (instance->watcherThread);
    pthread_join (instance->watcherThread, NULL);

    close (instance->readFD);
    close (instance->writeFD);

    pthread_mutex_destroy (&instance->childrenMutex);

    hshFvoid_destroy (instance->children);

    free (instance);
}

static void
process_watcher_setupInstance()
{
    g_instance = process_watcher_new();
}

ProcessWatcher *
process_watcher_getInstance()
{
    pthread_once (&g_instanceOnce, process_watcher_setupInstance);

    return (g_instance);
}

void
process_watcher_removeWatchPid (ProcessWatcher *instance, int pid)
{
    pthread_mutex_lock (&instance->childrenMutex);

    hshFvoid_int_del (instance->children, pid);

    pthread_mutex_unlock (&instance->childrenMutex);
}

void
process_watcher_watchPid (ProcessWatcher *instance, int pid,
        ProcessWatcherDelegate delegate, void *context)
{
    pthread_mutex_lock (&instance->childrenMutex);

    hshFvoid_int_add (instance->children, pid,
            process_watcher_entry_new (pid, delegate, context));

    pthread_mutex_unlock (&instance->childrenMutex);
}

static void
process_watcher_installHandlers()
{
#if 0
    signal (SIGHUP, process_watcher_sigchildHandler);
    signal (SIGINT, process_watcher_sigchildHandler);
    signal (SIGQUIT, process_watcher_sigchildHandler);
#endif

    /*
     * It seems that with linux 2.4, you won't get called unless you use
     * SA_SIGINFO.
     *
     *  -eric@5stops.com 1.18.2004
     */
    {
        struct sigaction action;

        action.sa_handler = NULL;
        action.sa_sigaction = process_watcher_sigchildHandler;
        action.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_SIGINFO | SA_NODEFER;
        action.sa_restorer = NULL;

        sigemptyset (&action.sa_mask);

        if (sigaction (SIGCHLD, &action, NULL) != 0)
        {
            logFmsg (CONT_LOG_ERROR, "Could not install signal handler for SIGCHLD: %s",
                    strerror (errno));

            return;
        }
    }
}

static void
process_watcher_setupPipes (ProcessWatcher *instance)
{
    int fdpipes[2];

    if (pipe (fdpipes) != 0)
    {
        perror ("pipe");
    }

    instance->readFD = fdpipes[0];
    instance->writeFD = fdpipes[1];
}


struct process_watcher_data
{
    int status;
    pid_t pid;
};

static void
process_watcher_sigchildHandler (int signum, __attribute__ ((unused)) siginfo_t *siginfo, __attribute__ ((unused)) void *extraData)
{
    struct process_watcher_data data;

    if (signum != SIGCHLD)
    {
        logFmsg (CONT_LOG_DEBUG, "modcgi/process_watcher: Received signal %d. Exiting.", signum);

        exit (0);
    }

    while (TRUE)
    {
        data.pid = waitpid (0, &data.status, WNOHANG);

        if (data.pid == 0 || (data.pid == -1 && errno == ECHILD))
        {
            /* we apparently don't have any more children to wait on */

            return;
        }

        if (data.pid == -1)
        {
            logFmsg (CONT_LOG_DEBUG, "modcgi/process_watcher: wait: %d: %s", errno, strerror (errno));

            return;
        }

#if PROCESS_DEBUGGING
        logFmsg (CONT_LOG_DEBUG, "modcgi/process_watcher: reaped process %d.", data.pid);
#endif

        /* 
         * BUGBUG: This really shouldn't be using g_instance directly, but we
         * can't use mutexes in here (which process_watcher_getInstance() does).
         */
        if (write (g_instance->writeFD, &data, sizeof (data)) != sizeof (data))
        {
            logFmsg (CONT_LOG_ERROR, "modcgi/process_watcher: Could not write to fd: %s", strerror (errno));

            return;
        }
    }
}


/* 
 * Because sigwait() seems to not work properly for SIGCHLD on linux 2.4, we
 * are resorting to using a signal handler write the required information to a
 * pipe
 *
 *  -eric@5stops.com 1.18.2004
 */
static void *
process_watcher_watchChildren (void *context)
{
    ProcessWatcher *instance = context;

    while (TRUE)
    {
        ProcessWatcherEntry *entry;
        struct process_watcher_data data;

        if (read (instance->readFD, &data, sizeof (data)) != sizeof (data))
        {
            logFmsg (CONT_LOG_ERROR, "modcgi/process_watcher: Could not read from fd: %s", strerror (errno));

            return (NULL);
        }

        pthread_mutex_lock (&instance->childrenMutex);

        entry = hshFvoid_int_remove (instance->children, data.pid);

        if (entry != NULL)
        {
            process_watcher_entry_invoke (entry, data.status);
        }

        pthread_mutex_unlock (&instance->childrenMutex);
    }

    return (NULL);
}


