⛏️ index : haiku.git

/*
 * Copyright 2015, Hamish Morrison, hamishm53@gmail.com.
 * Copyright 2008-2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include <semaphore.h>

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <pthread.h>

#include <OS.h>

#include <AutoDeleter.h>
#include <errno_private.h>
#include <posix/realtime_sem_defs.h>
#include <syscall_utils.h>
#include <syscalls.h>
#include <time_private.h>
#include <user_mutex_defs.h>


#define SEM_TYPE_NAMED		1
#define SEM_TYPE_UNNAMED	2
#define SEM_TYPE_UNNAMED_SHARED 3


static int32
atomic_add_if_greater(int32* value, int32 amount, int32 testValue)
{
	int32 current = atomic_get(value);
	while (current > testValue) {
		int32 old = atomic_test_and_set(value, current + amount, current);
		if (old == current)
			return old;
		current = old;
	}
	return current;
}


sem_t*
sem_open(const char* name, int openFlags,...)
{
	if (name == NULL) {
		__set_errno(B_BAD_VALUE);
		return SEM_FAILED;
	}

	// get the mode and semaphore count parameters, if O_CREAT is specified
	mode_t mode = 0;
	unsigned semCount = 0;

	if ((openFlags & O_CREAT) != 0) {
		va_list args;
		va_start(args, openFlags);
		mode = va_arg(args, mode_t);
		semCount = va_arg(args, unsigned);
		va_end(args);
	} else {
		// clear O_EXCL, if O_CREAT is not given
		openFlags &= ~O_EXCL;
	}

	// Allocate a sem_t structure -- we don't know, whether this is the first
	// call of this process to open the semaphore. If it is, we will keep the
	// structure, otherwise we will delete it later.
	sem_t* sem = (sem_t*)malloc(sizeof(sem_t));
	if (sem == NULL) {
		__set_errno(B_NO_MEMORY);
		return SEM_FAILED;
	}

	sem->type = SEM_TYPE_NAMED;
	MemoryDeleter semDeleter(sem);

	// ask the kernel to open the semaphore
	sem_t* usedSem;
	status_t error = _kern_realtime_sem_open(name, openFlags, mode, semCount,
		sem, &usedSem);
	if (error != B_OK) {
		__set_errno(error);
		return SEM_FAILED;
	}

	if (usedSem == sem)
		semDeleter.Detach();

	return usedSem;
}


int
sem_close(sem_t* semaphore)
{
	sem_t* deleteSem = NULL;
	status_t error = _kern_realtime_sem_close(semaphore->u.named_sem_id,
		&deleteSem);
	if (error == B_OK)
		free(deleteSem);

	RETURN_AND_SET_ERRNO(error);
}


int
sem_unlink(const char* name)
{
	RETURN_AND_SET_ERRNO(_kern_realtime_sem_unlink(name));
}


int
sem_init(sem_t* semaphore, int shared, unsigned value)
{
	semaphore->type = shared ? SEM_TYPE_UNNAMED_SHARED : SEM_TYPE_UNNAMED;
	semaphore->u.unnamed_sem = value;
	return 0;
}


int
sem_destroy(sem_t* semaphore)
{
	if (semaphore->type != SEM_TYPE_UNNAMED && semaphore->type != SEM_TYPE_UNNAMED_SHARED)
		RETURN_AND_SET_ERRNO(EINVAL);

	return 0;
}


static int
unnamed_sem_post(sem_t* semaphore)
{
	int32* sem = (int32*)&semaphore->u.unnamed_sem;
	int32 oldValue = atomic_add_if_greater(sem, 1, -1);
	if (oldValue > -1)
		return 0;

	uint32 flags = 0;
	if (semaphore->type == SEM_TYPE_UNNAMED_SHARED)
		flags |= B_USER_MUTEX_SHARED;

	return _kern_mutex_sem_release(sem, flags);
}


static int
unnamed_sem_trywait(sem_t* semaphore)
{
	int32* sem = (int32*)&semaphore->u.unnamed_sem;
	int32 oldValue = atomic_add_if_greater(sem, -1, 0);
	if (oldValue > 0)
		return 0;

	return EAGAIN;
}


static int
unnamed_sem_timedwait(sem_t* semaphore, clockid_t clock_id,
	const struct timespec* timeout)
{
	int32* sem = (int32*)&semaphore->u.unnamed_sem;

	bigtime_t timeoutMicros = B_INFINITE_TIMEOUT;
	uint32 flags = 0;
	if (semaphore->type == SEM_TYPE_UNNAMED_SHARED)
		flags |= B_USER_MUTEX_SHARED;
	if (timeout != NULL) {
		if (!timespec_to_bigtime(*timeout, timeoutMicros))
			timeoutMicros = -1;

		switch (clock_id) {
			case CLOCK_REALTIME:
				flags |= B_ABSOLUTE_REAL_TIME_TIMEOUT;
				break;
			case CLOCK_MONOTONIC:
				flags |= B_ABSOLUTE_TIMEOUT;
				break;
			default:
				return EINVAL;
		}
	}

	int result = unnamed_sem_trywait(semaphore);
	if (result == 0)
		return 0;
	if (timeoutMicros < 0)
		return EINVAL;

	return _kern_mutex_sem_acquire(sem, NULL, flags, timeoutMicros);
}


int
sem_post(sem_t* semaphore)
{
	status_t error;
	if (semaphore->type == SEM_TYPE_NAMED)
		error = _kern_realtime_sem_post(semaphore->u.named_sem_id);
	else
		error = unnamed_sem_post(semaphore);

	RETURN_AND_SET_ERRNO(error);
}


static int
named_sem_timedwait(sem_t* semaphore, clockid_t clock_id,
	const struct timespec* timeout)
{
	bigtime_t timeoutMicros = B_INFINITE_TIMEOUT;
	uint32 flags = 0;
	if (timeout != NULL) {
		if (!timespec_to_bigtime(*timeout, timeoutMicros)) {
			status_t err = _kern_realtime_sem_wait(semaphore->u.named_sem_id,
				B_RELATIVE_TIMEOUT, 0);
			if (err == B_WOULD_BLOCK)
				err = EINVAL;
			// do nothing, return err as it is.
			return err;
		}

		switch (clock_id) {
			case CLOCK_REALTIME:
				flags = B_ABSOLUTE_REAL_TIME_TIMEOUT;
				break;
			case CLOCK_MONOTONIC:
				flags = B_ABSOLUTE_TIMEOUT;
				break;
			default:
				return EINVAL;
		}
	}

	status_t err = _kern_realtime_sem_wait(semaphore->u.named_sem_id, flags,
		timeoutMicros);
	if (err == B_WOULD_BLOCK)
		err = ETIMEDOUT;

	return err;
}


int
sem_trywait(sem_t* semaphore)
{
	status_t error;
	if (semaphore->type == SEM_TYPE_NAMED) {
		error = _kern_realtime_sem_wait(semaphore->u.named_sem_id,
			B_RELATIVE_TIMEOUT, 0);
	} else
		error = unnamed_sem_trywait(semaphore);

	RETURN_AND_SET_ERRNO(error);
}


int
sem_wait(sem_t* semaphore)
{
	status_t error;
	if (semaphore->type == SEM_TYPE_NAMED)
		error = named_sem_timedwait(semaphore, CLOCK_REALTIME, NULL);
	else
		error = unnamed_sem_timedwait(semaphore, CLOCK_REALTIME, NULL);

	RETURN_AND_SET_ERRNO_TEST_CANCEL(error);
}


int
sem_clockwait(sem_t* semaphore, clockid_t clock_id, const struct timespec* abstime)
{
	status_t error;
	if (semaphore->type == SEM_TYPE_NAMED)
		error = named_sem_timedwait(semaphore, clock_id, abstime);
	else
		error = unnamed_sem_timedwait(semaphore, clock_id, abstime);

	RETURN_AND_SET_ERRNO_TEST_CANCEL(error);
}


int
sem_timedwait(sem_t* semaphore, const struct timespec* abstime)
{
	return sem_clockwait(semaphore, CLOCK_REALTIME, abstime);
}


int
sem_getvalue(sem_t* semaphore, int* value)
{
	if (semaphore->type == SEM_TYPE_NAMED) {
		RETURN_AND_SET_ERRNO(_kern_realtime_sem_get_value(
			semaphore->u.named_sem_id, value));
	} else {
		*value = semaphore->u.unnamed_sem < 0 ? 0 : semaphore->u.unnamed_sem;
		return 0;
	}
}