/*  -
   Copyright (C) 2006 Weongyo Jeong (weongyo@gmail.com)

This file is part of ROVM.

ROVM is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2, or (at your option) any later
version.

ROVM is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.  */

#include <unistd.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"

rc_status_t 
rc_mpm_pod_open (rc_pool_t *p, rc_pod_t **pod, struct rovm *r)
{
  rc_status_t rv;
  
  *pod = mp_alloc (p, sizeof(**pod));
  rv = rc_file_pipe_create (&((*pod)->pod_in), &((*pod)->pod_out), p);
  if (rv != RC_SUCCESS)
    return rv;

  (*pod)->p = p;
  (*pod)->r = r;
  
  /* close these before exec. */
  rc_file_inherit_unset ((*pod)->pod_in);
  rc_file_inherit_unset ((*pod)->pod_out);
  
  return RC_SUCCESS;
}

rc_status_t
rc_mpm_pod_check (rc_pod_t *pod)
{
  char c;
  rc_os_file_t fd;
  int rc;
  
  /* we need to surface EINTR so we'll have to grab the
   * native file descriptor and do the OS read() ourselves
   */
  rc_os_file_get (&fd, pod->pod_in);
  rc = read (fd, &c, 1);
  if (rc == 1) 
    {
      switch (c) 
        {
        case RESTART_CHAR:
          return RC_POD_RESTART;
        case GRACEFUL_CHAR:
          return RC_POD_GRACEFUL;
        }
    }
  return RC_POD_NORESTART;
}

rc_status_t
rc_mpm_pod_close (rc_pod_t *pod)
{
  rc_status_t rv;
  
  rv = rc_file_close (pod->pod_out);
  if (rv != RC_SUCCESS)
    return rv;

  rv = rc_file_close (pod->pod_in);
  if (rv != RC_SUCCESS)
    return rv;
  
  return RC_SUCCESS;  
}

static rc_status_t
pod_signal_internal (rc_pod_t *pod, int graceful)
{
  rc_status_t rv;
  char char_of_death = graceful ? GRACEFUL_CHAR : RESTART_CHAR;
  rc_size_t one = 1;

  rv = rc_file_write (pod->pod_out, &char_of_death, &one);
  if (rv != RC_SUCCESS) 
    rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK,
              "write pipe_of_death");

  return rv;
}

/*
  This function connects to the server, then immediately closes the connection.
  This permits the MPM to skip the poll when there is only one listening
  socket, because it provides a alternate way to unblock an accept() when
  the pod is used.
*/
static rc_status_t
dummy_connection (rc_pod_t *pod)
{
  char *srequest;
  rc_status_t rv;
  rc_socket_t *sock;
  rc_pool_t *p;
  rc_size_t len;
  
  /* create a temporary pool for the socket.  pconf stays around too long */
  rv = mp_create (&p, pod->p);
  if (rv != RC_SUCCESS)
    return rv;

  rv = rc_socket_create(&sock, pod->r->listeners->bind_addr->family,
                        SOCK_STREAM, 0, p);
  if (rv != RC_SUCCESS) 
    {
      rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK,
                "get socket to connect to listener");
      mp_destroy (p);
      return rv;
    }

  /* on some platforms (e.g., FreeBSD), the kernel won't accept many
   * queued connections before it starts blocking local connects...
   * we need to keep from blocking too long and instead return an error,
   * because the MPM won't want to hold up a graceful restart for a
   * long time
   */
  rv = rc_socket_timeout_set (sock, rc_time_from_sec(3));
  if (rv != RC_SUCCESS) 
    {
      rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK,
                "set timeout on socket to connect to listener");
      rc_socket_close(sock);
      mp_destroy(p);
      return rv;
    }
  
  rv = rc_socket_connect(sock, pod->r->listeners->bind_addr);
  if (rv != RC_SUCCESS) 
    {
      int log_level = ROVMLOG_WARNING;
      
      if (RC_STATUS_IS_TIMEUP(rv)) 
        {
          /* probably some server processes bailed out already and there
           * is nobody around to call accept and clear out the kernel
           * connection queue; usually this is not worth logging
           */
          log_level = ROVMLOG_DEBUG;
        }
      
      rovm_log (NULL, log_level, ROVMLOG_MARK,
                "connect to listener on %pI", pod->r->listeners->bind_addr);
    }

  /* 
     Create the request string.  Simple REQUEST command
  */

  srequest = mp_strcat (p, "\x01", NULL);
  
  /* Since some operating systems support buffering of data or entire
   * requests in the kernel, we send a simple request, to make sure
   * the server pops out of a blocking accept().
   */
  /* XXX: This is HTTP specific. We should look at the Protocol for each
   * listener, and send the correct type of request to trigger any Accept
   * Filters.
   */
  len = strlen (srequest);
  rc_socket_send (sock, srequest, &len);
  rc_socket_close (sock);
  mp_destroy (p);
  
  return rv;
}

rc_status_t
rc_mpm_pod_signal (rc_pod_t *pod, int graceful)
{
  return pod_signal_internal (pod, graceful);
}

void
rc_mpm_pod_killpg (rc_pod_t *pod, int num, int graceful)
{
  int i;
  rc_status_t rv = RC_SUCCESS;
  
  for (i = 0; i < num && rv == RC_SUCCESS; i++) 
    rv = pod_signal_internal (pod, graceful);
}
