/*
 * Copyright 2011, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Copyright 2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "pthread_private.h"
 
#include <syscalls.h>
 
 
static inline void
test_asynchronous_cancel(int32 flags)
{
	static const int32 kFlags = THREAD_CANCELED | THREAD_CANCEL_ENABLED
		| THREAD_CANCEL_ASYNCHRONOUS;
 
	if ((~flags & kFlags) == 0)
		pthread_exit(PTHREAD_CANCELED);
}
 
 
/*!	Signal handler like function invoked when this thread has been canceled.
	Has the simple signal handler signature, since it is invoked just like a
	signal handler.
*/
static void
asynchronous_cancel_thread(int)
{
	pthread_t thread = pthread_self();
 
	// Exit when asynchronous cancellation is enabled, otherwise we don't have
	// to do anything -- the syscall interrupting side effect is all we need.
	if ((atomic_get(&thread->flags) & THREAD_CANCEL_ASYNCHRONOUS) != 0)
		pthread_exit(PTHREAD_CANCELED);
}
 
 
// #pragma mark - public API
 
 
int
pthread_cancel(pthread_t thread)
{
	// set the canceled flag
	int32 oldFlags = atomic_or(&thread->flags, THREAD_CANCELED);
 
	// If the flag was already set, we're done.
	if ((oldFlags & THREAD_CANCELED) != 0)
		return 0;
 
	// If cancellation is enabled, notify the thread. This will call the
	// asynchronous_cancel_thread() handler.
	if ((oldFlags & THREAD_CANCEL_ENABLED) != 0)
		return _kern_cancel_thread(thread->id, &asynchronous_cancel_thread);
 
	return 0;
}
 
 
int
pthread_setcancelstate(int state, int *_oldState)
{
	pthread_thread* thread = pthread_self();
	if (thread == NULL)
		return EINVAL;
 
	// set the new flags
	int32 oldFlags;
	if (state == PTHREAD_CANCEL_ENABLE) {
		oldFlags = atomic_or(&thread->flags, THREAD_CANCEL_ENABLED);
		test_asynchronous_cancel(oldFlags | THREAD_CANCEL_ENABLED);
	} else if (state == PTHREAD_CANCEL_DISABLE) {
		oldFlags = atomic_and(&thread->flags, ~(int32)THREAD_CANCEL_ENABLED);
		test_asynchronous_cancel(oldFlags);
	} else
		return EINVAL;
 
	// return the old state
	if (_oldState != NULL) {
		*_oldState = (oldFlags & PTHREAD_CANCEL_ENABLE) != 0
			? PTHREAD_CANCEL_ENABLE : PTHREAD_CANCEL_DISABLE;
	}
 
	return 0;
}
 
 
int
pthread_setcanceltype(int type, int *_oldType)
{
	pthread_thread* thread = pthread_self();
	if (thread == NULL)
		return EINVAL;
 
	// set the new type
	int32 oldFlags;
	if (type == PTHREAD_CANCEL_DEFERRED) {
		oldFlags = atomic_and(&thread->flags,
			~(int32)THREAD_CANCEL_ASYNCHRONOUS);
		test_asynchronous_cancel(oldFlags);
	} else if (type == PTHREAD_CANCEL_ASYNCHRONOUS) {
		oldFlags = atomic_or(&thread->flags, THREAD_CANCEL_ASYNCHRONOUS);
		test_asynchronous_cancel(oldFlags | THREAD_CANCEL_ASYNCHRONOUS);
	} else
		return EINVAL;
 
	// return the old type
	if (_oldType != NULL) {
		*_oldType = (oldFlags & THREAD_CANCEL_ASYNCHRONOUS) != 0
			? PTHREAD_CANCEL_ASYNCHRONOUS : PTHREAD_CANCEL_DEFERRED;
	}
 
	return 0;
}
 
 
void
pthread_testcancel(void)
{
	pthread_thread* thread = pthread_self();
	if (thread == NULL)
		return;
 
	static const int32 kFlags = THREAD_CANCELED | THREAD_CANCEL_ENABLED;
 
	if ((~atomic_get(&thread->flags) & kFlags) == 0)
		pthread_exit(NULL);
}

V547 Expression '(oldFlags & 0) != 0' is always false.