/*
 * Copyright (C) 2018 - 2026, Stephan Mueller <smueller@chronox.de>
 *
 * License: see COPYING file in root directory
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
 * WHICH ARE HEREBY DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

#ifndef _MUTEX_W_PTHREAD_H
#define _MUTEX_W_PTHREAD_H

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>

#include "bool.h"
#include "ret_checkers.h"

/**
 * @brief Reader / Writer mutex based on pthread
 */
typedef struct {
	pthread_mutex_t lock;
	int ma_used;
	pthread_mutexattr_t ma;
	int robust;
} mutex_w_t;

#define MUTEX_W_UNLOCKED { .lock = PTHREAD_MUTEX_INITIALIZER, .ma_used = 0 }
#define DEFINE_MUTEX_W_UNLOCKED(name) mutex_w_t name = MUTEX_W_UNLOCKED

#define DEFINE_MUTEX_W_LOCKED(name) error "DEFINE_MUTEX_LOCKED not implemented"

/**
 * Mutual exclusion lock (covering also the reader lock use case).
 * @param [in] mutex lock variable to lock
 */
static inline int mutex_w_lock(mutex_w_t *mutex)
{
	int ret = pthread_mutex_lock(&mutex->lock);
	if (mutex->robust) {
		assert(ret == 0 || ret == EOWNERDEAD);
	} else {
		assert(ret == 0);
	}
	if (ret == EOWNERDEAD) {
		pthread_mutex_consistent(&mutex->lock);
	}
	return ret;
}

/**
 * Unlock the lock
 * @param [in] mutex lock variable to lock
 */
static inline int mutex_w_unlock(mutex_w_t *mutex)
{
	int ret = pthread_mutex_unlock(&mutex->lock);
	if (mutex->robust) {
		assert(ret == 0 || ret == EOWNERDEAD);
	} else {
		assert(ret == 0);
	}
	if (ret == EOWNERDEAD) {
		pthread_mutex_consistent(&mutex->lock);
	}
	return ret;
}

/**
 * @brief Initialize a mutex
 * @param [in] mutex Lock variable to initialize.
 * @param [in] locked Specify whether the lock shall already be locked (1)
 *		      or unlocked (0).
 * @param [in] robust initialize a robust mutex (1) or not (0)
 */
static inline int mutex_w_init(mutex_w_t *mutex, int locked, int robust)
{
	int ret = 0;

	CKINT(pthread_mutexattr_init(&mutex->ma));
	mutex->ma_used = 1;

	if (robust) {
		CKINT(pthread_mutexattr_setrobust(&mutex->ma,
						  PTHREAD_MUTEX_ROBUST));
		mutex->robust = 1;
	}

	CKINT(pthread_mutex_init(&mutex->lock, &mutex->ma));

	if (locked)
		CKINT(mutex_w_lock(mutex));

out:
	return ret;
}

static inline int mutex_w_destroy(mutex_w_t *mutex)
{
	int ret = 0;

	CKINT(pthread_mutex_destroy(&mutex->lock));
	if (mutex->ma_used)
		CKINT(pthread_mutexattr_destroy(&mutex->ma));

out:
	return ret;
}

/**
 * Mutual exclusion lock: Attempt to take the lock. The function will never
 * block but return whether the lock was successfully taken or not.
 *
 * @param [in] mutex lock variable to lock
 * @return true if lock was taken, false if lock was not taken
 */
static inline int mutex_w_trylock(mutex_w_t *mutex)
{
	int ret = pthread_mutex_trylock(&mutex->lock);
	assert(ret == 0 || ret == EBUSY);
	(void)ret;
	return ret;
}

/*
 * both current glibc and musl libc implement a clock-based
 * timed locking, which can use a monotonic clock.
*/
extern int pthread_mutex_clocklock(pthread_mutex_t *mutex, clockid_t clockid,
				   const struct timespec *abstime);

static inline int mutex_w_timedlock(mutex_w_t *mutex,
				    const struct timespec *abstime)
{
	int ret =
		pthread_mutex_clocklock(&mutex->lock, CLOCK_MONOTONIC, abstime);
	assert(ret == 0 || ret == ETIMEDOUT);
	(void)ret;
	return ret;
}

#endif /* _MUTEX_W_PTHREAD_H */
