/*
  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_close_all_connections.c
 * @brief function to close all connections open at a daemon
 * @author Christian Grothoff
 */
#include "internal.h"
#include "connection_cleanup.h"
#include "connection_close.h"
#include "connection_finish_forward.h"
#include "daemon_close_all_connections.h"
#include "request_resume.h"
#include "upgrade_process.h"


/**
 * Close the given connection, remove it from all of its
 * DLLs and move it into the cleanup queue.
 * @remark To be called only from thread that
 * process daemon's select()/poll()/etc.
 *
 * @param pos connection to move to cleanup
 */
static void
close_connection (struct MHD_Connection *pos)
{
  struct MHD_Daemon *daemon = pos->daemon;

  if (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_mode)
  {
    MHD_connection_mark_closed_ (pos);
    return;   /* must let thread to do the rest */
  }
  MHD_connection_close_ (pos,
                         MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN);

  MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);

  mhd_assert (! pos->suspended);
  mhd_assert (! pos->resuming);
  if (pos->connection_timeout ==
      pos->daemon->connection_default_timeout)
    XDLL_remove (daemon->normal_timeout_head,
                 daemon->normal_timeout_tail,
                 pos);
  else
    XDLL_remove (daemon->manual_timeout_head,
                 daemon->manual_timeout_tail,
                 pos);
  DLL_remove (daemon->connections_head,
              daemon->connections_tail,
              pos);
  DLL_insert (daemon->cleanup_head,
              daemon->cleanup_tail,
              pos);

  MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
}


/**
 * Close all connections for the daemon.  Must only be called when
 * MHD_Daemon::shutdown was set to true.
 *
 * @remark To be called only from thread that process daemon's
 * select()/poll()/etc.
 *
 * @param daemon daemon to close down
 */
void
MHD_daemon_close_all_connections_ (struct MHD_Daemon *daemon)
{
  struct MHD_Connection *pos;
  const bool used_thr_p_c = (MHD_TM_THREAD_PER_CONNECTION ==
                             daemon->threading_mode);
#ifdef UPGRADE_SUPPORT
  const bool upg_allowed = (! daemon->disallow_upgrade);
#endif /* UPGRADE_SUPPORT */
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
  struct MHD_UpgradeResponseHandle *urh;
  struct MHD_UpgradeResponseHandle *urhn;
  const bool used_tls = (NULL != daemon->tls_api);

  mhd_assert (NULL == daemon->worker_pool);
  mhd_assert (daemon->shutdown);
  /* give upgraded HTTPS connections a chance to finish */
  /* 'daemon->urh_head' is not used in thread-per-connection mode. */
  for (urh = daemon->urh_tail; NULL != urh; urh = urhn)
  {
    urhn = urh->prev;
    /* call generic forwarding function for passing data
       with chance to detect that application is done. */
    MHD_upgrade_response_handle_process_ (urh);
    MHD_connection_finish_forward_ (urh->connection);
    urh->clean_ready = true;
    /* Resuming will move connection to cleanup list. */
    MHD_request_resume (&urh->connection->request);
  }
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */

  /* Give suspended connections a chance to resume to avoid
     running into the check for there not being any suspended
     connections left in case of a tight race with a recently
     resumed connection. */
  if (! daemon->disallow_suspend_resume)
  {
    daemon->resuming = true;   /* Force check for pending resume. */
    MHD_resume_suspended_connections_ (daemon);
  }
  /* first, make sure all threads are aware of shutdown; need to
     traverse DLLs in peace... */
  MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);
#ifdef UPGRADE_SUPPORT
  if (upg_allowed)
  {
    struct MHD_Connection *susp;

    susp = daemon->suspended_connections_tail;
    while (NULL != susp)
    {
      if (NULL == susp->request.urh)     /* "Upgraded" connection? */
        MHD_PANIC (_ (
                     "MHD_stop_daemon() called while we have suspended connections.\n"));
#ifdef HTTPS_SUPPORT
      else if (used_tls &&
               used_thr_p_c &&
               (! susp->request.urh->clean_ready) )
        shutdown (susp->request.urh->app.socket,
                  SHUT_RDWR);     /* Wake thread by shutdown of app socket. */
#endif /* HTTPS_SUPPORT */
      else
      {
#ifdef HAVE_MESSAGES
        if (! susp->request.urh->was_closed)
          MHD_DLOG (daemon,
                    MHD_SC_SHUTDOWN_WITH_OPEN_UPGRADED_CONNECTION,
                    _ (
                      "Initiated daemon shutdown while \"upgraded\" connection was not closed.\n"));
#endif
        susp->request.urh->was_closed = true;
        /* If thread-per-connection is used, connection's thread
         * may still processing "upgrade" (exiting). */
        if (! used_thr_p_c)
          MHD_connection_finish_forward_ (susp);
        /* Do not use MHD_resume_connection() as mutex is
         * already locked. */
        susp->resuming = true;
        daemon->resuming = true;
      }
      susp = susp->prev;
    }
  }
  else /* This 'else' is combined with next 'if' */
#endif /* UPGRADE_SUPPORT */
  if (NULL != daemon->suspended_connections_head)
    MHD_PANIC (_ (
                 "MHD_stop_daemon() called while we have suspended connections.\n"));
  for (pos = daemon->connections_tail; NULL != pos; pos = pos->prev)
  {
    shutdown (pos->socket_fd,
              SHUT_RDWR);
#if MHD_WINSOCK_SOCKETS
    if ( (used_thr_p_c) &&
         (MHD_ITC_IS_VALID_ (daemon->itc)) &&
         (! MHD_itc_activate_ (daemon->itc,
                               "e")) )
      MHD_PANIC (_ (
                   "Failed to signal shutdown via inter-thread communication channel.\n"));
#endif
  }

  /* now, collect per-connection threads */
  if (used_thr_p_c)
  {
    pos = daemon->connections_tail;
    while (NULL != pos)
    {
      if (! pos->thread_joined)
      {
        MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);
        if (! MHD_join_thread_ (pos->pid.handle))
          MHD_PANIC (_ ("Failed to join a thread.\n"));
        MHD_mutex_lock_chk_ (&daemon->cleanup_connection_mutex);
        pos->thread_joined = true;
        /* The thread may have concurrently modified the DLL,
           need to restart from the beginning */
        pos = daemon->connections_tail;
        continue;
      }
      pos = pos->prev;
    }
  }
  MHD_mutex_unlock_chk_ (&daemon->cleanup_connection_mutex);

#ifdef UPGRADE_SUPPORT
  /* Finished threads with "upgraded" connections need to be moved
   * to cleanup list by resume_suspended_connections(). */
  /* "Upgraded" connections that were not closed explicitly by
   * application should be moved to cleanup list too. */
  if (upg_allowed)
  {
    daemon->resuming = true;   /* Force check for pending resume. */
    MHD_resume_suspended_connections_ (daemon);
  }
#endif /* UPGRADE_SUPPORT */

  /* now that we're alone, move everyone to cleanup */
  while (NULL != (pos = daemon->connections_tail))
  {
    if ( (used_thr_p_c) &&
         (! pos->thread_joined) )
      MHD_PANIC (_ ("Failed to join a thread.\n"));
    close_connection (pos);
  }
  MHD_connection_cleanup_ (daemon);
}


/* end of daemon_close_all_connections.c */
