#include "internal.h"
#ifndef PTHREAD_KEY_LEGACY_SUPPORT
#if TARGET_OS_DRIVERKIT
#define PTHREAD_KEY_LEGACY_SUPPORT 0
#else
#define PTHREAD_KEY_LEGACY_SUPPORT 1
#endif // TARGET_OS_DRIVERKIT
#endif // PTHREAD_KEY_LEGACY_SUPPORT
#if !VARIANT_DYLD
static const int __pthread_tsd_first = __TSD_RESERVED_MAX + 1;
static const int __pthread_tsd_start = _INTERNAL_POSIX_THREAD_KEYS_MAX;
static const int __pthread_tsd_end = _INTERNAL_POSIX_THREAD_KEYS_END;
static int __pthread_tsd_max = __pthread_tsd_first;
static _pthread_lock __pthread_tsd_lock = _PTHREAD_LOCK_INITIALIZER;
#if PTHREAD_KEY_LEGACY_SUPPORT
static bool __pthread_key_legacy_behaviour = 0;
static bool __pthread_key_legacy_behaviour_log = 0;
#else
#define __pthread_key_legacy_behaviour 0
#define _pthread_tsd_cleanup_legacy(...)
#endif // PTHREAD_KEY_LEGACY_SUPPORT
static struct {
uintptr_t destructor;
} _pthread_keys[_INTERNAL_POSIX_THREAD_KEYS_END];
void
_pthread_key_global_init(const char *envp[])
{
#if PTHREAD_KEY_LEGACY_SUPPORT
if (_simple_getenv(envp, "PTHREAD_KEY_LEGACY_DESTRUCTOR_ORDER")) {
__pthread_key_legacy_behaviour = true;
}
if (_simple_getenv(envp, "PTHREAD_KEY_LEGACY_DESTRUCTOR_ORDER_LOG")) {
__pthread_key_legacy_behaviour_log = true;
}
#endif // PTHREAD_KEY_LEGACY_SUPPORT
}
static bool
_pthread_key_set_destructor(pthread_key_t key, void (*destructor)(void *))
{
uintptr_t *ptr = &_pthread_keys[key].destructor;
uintptr_t value = ~(uintptr_t)destructor;
if (*ptr == 0) {
*ptr = value;
return true;
}
return false;
}
static bool
_pthread_key_unset_destructor(pthread_key_t key)
{
uintptr_t *ptr = &_pthread_keys[key].destructor;
if (*ptr != 0) {
*ptr = 0;
return true;
}
return false;
}
static bool
_pthread_key_get_destructor(pthread_key_t key, void (**destructor)(void *))
{
uintptr_t value = _pthread_keys[key].destructor;
if (destructor) {
*destructor = (void (*)(void *))(~value);
}
return (value != 0);
}
int
pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
{
int res = EAGAIN; pthread_key_t k;
_pthread_lock_lock(&__pthread_tsd_lock);
for (k = __pthread_tsd_start; k < __pthread_tsd_end; k++) {
if (_pthread_key_set_destructor(k, destructor)) {
*key = k;
res = 0;
break;
}
}
_pthread_lock_unlock(&__pthread_tsd_lock);
return res;
}
int
pthread_key_delete(pthread_key_t key)
{
int res = EINVAL;
_pthread_lock_lock(&__pthread_tsd_lock);
if (key >= __pthread_tsd_start && key < __pthread_tsd_end) {
if (_pthread_key_unset_destructor(key)) {
pthread_t p;
_pthread_lock_lock(&_pthread_list_lock);
TAILQ_FOREACH(p, &__pthread_head, tl_plist) {
p->tsd[key] = 0;
}
_pthread_lock_unlock(&_pthread_list_lock);
res = 0;
}
}
_pthread_lock_unlock(&__pthread_tsd_lock);
return res;
}
static inline int
_pthread_setspecific(pthread_t thread, pthread_key_t key, const void *value)
{
int res = EINVAL;
if (key >= __pthread_tsd_first && key < __pthread_tsd_end) {
bool created = _pthread_key_get_destructor(key, NULL);
if (key < __pthread_tsd_start || created) {
thread->tsd[key] = (void *)value;
res = 0;
if (key < __pthread_tsd_start) {
_pthread_key_set_destructor(key, NULL);
}
if (key > thread->max_tsd_key) {
thread->max_tsd_key = (uint16_t)key;
}
}
}
return res;
}
#endif // !VARIANT_DYLD
int
pthread_setspecific(pthread_key_t key, const void *value)
{
#if VARIANT_DYLD
return ENOTSUP;
#else
return _pthread_setspecific(pthread_self(), key, value);
#endif // !VARIANT_DYLD
}
int
_pthread_setspecific_static(pthread_key_t key, void *value)
{
int res = EINVAL;
#if !VARIANT_DYLD
if (key < __pthread_tsd_start) {
_pthread_setspecific_direct(key, value);
res = 0;
}
#endif // !VARIANT_DYLD
return res;
}
void*
pthread_getspecific(pthread_key_t key)
{
return _pthread_getspecific_direct(key);
}
#if !VARIANT_DYLD
int
pthread_introspection_setspecific_np(pthread_t thread,
pthread_key_t key, const void *value)
{
pthread_t self = _pthread_self();
if (os_unlikely(self->introspection != PTHREAD_INTROSPECTION_THREAD_CREATE)) {
PTHREAD_CLIENT_CRASH(0, "Calling pthread_introspection_setspecific_np "
"outside of a CREATE introspection hook");
}
return _pthread_setspecific(thread, key, value);
}
void *
pthread_introspection_getspecific_np(pthread_t thread, pthread_key_t key)
{
pthread_t self = _pthread_self();
if (os_unlikely(self->introspection != PTHREAD_INTROSPECTION_THREAD_DESTROY)) {
PTHREAD_CLIENT_CRASH(0, "Calling pthread_introspection_getspecific_np "
"outside of a DESTROY introspection hook");
}
return thread->tsd[key];
}
static void
_pthread_tsd_cleanup_key(pthread_t self, pthread_key_t key)
{
void (*destructor)(void *);
if (_pthread_key_get_destructor(key, &destructor)) {
void **ptr = &self->tsd[key];
void *value = *ptr;
if (value) {
*ptr = NULL;
if (destructor) {
destructor(value);
}
}
}
}
static void
_pthread_tsd_cleanup_new(pthread_t self)
{
int j;
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) {
pthread_key_t k;
for (k = __pthread_tsd_start; k <= self->max_tsd_key; k++) {
_pthread_tsd_cleanup_key(self, k);
}
for (k = __pthread_tsd_first; k <= __pthread_tsd_max; k++) {
_pthread_tsd_cleanup_key(self, k);
}
}
self->max_tsd_key = 0;
}
#if PTHREAD_KEY_LEGACY_SUPPORT
#import <_simple.h>
#import <dlfcn.h>
static void
_pthread_tsd_behaviour_check(pthread_t self)
{
pthread_key_t k;
for (k = __pthread_tsd_start; k < __pthread_tsd_end; k++) {
void (*destructor)(void *);
if (_pthread_key_get_destructor(k, &destructor)) {
void **ptr = &self->tsd[k];
void *value = *ptr;
if (value && destructor) {
_simple_asl_log(ASL_LEVEL_ERR, "pthread_tsd",
"warning: dynamic tsd keys dirty after static key cleanup loop.");
#if 0
Dl_info i;
if (dladdr(destructor, &i) == 0) {
_simple_asl_log(ASL_LEVEL_ERR, "pthread_tsd", i.dli_fname);
_simple_asl_log(ASL_LEVEL_ERR, "pthread_tsd", i.dli_saddr);
}
#endif
}
}
}
}
static void
_pthread_tsd_cleanup_legacy(pthread_t self)
{
int j;
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) {
pthread_key_t k;
for (k = __pthread_tsd_start; k <= self->max_tsd_key; k++) {
_pthread_tsd_cleanup_key(self, k);
}
}
self->max_tsd_key = 0;
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; j++) {
pthread_key_t k;
for (k = __pthread_tsd_first; k <= __pthread_tsd_max; k++) {
_pthread_tsd_cleanup_key(self, k);
}
if (__pthread_key_legacy_behaviour_log != 0 && self->max_tsd_key != 0) {
_pthread_tsd_behaviour_check(self);
}
}
}
#endif // PTHREAD_KEY_LEGACY_SUPPORT
#endif // !VARIANT_DYLD
void
_pthread_tsd_cleanup(pthread_t self)
{
#if !VARIANT_DYLD
if (__pthread_key_legacy_behaviour == 0) {
_pthread_tsd_cleanup_new(self);
} else {
_pthread_tsd_cleanup_legacy(self);
}
#endif // !VARIANT_DYLD
}
#if !VARIANT_DYLD
int
pthread_key_init_np(int key, void (*destructor)(void *))
{
int res = EINVAL; if (key >= __pthread_tsd_first && key < __pthread_tsd_start) {
_pthread_lock_lock(&__pthread_tsd_lock);
_pthread_key_set_destructor(key, destructor);
if (key > __pthread_tsd_max) {
__pthread_tsd_max = key;
}
_pthread_lock_unlock(&__pthread_tsd_lock);
res = 0;
}
return res;
}
#endif // !VARIANT_DYLD
#undef pthread_self
pthread_t
pthread_self(void)
{
pthread_t self = _pthread_self_direct();
_pthread_validate_signature(self);
return self;
}
pthread_t
_pthread_self(void)
{
return pthread_self();
}