/* Copyright 2001-2005 The Apache Software Foundation or its licensors, as
 * applicable.
 *
 * Licensed 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 <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

#include "types.h"
#include "mpool.h"
#include "tktree.h"
#include "clstree.h"
#include "sha1.h"
#include "listen.h"
#include "rovm.h"

#include "thread.h"
#include "thread_mutex.h"
#include "thread_cond.h"

#include "mpm_worker_fdqueue.h"
#include "mpm_worker_pod.h"
#include "mpm_worker.h"

#include "connection.h"
#include "request.h"

#include "log.h"
#include "utils.h"

static pid_t parent_pid, my_pid;

/* handle all varieties of core dumping signals */
static void
sig_coredump (int sig)
{
  rc_signal (sig, SIG_DFL);

  /* linuxthreads issue calling getpid() here:
   * This comparison won't match if the crashing thread is
   * some module's thread that runs in the parent process.
   * The fallout, which is limited to linuxthreads:
   * The special log message won't be written when such a
   * thread in the parent causes the parent to crash.
   */
  if (getpid () == parent_pid) 
    {
      rovm_log (NULL, ROVMLOG_NOTICE, ROVMLOG_MARK, 
                "seg fault or similar nasty error detected "
                "in the parent process");
      /* XXX we can probably add some rudimentary cleanup code here,
       * like getting rid of the pid file.  If any additional bad stuff
       * happens, we are protected from recursive errors taking down the
       * system since this function is no longer the signal handler   GLA
       */
    }
  kill (getpid (), sig);
  /* At this point we've got sig blocked, because we're still inside
   * the signal handler.  When we leave the signal handler it will
   * be unblocked, and we'll take the signal... and coredump or whatever
   * is appropriate for this particular Unix.  In addition the parent
   * will see the real signal we received -- whereas if we called
   * abort() here, the parent would only see SIGABRT.
   */
}

rc_status_t 
rc_fatal_signal_setup (struct rovm *r)
{
#ifndef NO_USE_SIGACTION
  struct sigaction sa;
  
  sigemptyset (&sa.sa_mask);
  
#if defined(SA_ONESHOT)
  sa.sa_flags = SA_ONESHOT;
#elif defined(SA_RESETHAND)
  sa.sa_flags = SA_RESETHAND;
#else
  sa.sa_flags = 0;
#endif
  
  sa.sa_handler = sig_coredump;
  if (sigaction (SIGSEGV, &sa, NULL) < 0)
    rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, "sigaction(SIGSEGV)");
#ifdef SIGBUS
  if (sigaction (SIGBUS, &sa, NULL) < 0)
    rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, "sigaction(SIGBUS)");
#endif
#ifdef SIGABORT
  if (sigaction (SIGABORT, &sa, NULL) < 0)
    rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, "sigaction(SIGABORT)");
#endif
#ifdef SIGABRT
  if (sigaction (SIGABRT, &sa, NULL) < 0)
    rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, "sigaction(SIGABRT)");
#endif
#ifdef SIGILL
  if (sigaction(SIGILL, &sa, NULL) < 0)
    rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, "sigaction(SIGILL)");
#endif
  
#else /* NO_USE_SIGACTION */
  rc_signal (SIGSEGV, sig_coredump);
#ifdef SIGBUS
  rc_signal (SIGBUS, sig_coredump);
#endif /* SIGBUS */
#ifdef SIGABORT
  rc_signal (SIGABORT, sig_coredump);
#endif /* SIGABORT */
#ifdef SIGABRT
  rc_signal (SIGABRT, sig_coredump);
#endif /* SIGABRT */
#ifdef SIGILL
  rc_signal (SIGILL, sig_coredump);
#endif /* SIGILL */
  
#endif /* NO_USE_SIGACTION */
  
  parent_pid = my_pid = getpid ();
  
  return RC_SUCCESS;
}

void
rc_run_monitor (rc_pool_t *p)
{
  /* nothing  */
}

/* number of calls to wait_or_timeout between writable probes */
#ifndef INTERVAL_OF_WRITABLE_PROBES
#define INTERVAL_OF_WRITABLE_PROBES 10
#endif
static int wait_or_timeout_counter;

void
rc_wait_or_timeout (rc_exit_why_e *status, int *exitcode, rc_proc_t *ret, rc_pool_t *p)
{
  rc_status_t rv;
  
  ++wait_or_timeout_counter;
  if (wait_or_timeout_counter == INTERVAL_OF_WRITABLE_PROBES)
    {
      wait_or_timeout_counter = 0;
      rc_run_monitor (p);
    }

  rv = rc_proc_wait_all_procs (ret, exitcode, status, RC_NOWAIT, p);
  if (RC_STATUS_IS_EINTR (rv)) 
    {
      ret->pid = -1;
      return;
    }
  
  if (RC_STATUS_IS_CHILD_DONE (rv)) 
    return;
  
#ifdef NEED_WAITPID
  if ((ret = reap_children(exitcode, status)) > 0) 
    {
      return;
    }
#endif
  
  rc_sleep (SCOREBOARD_MAINTENANCE_INTERVAL);
  ret->pid = -1;
  return;
}

int rc_process_child_status (apr_proc_t *pid, apr_exit_why_e why, int status)
{
  int signum = status;
  const char *sigdesc = rc_signal_description_get (signum);
  
  /* Child died... if it died due to a fatal error,
   * we should simply bail out.  The caller needs to
   * check for bad rc from us and exit, running any
   * appropriate cleanups.
   *
   * If the child died due to a resource shortage,
   * the parent should limit the rate of forking
   */
  if (RC_PROC_CHECK_EXIT (why)) 
    {
      if (status == RCEXIT_CHILDSICK)
        return status;

      if (status == RCEXIT_CHILDFATAL) 
        {
          rovm_log (NULL, ROVMLOG_ALERT, ROVMLOG_MARK, 
                       "Child %d returned a Fatal error... ROVM Server is exiting!",
                       pid->pid);
          return RCEXIT_CHILDFATAL;
        }

        return 0;
    }

  if (RC_PROC_CHECK_SIGNALED (why)) 
    {
      switch (signum) 
        {
        case SIGTERM:
        case SIGHUP:
        case SIGKILL:
          break;
          
        default:
          if (RC_PROC_CHECK_CORE_DUMP (why)) 
            rovm_log (NULL, ROVMLOG_NOTICE, ROVMLOG_MARK, 
                      "child pid %ld exit signal %s (%d) coredump",
                      (long) pid->pid, sigdesc, signum);
          else 
            rovm_log (NULL, ROVMLOG_NOTICE, ROVMLOG_MARK, 
                      "child pid %ld exit signal %s (%d)",
                      (long)pid->pid, sigdesc, signum);
        }
    }

  return 0;
}

typedef enum {DO_NOTHING, SEND_SIGTERM, SEND_SIGKILL, GIVEUP} action_t;

static int 
reclaim_one_pid (pid_t pid, action_t action)
{
  rc_proc_t proc;
  rc_status_t waitret;
  
  proc.pid = pid;
  waitret = apr_proc_wait (&proc, NULL, NULL, RC_NOWAIT);
  if (waitret != RC_CHILD_NOTDONE) 
    return 1;
  
  switch(action) 
    {
    case DO_NOTHING:
      break;
      
    case SEND_SIGTERM:
      /* ok, now it's being annoying */
      rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK, 
                "child process %d still did not exit, "
                "sending a SIGTERM",
                pid);
      kill (pid, SIGTERM);
      break;
      
    case SEND_SIGKILL:
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "child process %d"
                " still did not exit, "
                "sending a SIGKILL",
                pid);
      kill (pid, SIGKILL);
      break;

    case GIVEUP:
      /* gave it our best shot, but alas...  If this really
       * is a child we are trying to kill and it really hasn't
       * exited, we will likely fail to bind to the port
       * after the restart.
       */
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                "could not make child process %d" 
                " exit, "
                "attempting to continue anyway",
                pid);
      break;
    }
  
  return 0;
}

int
rc_reclaim_child_processes (r, terminate)
     struct rovm *r;
     int terminate;
{
  /* this table of actions and elapsed times tells what action is taken
   * at which elapsed time from starting the reclaim
   */
  struct 
  {
    action_t action;
    apr_time_t action_time;
  } action_table[] = 
      {
        {DO_NOTHING, 0}, /* dummy entry for iterations where we reap
                          * children but take no action against
                          * stragglers
                          */
        {SEND_SIGTERM, rc_time_from_sec (3)},
        {SEND_SIGTERM, rc_time_from_sec (5)},
        {SEND_SIGTERM, rc_time_from_sec (7)},
        {SEND_SIGKILL, rc_time_from_sec (9)},
        {GIVEUP,       rc_time_from_sec (10)}
      };
  int cur_action;      /* index of action we decided to take this
                        * iteration
                        */
  int next_action = 1; /* index of first real action */
  rc_time_t waittime = 1024 * 16;
  int i, not_dead_yet;
  rc_time_t starttime = rc_time_now ();
  int max_daemons = CONF_DAEMONS_TO_START (ROVM_CONF (r));

  do
    {
      rc_sleep (waittime);
      /* don't let waittime get longer than 1 second; otherwise, we don't
         react quickly to the last child exiting, and taking action can
         be delayed  */
      waittime = waittime * 4;
      if (waittime > rc_time_from_sec (1))
        waittime = rc_time_from_sec (1);
      
      /* see what action to take, if any */
      if (action_table[next_action].action_time <= rc_time_now () - starttime) 
        {
          cur_action = next_action;
          ++next_action;
        }
      else 
        cur_action = 0; /* nothing to do */

      /* now see who is done */
      not_dead_yet = 0;
      for (i = 0; i < max_daemons; ++i) 
        {
          pid_t pid = SB_PARENT_IDX (ROVM_SCOREBOARD (r), i)->pid;
          
          if (pid == 0)
            continue; /* not every scoreboard entry is in use */

          if (reclaim_one_pid (pid, action_table[cur_action].action))
            SB_PARENT_IDX (ROVM_SCOREBOARD (r), i)->pid = 0;
          else
            ++not_dead_yet;
        }
    }
  while (not_dead_yet > 0 &&
         action_table[cur_action].action != GIVEUP);

  return 0;
}
