/*
  This file is part of libmicrohttpd
  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff

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

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @file lib/daemon_destroy.c
 * @brief main functions to destroy a daemon
 * @author Christian Grothoff
 */
#include "internal.h"
#include "request_resume.h"
#include "daemon_close_all_connections.h"


/**
 * Stop all worker threads from the worker pool.
 *
 * @param daemon master daemon controlling the workers
 */
static void
stop_workers (struct MHD_Daemon *daemon)
{
  MHD_socket fd;
  unsigned int i;

  mhd_assert (1 < daemon->worker_pool_size);
  mhd_assert (1 < daemon->threading_mode);
  if (daemon->was_quiesced)
    fd = MHD_INVALID_SOCKET; /* Do not use FD if daemon was quiesced */
  else
    fd = daemon->listen_socket;
  /* Let workers shutdown in parallel. */
  for (i = 0; i < daemon->worker_pool_size; i++)
  {
    daemon->worker_pool[i].shutdown = true;
    if (MHD_ITC_IS_VALID_ (daemon->worker_pool[i].itc))
    {
      if (! MHD_itc_activate_ (daemon->worker_pool[i].itc,
                               "e"))
        MHD_PANIC (_ (
                     "Failed to signal shutdown via inter-thread communication channel.\n"));
    }
    else
    {
      /* Better hope shutdown() works... */
      mhd_assert (MHD_INVALID_SOCKET != fd);
    }
  }
#ifdef HAVE_LISTEN_SHUTDOWN
  if (MHD_INVALID_SOCKET != fd)
  {
    (void) shutdown (fd,
                     SHUT_RDWR);
  }
#endif /* HAVE_LISTEN_SHUTDOWN */
  for (i = 0; i < daemon->worker_pool_size; ++i)
  {
    MHD_daemon_destroy (&daemon->worker_pool[i]);
  }
  free (daemon->worker_pool);
  daemon->worker_pool = NULL;
  /* FIXME: does this still hold? */
  mhd_assert (MHD_ITC_IS_INVALID_ (daemon->itc));
#ifdef EPOLL_SUPPORT
  mhd_assert (-1 == daemon->epoll_fd);
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
  mhd_assert (-1 == daemon->epoll_upgrade_fd);
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
#endif /* EPOLL_SUPPORT */
}


/**
 * Shutdown and destroy an HTTP daemon.
 *
 * @param daemon daemon to stop
 * @ingroup event
 */
void
MHD_daemon_destroy (struct MHD_Daemon *daemon)
{
  MHD_socket fd;

  daemon->shutdown = true;
  if (daemon->was_quiesced)
    fd = MHD_INVALID_SOCKET; /* Do not use FD if daemon was quiesced */
  else
    fd = daemon->listen_socket;

  /* FIXME: convert from here to microhttpd2-style API! */

  if (NULL != daemon->worker_pool)
  {   /* Master daemon with worker pool. */
    stop_workers (daemon);
  }
  else
  {
    mhd_assert (0 == daemon->worker_pool_size);
    /* Worker daemon or single-thread daemon. */
    if (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_mode)
    {
      /* Worker daemon or single daemon with internal thread(s). */
      /* Separate thread(s) is used for polling sockets. */
      if (MHD_ITC_IS_VALID_ (daemon->itc))
      {
        if (! MHD_itc_activate_ (daemon->itc,
                                 "e"))
          MHD_PANIC (_ (
                       "Failed to signal shutdown via inter-thread communication channel.\n"));
      }
      else
      {
#ifdef HAVE_LISTEN_SHUTDOWN
        if (MHD_INVALID_SOCKET != fd)
        {
          if (NULL == daemon->master)
            (void) shutdown (fd,
                             SHUT_RDWR);
        }
        else
#endif /* HAVE_LISTEN_SHUTDOWN */
        mhd_assert (false); /* Should never happen */
      }

      if (! MHD_join_thread_ (daemon->pid.handle))
      {
        MHD_PANIC (_ ("Failed to join a thread.\n"));
      }
      /* close_all_connections() was called in daemon thread. */
    }
    else
    {
      /* No internal threads are used for polling sockets
   (external event loop) */
      MHD_daemon_close_all_connections_ (daemon);
    }
    if (MHD_ITC_IS_VALID_ (daemon->itc))
      MHD_itc_destroy_chk_ (daemon->itc);

#ifdef EPOLL_SUPPORT
    if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) &&
         (-1 != daemon->epoll_fd) )
      MHD_socket_close_chk_ (daemon->epoll_fd);
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
    if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) &&
         (-1 != daemon->epoll_upgrade_fd) )
      MHD_socket_close_chk_ (daemon->epoll_upgrade_fd);
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
#endif /* EPOLL_SUPPORT */

    MHD_mutex_destroy_chk_ (&daemon->cleanup_connection_mutex);
  }

  if (NULL != daemon->master)
    return;
  /* Cleanup that should be done only one time in master/single daemon.
   * Do not perform this cleanup in worker daemons. */

  if (MHD_INVALID_SOCKET != fd)
    MHD_socket_close_chk_ (fd);

  /* TLS clean up */
#ifdef HTTPS_SUPPORT
  if (NULL != daemon->tls_api)
  {
#if FIXME_TLS_API
    if (daemon->have_dhparams)
    {
      gnutls_dh_params_deinit (daemon->https_mem_dhparams);
      daemon->have_dhparams = false;
    }
    gnutls_priority_deinit (daemon->priority_cache);
    if (daemon->x509_cred)
      gnutls_certificate_free_credentials (daemon->x509_cred);
#endif
  }
#endif /* HTTPS_SUPPORT */

#ifdef DAUTH_SUPPORT
  free (daemon->nnc);
  MHD_mutex_destroy_chk_ (&daemon->nnc_lock);
#endif
  MHD_mutex_destroy_chk_ (&daemon->per_ip_connection_mutex);
  free (daemon);
}


/* end of daemon_destroy.c */
