/*
  This file is part of libmicrohttpd
  Copyright (C) 2016-2022 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_locks.h
 * @brief  Header for platform-independent locks abstraction
 * @author Karlson2k (Evgeny Grin)
 * @author Christian Grothoff
 *
 * Provides basic abstraction for locks/mutex.
 * Any functions can be implemented as macro on some platforms
 * unless explicitly marked otherwise.
 * Any function argument can be skipped in macro, so avoid
 * variable modification in function parameters.
 *
 * @warning Unlike pthread functions, most of functions return
 *          nonzero on success.
 */

#ifndef MHD_LOCKS_H
#define MHD_LOCKS_H 1

#include "mhd_options.h"

#ifdef MHD_USE_THREADS

#if defined(MHD_USE_W32_THREADS)
#  define MHD_W32_MUTEX_ 1
#  ifndef WIN32_LEAN_AND_MEAN
#    define WIN32_LEAN_AND_MEAN 1
#  endif /* !WIN32_LEAN_AND_MEAN */
#  include <windows.h>
#elif defined(HAVE_PTHREAD_H) && defined(MHD_USE_POSIX_THREADS)
#  define MHD_PTHREAD_MUTEX_ 1
#  undef HAVE_CONFIG_H
#  include <pthread.h>
#  define HAVE_CONFIG_H 1
#else
#  error No base mutex API is available.
#endif

#ifndef MHD_PANIC
#  include <stdio.h>
#  ifdef HAVE_STDLIB_H
#    include <stdlib.h>
#  endif /* HAVE_STDLIB_H */
/* Simple implementation of MHD_PANIC, to be used outside lib */
#  define MHD_PANIC(msg) \
  do { fprintf (stderr,           \
       "Abnormal termination at %d line in file %s: %s\n", \
       (int) __LINE__, __FILE__, msg); abort (); \
  } while (0)
#endif /* ! MHD_PANIC */

#if defined(MHD_PTHREAD_MUTEX_)
typedef pthread_mutex_t MHD_mutex_;
#elif defined(MHD_W32_MUTEX_)
typedef CRITICAL_SECTION MHD_mutex_;
#endif

#if defined(MHD_PTHREAD_MUTEX_)
/**
 * Initialise new mutex.
 * @param pmutex pointer to the mutex
 * @return nonzero on success, zero otherwise
 */
#define MHD_mutex_init_(pmutex) (! (pthread_mutex_init ((pmutex), NULL)))
#elif defined(MHD_W32_MUTEX_)
/**
 * Initialise new mutex.
 * @param pmutex pointer to mutex
 * @return nonzero on success, zero otherwise
 */
#define MHD_mutex_init_(pmutex) \
  (InitializeCriticalSectionAndSpinCount ((pmutex),16))
#endif

#if defined(MHD_PTHREAD_MUTEX_)
#  if defined(PTHREAD_MUTEX_INITIALIZER)
/**
 *  Define static mutex and statically initialise it.
 */
#    define MHD_MUTEX_STATIC_DEFN_INIT_(m) \
  static MHD_mutex_ m = PTHREAD_MUTEX_INITIALIZER
#  endif /* PTHREAD_MUTEX_INITIALIZER */
#endif

#if defined(MHD_PTHREAD_MUTEX_)
/**
 * Destroy previously initialised mutex.
 * @param pmutex pointer to mutex
 * @return nonzero on success, zero otherwise
 */
#define MHD_mutex_destroy_(pmutex) (! (pthread_mutex_destroy ((pmutex))))
#elif defined(MHD_W32_MUTEX_)
/**
 * Destroy previously initialised mutex.
 * @param pmutex pointer to mutex
 * @return Always nonzero
 */
#define MHD_mutex_destroy_(pmutex) (DeleteCriticalSection ((pmutex)), ! 0)
#endif

/**
 * Destroy previously initialised mutex and abort execution
 * if error is detected.
 * @param pmutex pointer to mutex
 */
#define MHD_mutex_destroy_chk_(pmutex) do {       \
    if (! MHD_mutex_destroy_ (pmutex))              \
      MHD_PANIC (_ ("Failed to destroy mutex.\n")); \
  } while (0)


#if defined(MHD_PTHREAD_MUTEX_)
/**
 * Acquire lock on previously initialised mutex.
 * If mutex was already locked by other thread, function
 * blocks until mutex becomes available.
 * @param pmutex pointer to mutex
 * @return nonzero on success, zero otherwise
 */
#define MHD_mutex_lock_(pmutex) (! (pthread_mutex_lock ((pmutex))))
#elif defined(MHD_W32_MUTEX_)
/**
 * Acquire lock on previously initialised mutex.
 * If mutex was already locked by other thread, function
 * blocks until mutex becomes available.
 * @param pmutex pointer to mutex
 * @return Always nonzero
 */
#define MHD_mutex_lock_(pmutex) (EnterCriticalSection ((pmutex)), ! 0)
#endif

/**
 * Acquire lock on previously initialised mutex.
 * If mutex was already locked by other thread, function
 * blocks until mutex becomes available.
 * If error is detected, execution will be aborted.
 * @param pmutex pointer to mutex
 */
#define MHD_mutex_lock_chk_(pmutex) do {       \
    if (! MHD_mutex_lock_ (pmutex))              \
      MHD_PANIC (_ ("Failed to lock mutex.\n")); \
} while (0)

#if defined(MHD_PTHREAD_MUTEX_)
/**
 * Unlock previously initialised and locked mutex.
 * @param pmutex pointer to mutex
 * @return nonzero on success, zero otherwise
 */
#define MHD_mutex_unlock_(pmutex) (! (pthread_mutex_unlock ((pmutex))))
#elif defined(MHD_W32_MUTEX_)
/**
 * Unlock previously initialised and locked mutex.
 * @param pmutex pointer to mutex
 * @return Always nonzero
 */
#define MHD_mutex_unlock_(pmutex) (LeaveCriticalSection ((pmutex)), ! 0)
#endif

/**
 * Unlock previously initialised and locked mutex.
 * If error is detected, execution will be aborted.
 * @param pmutex pointer to mutex
 */
#define MHD_mutex_unlock_chk_(pmutex) do {       \
    if (! MHD_mutex_unlock_ (pmutex))              \
      MHD_PANIC (_ ("Failed to unlock mutex.\n")); \
} while (0)

#else  /* ! MHD_USE_THREADS */

#define MHD_mutex_init_(ignore) (! 0)
#define MHD_mutex_destroy_(ignore) (! 0)
#define MHD_mutex_destroy_chk_(ignore) (void)0
#define MHD_mutex_lock_(ignore) (! 0)
#define MHD_mutex_lock_chk_(ignore) (void)0
#define MHD_mutex_unlock_(ignore) (! 0)
#define MHD_mutex_unlock_chk_(ignore) (void)0

#endif /* ! MHD_USE_THREADS */

#endif /* ! MHD_LOCKS_H */
