* 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;
}
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 {
openFlags &= ~O_EXCL;
}
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);
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;
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;
}
}