/* $NetBSD: dll.c,v 1.2.38.1 2023/08/11 13:39:57 martin Exp $ */ /*********************************************************************** * Copyright (c) 2016 Kungliga Tekniska Högskolan * (Royal Institute of Technology, Stockholm, Sweden). * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS 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 ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * **********************************************************************/ /* * This is an implementation of thread-specific storage with * destructors. WIN32 doesn't quite have this. Instead it has * DllMain(), an entry point in every DLL that gets called to notify the * DLL of thread/process "attach"/"detach" events. * * We use __thread (or __declspec(thread)) for the thread-local itself * and DllMain() DLL_THREAD_DETACH events to drive destruction of * thread-local values. * * When building in maintainer mode on non-Windows pthread systems this * uses a single pthread key instead to implement multiple keys. This * keeps the code from rotting when modified by non-Windows developers. */ #include "baselocl.h" #ifdef WIN32 #include #endif #ifdef HEIM_WIN32_TLS #include #include #include #ifndef WIN32 #include #endif /* Logical array of keys that grows lock-lessly */ typedef struct tls_keys tls_keys; struct tls_keys { void (**keys_dtors)(void *); /* array of destructors */ size_t keys_start_idx; /* index of first destructor */ size_t keys_num; tls_keys *keys_next; }; /* * Well, not quite locklessly. We need synchronization primitives to do * this locklessly. An atomic CAS will do. */ static HEIMDAL_MUTEX tls_key_defs_lock = HEIMDAL_MUTEX_INITIALIZER; static tls_keys *tls_key_defs; /* Logical array of values (per-thread; no locking needed here) */ struct tls_values { void **values; /* realloc()ed */ size_t values_num; }; static HEIMDAL_THREAD_LOCAL struct tls_values values; static char dead_key[1]; static void no_dtor(void *d) { (void)d; } void heim_w32_service_thread_detach(void *unused) { tls_keys *key_defs; void (*dtor)(void*); size_t i; HEIMDAL_MUTEX_lock(&tls_key_defs_lock); key_defs = tls_key_defs; HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); if (key_defs == NULL) return; for (i = 0; i < values.values_num; i++) { assert(i >= key_defs->keys_start_idx); if (i >= key_defs->keys_start_idx + key_defs->keys_num) { HEIMDAL_MUTEX_lock(&tls_key_defs_lock); key_defs = key_defs->keys_next; HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); assert(key_defs != NULL); assert(i >= key_defs->keys_start_idx); assert(i < key_defs->keys_start_idx + key_defs->keys_num); } dtor = key_defs->keys_dtors[i - key_defs->keys_start_idx]; if (values.values[i] != NULL && dtor != NULL && dtor != no_dtor) dtor(values.values[i]); values.values[i] = NULL; } } #if !defined(WIN32) static pthread_key_t pt_key; pthread_once_t pt_once = PTHREAD_ONCE_INIT; static void atexit_del_tls_for_thread(void) { heim_w32_service_thread_detach(NULL); } static void create_pt_key(void) { int ret; /* The main thread may not execute TLS destructors */ atexit(atexit_del_tls_for_thread); ret = pthread_key_create(&pt_key, heim_w32_service_thread_detach); if (ret != 0) err(1, "pthread_key_create() failed"); } #endif int heim_w32_key_create(HEIM_PRIV_thread_key *key, void (*dtor)(void *)) { tls_keys *key_defs, *new_key_defs; size_t i, k; int ret = ENOMEM; #if !defined(WIN32) (void) pthread_once(&pt_once, create_pt_key); (void) pthread_setspecific(pt_key, dead_key); #endif HEIMDAL_MUTEX_lock(&tls_key_defs_lock); if (tls_key_defs == NULL) { /* First key */ new_key_defs = calloc(1, sizeof(*new_key_defs)); if (new_key_defs == NULL) { HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); return ENOMEM; } new_key_defs->keys_num = 8; new_key_defs->keys_dtors = calloc(new_key_defs->keys_num, sizeof(*new_key_defs->keys_dtors)); if (new_key_defs->keys_dtors == NULL) { HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); free(new_key_defs); return ENOMEM; } tls_key_defs = new_key_defs; new_key_defs->keys_dtors[0] = dtor; for (i = 1; i < new_key_defs->keys_num; i++) new_key_defs->keys_dtors[i] = NULL; HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); return 0; } for (key_defs = tls_key_defs; key_defs != NULL; key_defs = key_defs->keys_next) { k = key_defs->keys_start_idx; for (i = 0; i < key_defs->keys_num; i++, k++) { if (key_defs->keys_dtors[i] == NULL) { /* Found free slot; use it */ key_defs->keys_dtors[i] = dtor; *key = k; HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); return 0; } } if (key_defs->keys_next != NULL) continue; /* Grow the registration array */ /* XXX DRY */ new_key_defs = calloc(1, sizeof(*new_key_defs)); if (new_key_defs == NULL) break; new_key_defs->keys_dtors = calloc(key_defs->keys_num + key_defs->keys_num / 2, sizeof(*new_key_defs->keys_dtors)); if (new_key_defs->keys_dtors == NULL) { free(new_key_defs); break; } new_key_defs->keys_start_idx = key_defs->keys_start_idx + key_defs->keys_num; new_key_defs->keys_num = key_defs->keys_num + key_defs->keys_num / 2; new_key_defs->keys_dtors[i] = dtor; for (i = 1; i < new_key_defs->keys_num; i++) new_key_defs->keys_dtors[i] = NULL; key_defs->keys_next = new_key_defs; ret = 0; break; } HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); return ret; } static void key_lookup(HEIM_PRIV_thread_key key, tls_keys **kd, size_t *dtor_idx, void (**dtor)(void *)) { tls_keys *key_defs; if (kd != NULL) *kd = NULL; if (dtor_idx != NULL) *dtor_idx = 0; if (dtor != NULL) *dtor = NULL; HEIMDAL_MUTEX_lock(&tls_key_defs_lock); key_defs = tls_key_defs; HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); while (key_defs != NULL) { if (key >= key_defs->keys_start_idx && key < key_defs->keys_start_idx + key_defs->keys_num) { if (kd != NULL) *kd = key_defs; if (dtor_idx != NULL) *dtor_idx = key - key_defs->keys_start_idx; if (dtor != NULL) *dtor = key_defs->keys_dtors[key - key_defs->keys_start_idx]; return; } HEIMDAL_MUTEX_lock(&tls_key_defs_lock); key_defs = key_defs->keys_next; HEIMDAL_MUTEX_unlock(&tls_key_defs_lock); assert(key_defs != NULL); assert(key >= key_defs->keys_start_idx); } } int heim_w32_delete_key(HEIM_PRIV_thread_key key) { tls_keys *key_defs; size_t dtor_idx; key_lookup(key, &key_defs, &dtor_idx, NULL); if (key_defs == NULL) return EINVAL; key_defs->keys_dtors[dtor_idx] = no_dtor; return 0; } int heim_w32_setspecific(HEIM_PRIV_thread_key key, void *value) { void **new_values; size_t new_num; void (*dtor)(void *); size_t i; #if !defined(WIN32) (void) pthread_setspecific(pt_key, dead_key); #endif key_lookup(key, NULL, NULL, &dtor); if (dtor == NULL) return EINVAL; if (key >= values.values_num) { if (values.values_num == 0) { values.values = NULL; new_num = 8; } else { new_num = (values.values_num + values.values_num / 2); } new_values = realloc(values.values, sizeof(void *) * new_num); if (new_values == NULL) return ENOMEM; for (i = values.values_num; i < new_num; i++) new_values[i] = NULL; values.values = new_values; values.values_num = new_num; } assert(key < values.values_num); if (values.values[key] != NULL && dtor != NULL && dtor != no_dtor) dtor(values.values[key]); values.values[key] = value; return 0; } void * heim_w32_getspecific(HEIM_PRIV_thread_key key) { if (key >= values.values_num) return NULL; return values.values[key]; } #else static char dummy; #endif /* HEIM_WIN32_TLS */