/*  -
   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 "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"

void 
close_listeners (r)
     struct rovm *r;
{
  rc_listen_rec *lr;
  
  for (lr = ROVM_LISTENERS (r); lr; lr = lr->next) 
    {
      rc_socket_close (lr->sd);
      lr->active = 0;
    }
}

static rc_status_t
close_listeners_on_exec (void *v)
{
  close_listeners (v);

  return RC_SUCCESS;
}

/* TODO: make_sock is just begging and screaming for APR abstraction */
static rc_status_t
make_sock (struct rovm *r, rc_pool_t *p, rc_listen_rec *server)
{
  rc_socket_t *s = server->sd;
  int one = 1;
  rc_status_t stat;
  
#ifndef WIN32
  stat = rc_socket_opt_set (s, RC_SO_REUSEADDR, one);
  if (stat != RC_SUCCESS && stat != RC_ENOTIMPL) 
    {
      rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK,
                "make_sock: for address %pI, rc_socket_opt_set: (SO_REUSEADDR)",
                server->bind_addr);
      rc_socket_close (s);

      return stat;
    }
#endif
  
  stat = rc_socket_opt_set (s, RC_SO_KEEPALIVE, one);
  if (stat != RC_SUCCESS && stat != RC_ENOTIMPL) 
    {
      rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK,
                "make_sock: for address %pI, rc_socket_opt_set: (SO_KEEPALIVE)",
                server->bind_addr);
      rc_socket_close (s);

      return stat;
    }
  
  /*
    To send data over high bandwidth-delay connections at full
    speed we must force the TCP window to open wide enough to keep the
    pipe full.  The default window size on many systems
    is only 4kB.  Cross-country WAN connections of 100ms
    at 1Mb/s are not impossible for well connected sites.
    If we assume 100ms cross-country latency,
    a 4kB buffer limits throughput to 40kB/s.
    
    To avoid this problem I've added the SendBufferSize directive
    to allow the web master to configure send buffer size.
    
    The trade-off of larger buffers is that more kernel memory
    is consumed.  YMMV, know your customers and your network!
    
    -John Heidemann <johnh@isi.edu> 25-Oct-96
    
    If no size is specified, use the kernel default.
  */
  if (CONF_SEND_BUFFER_SIZE (ROVM_CONF (r)))
    {
      stat = rc_socket_opt_set(s, RC_SO_SNDBUF,  CONF_SEND_BUFFER_SIZE (ROVM_CONF (r)));
      if (stat != RC_SUCCESS && stat != RC_ENOTIMPL) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK,
                    "make_sock: failed to set SendBufferSize for "
                    "address %pI, using default",
                    server->bind_addr);
          /* not a fatal error */
        }
    }
  if (CONF_RECEIVE_BUFFER_SIZE (ROVM_CONF (r)))
    {
      stat = rc_socket_opt_set (s, RC_SO_RCVBUF, CONF_RECEIVE_BUFFER_SIZE (ROVM_CONF (r)));
      if (stat != RC_SUCCESS && stat != RC_ENOTIMPL) 
        {
          rovm_log (NULL, ROVMLOG_WARNING, ROVMLOG_MARK,
                    "make_sock: failed to set ReceiveBufferSize for "
                    "address %pI, using default",
                    server->bind_addr);
          /* not a fatal error */
        }
    }
  
#if RC_TCP_NODELAY_INHERITED
  ap_sock_disable_nagle (s);
#endif
  
  if ((stat = rc_socket_bind (s, server->bind_addr)) != RC_SUCCESS) 
    {
      rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK,
                "make_sock: could not bind to address %pI",
                server->bind_addr);
      rc_socket_close (s);
      return stat;
    }

  if ((stat = rc_socket_listen(s, CONF_LISTENBACKLOG (ROVM_CONF (r)))) != RC_SUCCESS)
    {
      rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK,
                "make_sock: unable to listen for connections "
                "on address %pI",
                server->bind_addr);
      rc_socket_close (s);
      return stat;
    }
  
#ifdef WIN32
  /* 
     I seriously doubt that this would work on Unix; I have doubts that
     it entirely solves the problem on Win32.  However, since setting
     reuseaddr on the listener -prior- to binding the socket has allowed
     us to attach to the same port as an already running instance of
     Apache, or even another web server, we cannot identify that this
     port was exclusively granted to this instance of Apache.
     
     So set reuseaddr, but do not attempt to do so until we have the
     parent listeners successfully bound.
  */
  stat = rc_socket_opt_set (s, RC_SO_REUSEADDR, one);
  if (stat != RC_SUCCESS && stat != RC_ENOTIMPL) 
    {
      rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK,
                "make_sock: for address %pI, rc_socket_opt_set: (SO_REUSEADDR)",
                server->bind_addr);
      rc_socket_close (s);
      return stat;
    }
#endif
  
  server->sd = s;
  server->active = 1;
  
#ifdef MPM_ACCEPT_FUNC
  server->accept_func = MPM_ACCEPT_FUNC;
#else
  server->accept_func = NULL;
#endif
  
  return RC_SUCCESS;
}

static char *
alloc_listener (struct rovm *r, char *addr, rc_port_t port, const char* proto)
{
  rc_listen_rec *last;
  rc_status_t status;
  rc_sockaddr_t *sa;
  rc_pool_t *p = PROCREC_POOL (ROVM_PROCESS (r));
   
  if ((status = rc_sockaddr_info_get (&sa, addr, RC_INET, port, 0, p)) != RC_SUCCESS) 
    {
      rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK,
                "alloc_listener: failed to set up sockaddr for %s", addr);

      return "Listen setup failed";
    }

  /* Initialize to our last configured ap_listener. */
  last = ROVM_LISTENERS (r);
  while (last && last->next)
    last = last->next;

  while (sa)
    {
      rc_listen_rec *new;
      
      /* this has to survive restarts */
      new = mp_alloc (p, sizeof (rc_listen_rec));
      new->active = 0;
      new->next = 0;
      new->bind_addr = sa;
      new->protocol = mp_strdup (p, proto);
      
      /* Go to the next sockaddr. */
      sa = sa->next;

      status = rc_socket_create (&new->sd, new->bind_addr->family, SOCK_STREAM, 0, p);

      if (status != RC_SUCCESS) 
        {
          rovm_log (NULL, ROVMLOG_CRIT, ROVMLOG_MARK,
                    "alloc_listener: failed to get a socket for %s", addr);

          return "Listen setup failed";
        }
      /* We need to preserve the order returned by getaddrinfo() */
      if (last == NULL)
        ROVM_LISTENERS (r) = last = new;
      else 
        {
          last->next = new;
          last = new;
        }
    }
  
  return NULL;
}

/**
   모든 소켓을 생성하고, 열고, listen 하고 bind 를 수행한다.

   @param       ROVM 구조체
   @return      성공적으로 소켓을 여는데, 성공했다면 0 을 그렇지 않다면 -1 을 반환한다.
 */
static int 
open_listeners (r)
     struct rovm *r;
{
  rc_listen_rec *lr;
  rc_listen_rec *previous;
  int num_open;
  const char *userdata_key = "rc_open_listeners";
  void *data;
  rc_pool_t *pool = PROCREC_POOL (ROVM_PROCESS (r));

  /* 
     Don't allocate a default listener.  If we need to listen to a
     port, then the user needs to have a Listen directive in their
     config file.
  */
  num_open = 0;
  previous = NULL;
  for (lr = ROVM_LISTENERS (r); lr; previous = lr, lr = lr->next) 
    {
      if (lr->active)
        ++num_open;
      else 
        {
          if (make_sock (r, pool, lr) == RC_SUCCESS) 
            {
              ++num_open;
              lr->active = 1;
            }
          else 
            /* fatal error */
            return -1;
        }
    }

#if RC_NONBLOCK_WHEN_MULTI_LISTEN
  /* if multiple listening sockets, make them non-blocking so that
   * if select()/poll() reports readability for a reset connection that
   * is already forgotten about by the time we call accept, we won't
   * be hung until another connection arrives on that port
   */
  if (ROVM_LISTENERS (r) && ROVM_LISTENERS (r)->next) 
    {
      for (lr = ROVM_LISTENERS (r); lr; lr = lr->next) 
        {
          rc_status_t status;
          
          status = rc_socket_opt_set (lr->sd, RC_SO_NONBLOCK, 1);
          if (status != RC_SUCCESS) 
            {
              rovm_log (NULL, ROVMLOG_ERR, ROVMLOG_MARK, 
                        "unable to make listening socket non-blocking");
              return -1;
            }
        }
    }
#endif /* RC_NONBLOCK_WHEN_MULTI_LISTEN */

  /* we come through here on both passes of the open logs phase
   * only register the cleanup once... otherwise we try to close
   * listening sockets twice when cleaning up prior to exec
   */
  mp_userdata_get (&data, userdata_key, pool);
  if (!data) 
    {
      mp_userdata_set ((const void *)1, userdata_key, rc_pool_cleanup_null, pool);
      mp_cleanup_register (pool, (const void *) r, rc_pool_cleanup_null, close_listeners_on_exec);
    }

  return num_open ? 0 : -1;
}

/**
   ROVM 의 listening port 에 대한 정보를 초기화한다.

   @param r     ROVM 구조체
   @param p     사용할 메모리 pool
*/
int
init_rovm_listen (r, p)
     struct rovm *r;
     rc_pool_t *p;
{
  char *rv, *host = NULL;
  rc_port_t port = CONF_PORT (ROVM_CONF (r));
  char *proto = "envlang";

  rv = alloc_listener (r, host, port, proto);
  if (rv)
    return -1;

  if (open_listeners (r))
    return -1;

  return 0;
}
