/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "httpd.h"
#include "http_config.h"
#include "simple_types.h"
#include "simple_event.h"
#include "simple_run.h"
#include "http_log.h"
#include "simple_children.h"
#include "apr_hash.h"

#include <unistd.h> /* For fork() */

#define SPAWN_CHILDREN_INTERVAL (apr_time_from_sec(5))

APLOG_USE_MODULE(mpm_simple);

static void simple_kill_random_child(simple_core_t * sc)
{
    /* See comment in simple_spawn_child for why we check here. */
    if (!sc->run_single_process) {
        apr_hash_index_t *hi;
        simple_child_t *child = NULL;

        apr_thread_mutex_lock(sc->mtx);
        hi = apr_hash_first(sc->pool, sc->children);
        if (hi != NULL) {
            apr_hash_this(hi, NULL, NULL, (void **)&child);
            apr_hash_set(sc->children, &child->pid, sizeof(child->pid), NULL);
        }
        apr_thread_mutex_unlock(sc->mtx);

        if (child != NULL) {
            kill(child->pid, 9);
            /* TODO: recycle child object */
        }
    }
}

static void clean_child_exit(int code) __attribute__ ((noreturn));
static void clean_child_exit(int code)
{
    /* TODO: Pool cleanups.... sigh. */
    exit(code);
}

static int simple_spawn_child(simple_core_t * sc)
{
    pid_t pid = 0;
    int rv = 0;
    /* Although we could cut this off 'earlier', and not even invoke this
     * function, I would like to keep the functions invoked when in debug mode
     * to be as close as possible to those when not in debug... So, we just skip
     * the actual spawn itself, but go through all of the motions...
     */
    if (!sc->run_single_process) {
        if (sc->spawn_via == SIMPLE_SPAWN_FORK) {

            pid = fork();
            if (pid == -1) {
                rv = errno;
                ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(00245)
                             "simple_spawn_child: Unable to fork new process");
                return rv;
            }

            if (pid == 0) {
                /* this is the child process */

                rv = simple_child_loop(sc);

                if (rv) {
                    clean_child_exit(APEXIT_CHILDFATAL);
                }
                else {
                    clean_child_exit(0);
                }
            }
        }
        else {
            /* TODO: SIMPLE_SPAWN_EXEC */
            abort();
        }
    }

    if (pid != 0) {
        simple_child_t *child;

        apr_thread_mutex_lock(sc->mtx);

        child = apr_palloc(sc->pool, sizeof(simple_child_t));
        child->pid = pid;
        apr_hash_set(sc->children, &child->pid, sizeof(child->pid), child);

        apr_thread_mutex_unlock(sc->mtx);
    }

    return 0;
}

void simple_check_children_size(simple_core_t * sc, void *baton)
{
    unsigned int count;
    int wanted;
    int i;

    simple_register_timer(sc,
                          simple_check_children_size,
                          NULL, SPAWN_CHILDREN_INTERVAL,
                          sc->pool);

    if (sc->run_single_process && sc->restart_num == 2) {
        static int run = 0;
        /* This is kinda of hack, but rather than spawning a child process,
         * we register the normal IO handlers in the main event loop....
         */
        if (run == 0) {
            simple_single_process_hack(sc);
            run++;
        }
    }

    {
        apr_thread_mutex_lock(sc->mtx);
        count = apr_hash_count(sc->children);
        wanted = sc->procmgr.proc_count;
        apr_thread_mutex_unlock(sc->mtx);
    }

    if (count > wanted) {
        /* kill some kids */
        int to_kill = count - wanted;
        for (i = 0; i < to_kill; i++) {
            simple_kill_random_child(sc);
        }
    }
    else if (count < wanted) {
        int rv = 0;
        /* spawn some kids */
        int to_spawn = wanted - count;
        for (i = 0; rv == 0 && i < to_spawn; i++) {
            rv = simple_spawn_child(sc);
        }
    }
    else {
        /* juuuuust right. */
    }
}
