# SYNOPSIS
#
#   MHD_CHECK_SOCKET_SHUTDOWN_TRIGGER([ACTION-IF-TRIGGER], [ACTION-IF-NOT],
#                                     [ACTION-IF-UNKNOWN])
#
# DESCRIPTION
#
#   Check whether shutdown of listen socket triggers waiting select().
#   If cross-compiling, result may be unknown (third action).
#   Result is cached in $mhd_cv_host_shtdwn_trgr_select variable.
#
# LICENSE
#
#   Copyright (c) 2017-2023 Karlson2k (Evgeny Grin) <k2k@narod.ru>
#
#   Copying and distribution of this file, with or without modification, are
#   permitted in any medium without royalty provided the copyright notice
#   and this notice are preserved. This file is offered as-is, without any
#   warranty.

#serial 6

AC_DEFUN([MHD_CHECK_SOCKET_SHUTDOWN_TRIGGER],[dnl
  AC_PREREQ([2.64])dnl
  AC_REQUIRE([AC_CANONICAL_HOST])dnl
  AC_REQUIRE([AC_PROG_CC])dnl
  AC_REQUIRE([AX_PTHREAD])dnl
  AC_REQUIRE([MHD_CHECK_FUNC_GETTIMEOFDAY])dnl
  MHD_CHECK_FUNC([[usleep]], [[#include <unistd.h>]], [[usleep(100000);]])
  MHD_CHECK_FUNC([[nanosleep]], [[#include <time.h>]], [[struct timespec ts2, ts1 = {0, 0}; nanosleep(&ts1, &ts2);]])
  AC_CHECK_HEADERS([string.h sys/types.h sys/socket.h netinet/in.h time.h sys/select.h netinet/tcp.h],[],[], [AC_INCLUDES_DEFAULT])
  AC_CACHE_CHECK([[whether shutdown of listen socket triggers select()]],
    [[mhd_cv_host_shtdwn_trgr_select]], [dnl
    _MHD_OS_KNOWN_SOCKET_SHUTDOWN_TRIGGER([[mhd_cv_host_shtdwn_trgr_select]])
    AS_VAR_IF([mhd_cv_host_shtdwn_trgr_select], [["maybe"]],
      [_MHD_RUN_CHECK_SOCKET_SHUTDOWN_TRIGGER([[mhd_cv_host_shtdwn_trgr_select]])])
    ]
  )
  AS_IF([[test "x$mhd_cv_host_shtdwn_trgr_select" = "xyes"]], [$1],
    [[test "x$mhd_cv_host_shtdwn_trgr_select" = "xno"]], [$2], [$3])
  ]
)

#
# _MHD_OS_KNOWN_SOCKET_SHUTDOWN_TRIGGER(VAR)
#
# Sets VAR to 'yes', 'no' or 'maybe'.

AC_DEFUN([_MHD_OS_KNOWN_SOCKET_SHUTDOWN_TRIGGER],[dnl
[#] On Linux shutdown of listen socket always trigger select().
[#] On Windows select() always ignore shutdown of listen socket.
[#] On other paltforms result may vary depending on platform version.
  AS_CASE([[$host_os]],
    [[linux | linux-* | *-linux | *-linux-*]], [$1='yes'],
    [[mingw*]], [$1='no'],
    [[cygwin* | msys*]], [$1='no'],
    [[winnt* | interix*]], [$1='no'],
    [[mks]], [$1='no'],
    [[uwin]], [$1='no'],
    [$1='maybe']
  )
  ]
)

#
# _MHD_RUN_CHECK_SOCKET_SHUTDOWN_TRIGGER(VAR)
#
# Sets VAR to 'yes', 'no' or 'guessing no'.

AC_DEFUN([_MHD_RUN_CHECK_SOCKET_SHUTDOWN_TRIGGER],[dnl
  AC_LANG_PUSH([C])
  MHD_CST_SAVE_CC="$CC"
  MHD_CST_SAVE_CFLAGS="$CFLAGS"
  MHD_CST_SAVE_LIBS="$LIBS"
  CC="$PTHREAD_CC"
  CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
  LIBS="$PTHREAD_LIBS $LIBS"
  AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <stdlib.h>

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_TIME_H
#  include <time.h>
#endif
#ifdef HAVE_STRING_H
#  include <string.h>
#endif

#if !defined(_WIN32) || defined(__CYGWIN__)
#  ifdef HAVE_SYS_TYPES_H
#    include <sys/types.h>
#  endif
#  ifdef HAVE_SYS_SOCKET_H
#    include <sys/socket.h>
#  endif
#  ifdef HAVE_NETINET_IN_H
#    include <netinet/in.h>
#  endif
#  ifdef HAVE_SYS_TIME_H
#    include <sys/time.h>
#  endif
#  ifdef HAVE_SYS_SELECT_H
#    include <sys/select.h>
#  endif
#  ifdef HAVE_NETINET_TCP_H
#    include <netinet/tcp.h>
#  endif
   typedef int MHD_socket;
#  define MHD_INVALID_SOCKET (-1)
#  define MHD_POSIX_SOCKETS 1
#else
#  include <winsock2.h>
#  include <ws2tcpip.h>
#  include <windows.h>
   typedef SOCKET MHD_socket;
#  define MHD_INVALID_SOCKET (INVALID_SOCKET)
#  define MHD_WINSOCK_SOCKETS 1
#endif

#include <pthread.h>

#ifndef SHUT_RD
#  define SHUT_RD 0
#endif
#ifndef SHUT_WR
#  define SHUT_WR 1
#endif
#ifndef SHUT_RDWR
#  define SHUT_RDWR 2
#endif

#ifndef NULL
#  define NULL ((void*)0)
#endif

#ifdef HAVE_GETTIMEOFDAY
#  if defined(_WIN32) && !defined(__CYGWIN__)
#    undef HAVE_GETTIMEOFDAY
#  endif
#endif


#ifdef HAVE_NANOSLEEP
static const struct timespec sm_tmout = {0, 1000};
#  define short_sleep() nanosleep(&sm_tmout, NULL)
#elif defined(HAVE_USLEEP)
#  define short_sleep() usleep(1)
#else
#  define short_sleep() (void)0
#endif

static volatile int going_select = 0;
static volatile int select_ends = 0;
static volatile int gerror = 0;
static int timeout_mils;

#ifndef HAVE_GETTIMEOFDAY
static volatile long long select_elapsed_time = 0;

static long long time_chk(void)
{
  long long ret = time(NULL);
  if (-1 == ret)
    gerror = 4;
  return ret;
}
#endif


static void* select_thrd_func(void* param)
{
#ifndef HAVE_GETTIMEOFDAY
  long long start, stop;
#endif
  fd_set rs;
  struct timeval tmot = {0, 0};
  MHD_socket fd = *((MHD_socket*)param);

  FD_ZERO(&rs);
  FD_SET(fd, &rs);
  tmot.tv_usec = timeout_mils * 1000;
#ifndef HAVE_GETTIMEOFDAY
  start = time_chk();
#endif
  going_select = 1;
  if (0 > select ((int)(fd) + 1, &rs, NULL, NULL, &tmot))
    gerror = 5;
#ifndef HAVE_GETTIMEOFDAY
  stop = time_chk();
  select_elapsed_time = stop - start;
#endif
  select_ends = 1;
  return NULL;
}


static MHD_socket create_socket(void)
{ return socket (AF_INET, SOCK_STREAM, 0); }

static void close_socket(MHD_socket fd)
{
#ifdef MHD_POSIX_SOCKETS
  close(fd);
#else
  closesocket(fd);
#endif
}

static MHD_socket
create_socket_listen(int port)
{
  MHD_socket fd;
  struct sockaddr_in sock_addr;
  fd = create_socket();
  if (MHD_INVALID_SOCKET == fd)
    return fd;

  memset (&sock_addr, 0, sizeof (struct sockaddr_in));
  sock_addr.sin_family = AF_INET;
  sock_addr.sin_port = htons(port);
  sock_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  if (bind (fd, (const struct sockaddr*) &sock_addr, sizeof(sock_addr)) < 0 ||
      listen(fd, SOMAXCONN) < 0)
    {
      close_socket(fd);
      return MHD_INVALID_SOCKET;
    }
  return fd;
}

#ifdef HAVE_GETTIMEOFDAY
#define diff_time(tv1, tv2) ((long long)(tv1.tv_sec-tv2.tv_sec)*10000 + (long long)(tv1.tv_usec-tv2.tv_usec)/100)
#else
#define diff_time(tv1, tv2) ((long long)(tv1-tv2))
#endif

static long long test_run_select(int timeout_millsec, int use_shutdown, long long delay_before_shutdown)
{
  pthread_t select_thrd;
  MHD_socket fd;
#ifdef HAVE_GETTIMEOFDAY
  struct timeval start, stop;
#else
  long long start;
#endif

  fd = create_socket_listen(0);
  if (MHD_INVALID_SOCKET == fd)
    return -7;
  going_select = 0;
  select_ends = 0;
  gerror = 0;
  timeout_mils = timeout_millsec;
  if (0 != pthread_create (&select_thrd, NULL, select_thrd_func, (void*)&fd))
    return -8;
  while (!going_select) {short_sleep();}
#ifdef HAVE_GETTIMEOFDAY
  gettimeofday (&start, NULL);
#else
  start = time_chk();
#endif
  if (use_shutdown)
    {
#ifdef HAVE_GETTIMEOFDAY
      struct timeval current;
      do {short_sleep(); gettimeofday(&current, NULL); } while (delay_before_shutdown > diff_time(current, start));
#else
      while (delay_before_shutdown > time_chk() - start) {short_sleep();}
#endif
      shutdown(fd, SHUT_RDWR);
    }
#ifdef HAVE_GETTIMEOFDAY
  while (!select_ends) {short_sleep();}
  gettimeofday (&stop, NULL);
#endif
  if (0 != pthread_join(select_thrd, NULL))
    return -9;
  close_socket(fd);
  if (gerror)
    return -10;
#ifdef HAVE_GETTIMEOFDAY
  return (long long)diff_time(stop, start);
#else
  return select_elapsed_time;
#endif
}

static int test_it(void)
{
  long long duration2;
#ifdef HAVE_GETTIMEOFDAY
  long long duration0, duration1;
  duration0 = test_run_select(0, 0, 0);
  if (0 > duration0)
    return -duration0;

  duration1 = test_run_select(50, 0, 0);
  if (0 > duration1)
    return -duration1 + 20;

  duration2 = test_run_select(500, 1, (duration0 + duration1) / 2);
  if (0 > duration2)
    return -duration2 + 40;

  if (duration1 * 2 > duration2)
    { /* Check second time to be sure. */
      duration2 = test_run_select(500, 1, (duration0 + duration1) / 2);
      if (0 > duration2)
        return -duration2 + 60;
      if (duration1 * 2 > duration2)
        return 0;
    }
#else
  duration2 = test_run_select(5000, 1, 2);
  if (0 > duration2)
    return -duration2 + 80;

  if (4 > duration2)
    { /* Check second time to be sure. */
      duration2 = test_run_select(5000, 1, 2);
      if (0 > duration2)
      return -duration2 + 100;
      if (4 > duration2)
        return 0;
    }
#endif
  return 1;
}


static int init(void)
{
#ifdef MHD_WINSOCK_SOCKETS
  WSADATA wsa_data;

  if (0 != WSAStartup(MAKEWORD(2, 2), &wsa_data) || MAKEWORD(2, 2) != wsa_data.wVersion)
    {
      WSACleanup();
      return 0;
    }
#endif /* MHD_WINSOCK_SOCKETS */
  return 1;
}

static void cleanup(void)
{
#ifdef MHD_WINSOCK_SOCKETS
  WSACleanup();
#endif /* MHD_WINSOCK_SOCKETS */
}

int main(void)
{
  int res;
  if (!init())
    return 19;

  res = test_it();

  cleanup();
  if (gerror)
    return gerror;

  return res;
}
]])], [$1='yes'], [$1='no'], [$1='guessing no'])
  CC="$MHD_CST_SAVE_CC"
  CFLAGS="$MHD_CST_SAVE_CFLAGS"
  LIBS="$MHD_CST_SAVE_LIBS"
  AS_UNSET([[MHD_CST_SAVE_CC]])
  AS_UNSET([[MHD_CST_SAVE_CFLAGS]])
  AS_UNSET([[MHD_CST_SAVE_LIBS]])
  AC_LANG_POP([C])
  ]
)
