/*
  This file is part of libmicrohttpd
  Copyright (C) 2016 Karlson2k (Evgeny Grin)

  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 microhttpd/mhd_threads.c
 * @brief  Implementation for thread functions
 * @author Karlson2k (Evgeny Grin)
 */

#include "mhd_threads.h"
#ifdef MHD_USE_W32_THREADS
#include "mhd_limits.h"
#include <process.h>
#endif
#ifdef MHD_USE_THREAD_NAME_
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif /* HAVE_STDLIB_H */
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif /* HAVE_PTHREAD_NP_H */
#endif /* MHD_USE_THREAD_NAME_ */
#include <errno.h>


#ifndef MHD_USE_THREAD_NAME_

#define MHD_set_thread_name_(t, n) (void)
#define MHD_set_cur_thread_name_(n) (void)

#else  /* MHD_USE_THREAD_NAME_ */

#if defined(MHD_USE_POSIX_THREADS)
#if defined(HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD) || \
  defined(HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI)
#  define MHD_USE_THREAD_ATTR_SETNAME 1
#endif /* HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD || \
          HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI */

#if defined(HAVE_PTHREAD_SETNAME_NP_GNU) || \
  defined(HAVE_PTHREAD_SET_NAME_NP_FREEBSD) \
  || defined(HAVE_PTHREAD_SETNAME_NP_NETBSD)

/**
 * Set thread name
 *
 * @param thread_id ID of thread
 * @param thread_name name to set
 * @return non-zero on success, zero otherwise
 */
static int
MHD_set_thread_name_ (const MHD_thread_ID_ thread_id,
                      const char *thread_name)
{
  if (NULL == thread_name)
    return 0;

#if defined(HAVE_PTHREAD_SETNAME_NP_GNU)
  return ! pthread_setname_np (thread_id, thread_name);
#elif defined(HAVE_PTHREAD_SET_NAME_NP_FREEBSD)
  /* FreeBSD and OpenBSD use different function name and void return type */
  pthread_set_name_np (thread_id, thread_name);
  return ! 0;
#elif defined(HAVE_PTHREAD_SETNAME_NP_NETBSD)
  /* NetBSD use 3 arguments: second argument is string in printf-like format,
   *                         third argument is a single argument for printf();
   * OSF1 use 3 arguments too, but last one always must be zero (NULL).
   * MHD doesn't use '%' in thread names, so both form are used in same way.
   */
  return ! pthread_setname_np (thread_id, thread_name, 0);
#endif /* HAVE_PTHREAD_SETNAME_NP_NETBSD */
}


#ifndef __QNXNTO__
/**
 * Set current thread name
 * @param n name to set
 * @return non-zero on success, zero otherwise
 */
#define MHD_set_cur_thread_name_(n) MHD_set_thread_name_ (pthread_self (),(n))
#else  /* __QNXNTO__ */
/* Special case for QNX Neutrino - using zero for thread ID sets name faster. */
#define MHD_set_cur_thread_name_(n) MHD_set_thread_name_ (0,(n))
#endif /* __QNXNTO__ */
#elif defined(HAVE_PTHREAD_SETNAME_NP_DARWIN)

/**
 * Set current thread name
 * @param n name to set
 * @return non-zero on success, zero otherwise
 */
#define MHD_set_cur_thread_name_(n) (! (pthread_setname_np ((n))))
#endif /* HAVE_PTHREAD_SETNAME_NP_DARWIN */

#elif defined(MHD_USE_W32_THREADS)
#ifndef _MSC_FULL_VER
/* Thread name available only for VC-compiler */
#else  /* _MSC_FULL_VER */
/**
 * Set thread name
 *
 * @param thread_id ID of thread, -1 for current thread
 * @param thread_name name to set
 * @return non-zero on success, zero otherwise
 */
static int
MHD_set_thread_name_ (const MHD_thread_ID_ thread_id,
                      const char *thread_name)
{
  static const DWORD VC_SETNAME_EXC = 0x406D1388;
#pragma pack(push,8)
  struct thread_info_struct
  {
    DWORD type;   /* Must be 0x1000. */
    LPCSTR name;  /* Pointer to name (in user address space). */
    DWORD ID;     /* Thread ID (-1 = caller thread). */
    DWORD flags;  /* Reserved for future use, must be zero. */
  } thread_info;
#pragma pack(pop)

  if (NULL == thread_name)
    return 0;

  thread_info.type  = 0x1000;
  thread_info.name  = thread_name;
  thread_info.ID    = thread_id;
  thread_info.flags = 0;

  __try
  { /* This exception is intercepted by debugger */
    RaiseException (VC_SETNAME_EXC,
                    0,
                    sizeof (thread_info) / sizeof(ULONG_PTR),
                    (ULONG_PTR *) &thread_info);
  }
  __except (EXCEPTION_EXECUTE_HANDLER)
  {}

  return ! 0;
}


/**
 * Set current thread name
 * @param n name to set
 * @return non-zero on success, zero otherwise
 */
#define MHD_set_cur_thread_name_(n) \
  MHD_set_thread_name_ ((MHD_thread_ID_) -1,(n))
#endif /* _MSC_FULL_VER */
#endif /* MHD_USE_W32_THREADS */

#endif /* MHD_USE_THREAD_NAME_ */


/**
 * Create a thread and set the attributes according to our options.
 *
 * @param thread        handle to initialize
 * @param stack_size    size of stack for new thread, 0 for default
 * @param start_routine main function of thread
 * @param arg argument  for start_routine
 * @return non-zero on success; zero otherwise (with errno set)
 */
int
MHD_create_thread_ (MHD_thread_handle_ID_ *thread,
                    size_t stack_size,
                    MHD_THREAD_START_ROUTINE_ start_routine,
                    void *arg)
{
#if defined(MHD_USE_POSIX_THREADS)
  int res;

  if (0 != stack_size)
  {
    pthread_attr_t attr;
    res = pthread_attr_init (&attr);
    if (0 == res)
    {
      res = pthread_attr_setstacksize (&attr,
                                       stack_size);
      if (0 == res)
        res = pthread_create (&(thread->handle),
                              &attr,
                              start_routine,
                              arg);
      pthread_attr_destroy (&attr);
    }
  }
  else
    res = pthread_create (&(thread->handle),
                          NULL,
                          start_routine,
                          arg);

  if (0 != res)
    errno = res;

  return ! res;
#elif defined(MHD_USE_W32_THREADS)
#if SIZEOF_SIZE_T != SIZEOF_UNSIGNED_INT
  if (stack_size > UINT_MAX)
  {
    errno = EINVAL;
    return 0;
  }
#endif /* SIZEOF_SIZE_T != SIZEOF_UNSIGNED_INT */

  thread->handle = (MHD_thread_handle_)
                   _beginthreadex (NULL,
                                   (unsigned int) stack_size,
                                   start_routine,
                                   arg,
                                   0,
                                   NULL);

  if ((MHD_thread_handle_) - 1 == thread->handle)
    return 0;

  return ! 0;
#endif
}


#ifdef MHD_USE_THREAD_NAME_

#ifndef MHD_USE_THREAD_ATTR_SETNAME
struct MHD_named_helper_param_
{
  /**
   * Real thread start routine
   */
  MHD_THREAD_START_ROUTINE_ start_routine;

  /**
   * Argument for thread start routine
   */
  void *arg;

  /**
   * Name for thread
   */
  const char *name;
};


static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_
named_thread_starter (void *data)
{
  struct MHD_named_helper_param_ *const param =
    (struct MHD_named_helper_param_ *) data;
  void *arg;
  MHD_THREAD_START_ROUTINE_ thr_func;

  if (NULL == data)
    return (MHD_THRD_RTRN_TYPE_) 0;

  MHD_set_cur_thread_name_ (param->name);

  arg = param->arg;
  thr_func = param->start_routine;
  free (data);

  return thr_func (arg);
}


#endif /* ! MHD_USE_THREAD_ATTR_SETNAME */


/**
 * Create a named thread and set the attributes according to our options.
 *
 * @param thread        handle to initialize
 * @param thread_name   name for new thread
 * @param stack_size    size of stack for new thread, 0 for default
 * @param start_routine main function of thread
 * @param arg argument  for start_routine
 * @return non-zero on success; zero otherwise (with errno set)
 */
int
MHD_create_named_thread_ (MHD_thread_handle_ID_ *thread,
                          const char *thread_name,
                          size_t stack_size,
                          MHD_THREAD_START_ROUTINE_ start_routine,
                          void *arg)
{
#if defined(MHD_USE_THREAD_ATTR_SETNAME)
  int res;
  pthread_attr_t attr;

  res = pthread_attr_init (&attr);
  if (0 == res)
  {
#if defined(HAVE_PTHREAD_ATTR_SETNAME_NP_NETBSD)
    /* NetBSD use 3 arguments: second argument is string in printf-like format,
     *                         third argument is single argument for printf;
     * OSF1 use 3 arguments too, but last one always must be zero (NULL).
     * MHD doesn't use '%' in thread names, so both form are used in same way.
     */
    res = pthread_attr_setname_np (&attr,
                                   thread_name,
                                   0);
#elif defined(HAVE_PTHREAD_ATTR_SETNAME_NP_IBMI)
    res = pthread_attr_setname_np (&attr,
                                   thread_name);
#else
#error No pthread_attr_setname_np() function.
#endif
    if ((res == 0) && (0 != stack_size) )
      res = pthread_attr_setstacksize (&attr,
                                       stack_size);
    if (0 == res)
      res = pthread_create (&(thread->handle),
                            &attr,
                            start_routine,
                            arg);
    pthread_attr_destroy (&attr);
  }
  if (0 != res)
    errno = res;

  return ! res;
#else  /* ! MHD_USE_THREAD_ATTR_SETNAME */
  struct MHD_named_helper_param_ *param;

  if (NULL == thread_name)
  {
    errno = EINVAL;
    return 0;
  }

  param = malloc (sizeof (struct MHD_named_helper_param_));
  if (NULL == param)
    return 0;

  param->start_routine = start_routine;
  param->arg = arg;
  param->name = thread_name;

  /* Set thread name in thread itself to avoid problems with
   * threads which terminated before name is set in other thread.
   */
  if (! MHD_create_thread_ (thread,
                            stack_size,
                            &named_thread_starter,
                            (void *) param))
  {
    free (param);
    return 0;
  }

  return ! 0;
#endif /* ! MHD_USE_THREAD_ATTR_SETNAME */
}


#endif /* MHD_USE_THREAD_NAME_ */
