Path: blob/main/crypto/krb5/src/util/support/threads.c
34889 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/* util/support/threads.c - Portable thread support */2/*3* Copyright 2004,2005,2006,2007,2008 by the Massachusetts Institute of4* Technology. All Rights Reserved.5*6* Export of this software from the United States of America may7* require a specific license from the United States Government.8* It is the responsibility of any person or organization contemplating9* export to obtain such a license before exporting.10*11* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and12* distribute this software and its documentation for any purpose and13* without fee is hereby granted, provided that the above copyright14* notice appear in all copies and that both that copyright notice and15* this permission notice appear in supporting documentation, and that16* the name of M.I.T. not be used in advertising or publicity pertaining17* to distribution of the software without specific, written prior18* permission. Furthermore if you modify this software you must label19* your software as modified software and not distribute it in such a20* fashion that it might be confused with the original M.I.T. software.21* M.I.T. makes no representations about the suitability of22* this software for any purpose. It is provided "as is" without express23* or implied warranty.24*/2526#define THREAD_SUPPORT_IMPL27#include "k5-platform.h"28#include "k5-thread.h"29#include "supp-int.h"3031MAKE_INIT_FUNCTION(krb5int_thread_support_init);32MAKE_FINI_FUNCTION(krb5int_thread_support_fini);3334/* This function used to be referenced from elsewhere in the tree, but is now35* only used internally. Keep it linker-visible for now. */36int krb5int_pthread_loaded(void);3738#ifndef ENABLE_THREADS /* no thread support */3940static void (*destructors[K5_KEY_MAX])(void *);41struct tsd_block { void *values[K5_KEY_MAX]; };42static struct tsd_block tsd_no_threads;43static unsigned char destructors_set[K5_KEY_MAX];4445int krb5int_pthread_loaded (void)46{47return 0;48}4950#elif defined(_WIN32)5152static DWORD tls_idx;53static CRITICAL_SECTION key_lock;54struct tsd_block {55void *values[K5_KEY_MAX];56};57static void (*destructors[K5_KEY_MAX])(void *);58static unsigned char destructors_set[K5_KEY_MAX];5960void krb5int_thread_detach_hook (void)61{62/* XXX Memory leak here!63Need to destroy all TLS objects we know about for this thread. */64struct tsd_block *t;65int i, err;6667err = CALL_INIT_FUNCTION(krb5int_thread_support_init);68if (err)69return;7071t = TlsGetValue(tls_idx);72if (t == NULL)73return;74for (i = 0; i < K5_KEY_MAX; i++) {75if (destructors_set[i] && destructors[i] && t->values[i]) {76void *v = t->values[i];77t->values[i] = 0;78(*destructors[i])(v);79}80}81}8283/* Stub function not used on Windows. */84int krb5int_pthread_loaded (void)85{86return 0;87}88#else /* POSIX threads */8990/* Must support register/delete/register sequence, e.g., if krb5 is91loaded so this support code stays in the process, and gssapi is92loaded, unloaded, and loaded again. */9394static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;95static void (*destructors[K5_KEY_MAX])(void *);96static unsigned char destructors_set[K5_KEY_MAX];9798/* This is not safe yet!99100Thread termination concurrent with key deletion can cause two101threads to interfere. It's a bit tricky, since one of the threads102will want to remove this structure from the list being walked by103the other.104105Other cases, like looking up data while the library owning the key106is in the process of being unloaded, we don't worry about. */107108struct tsd_block {109struct tsd_block *next;110void *values[K5_KEY_MAX];111};112113#ifdef HAVE_PRAGMA_WEAK_REF114# pragma weak pthread_once115# pragma weak pthread_mutex_lock116# pragma weak pthread_mutex_unlock117# pragma weak pthread_mutex_destroy118# pragma weak pthread_mutex_init119# pragma weak pthread_self120# pragma weak pthread_getspecific121# pragma weak pthread_setspecific122# pragma weak pthread_key_create123# pragma weak pthread_key_delete124# pragma weak pthread_create125# pragma weak pthread_join126# define K5_PTHREADS_LOADED (krb5int_pthread_loaded())127static volatile int flag_pthread_loaded = -1;128static void loaded_test_aux(void)129{130if (flag_pthread_loaded == -1)131flag_pthread_loaded = 1;132else133/* Could we have been called twice? */134flag_pthread_loaded = 0;135}136static pthread_once_t loaded_test_once = PTHREAD_ONCE_INIT;137int krb5int_pthread_loaded (void)138{139int x = flag_pthread_loaded;140if (x != -1)141return x;142if (&pthread_getspecific == 0143|| &pthread_setspecific == 0144|| &pthread_key_create == 0145|| &pthread_key_delete == 0146|| &pthread_once == 0147|| &pthread_mutex_lock == 0148|| &pthread_mutex_unlock == 0149|| &pthread_mutex_destroy == 0150|| &pthread_mutex_init == 0151|| &pthread_self == 0152/* Any program that's really multithreaded will have to be153able to create threads. */154|| &pthread_create == 0155|| &pthread_join == 0156/* Okay, all the interesting functions -- or stubs for them --157seem to be present. If we call pthread_once, does it158actually seem to cause the indicated function to get called159exactly one time? */160|| pthread_once(&loaded_test_once, loaded_test_aux) != 0161|| pthread_once(&loaded_test_once, loaded_test_aux) != 0162/* This catches cases where pthread_once does nothing, and163never causes the function to get called. That's a pretty164clear violation of the POSIX spec, but hey, it happens. */165|| flag_pthread_loaded < 0) {166flag_pthread_loaded = 0;167return 0;168}169/* If we wanted to be super-paranoid, we could try testing whether170pthread_get/setspecific work, too. I don't know -- so far --171of any system with non-functional stubs for those. */172return flag_pthread_loaded;173}174175static struct tsd_block tsd_if_single;176# define GET_NO_PTHREAD_TSD() (&tsd_if_single)177#else178# define K5_PTHREADS_LOADED (1)179int krb5int_pthread_loaded (void)180{181return 1;182}183184# define GET_NO_PTHREAD_TSD() (abort(),(struct tsd_block *)0)185#endif186187static pthread_key_t key;188static void thread_termination(void *);189190static void thread_termination (void *tptr)191{192int i, pass, none_found;193struct tsd_block *t = tptr;194195k5_mutex_lock(&key_lock);196197/*198* Make multiple passes in case, for example, a libkrb5 cleanup199* function wants to print out an error message, which causes200* com_err to allocate a thread-specific buffer, after we just201* freed up the old one.202*203* Shouldn't actually happen, if we're careful, but check just in204* case.205*/206207pass = 0;208none_found = 0;209while (pass < 4 && !none_found) {210none_found = 1;211for (i = 0; i < K5_KEY_MAX; i++) {212if (destructors_set[i] && destructors[i] && t->values[i]) {213void *v = t->values[i];214t->values[i] = 0;215(*destructors[i])(v);216none_found = 0;217}218}219}220free (t);221k5_mutex_unlock(&key_lock);222223/* remove thread from global linked list */224}225226#endif /* no threads vs Win32 vs POSIX */227228void *k5_getspecific (k5_key_t keynum)229{230struct tsd_block *t;231int err;232233err = CALL_INIT_FUNCTION(krb5int_thread_support_init);234if (err)235return NULL;236237assert(destructors_set[keynum] == 1);238239#ifndef ENABLE_THREADS240241t = &tsd_no_threads;242243#elif defined(_WIN32)244245t = TlsGetValue(tls_idx);246247#else /* POSIX */248249if (K5_PTHREADS_LOADED)250t = pthread_getspecific(key);251else252t = GET_NO_PTHREAD_TSD();253254#endif255256if (t == NULL)257return NULL;258return t->values[keynum];259}260261int k5_setspecific (k5_key_t keynum, void *value)262{263struct tsd_block *t;264int err;265266err = CALL_INIT_FUNCTION(krb5int_thread_support_init);267if (err)268return err;269270assert(destructors_set[keynum] == 1);271272#ifndef ENABLE_THREADS273274t = &tsd_no_threads;275276#elif defined(_WIN32)277278t = TlsGetValue(tls_idx);279if (t == NULL) {280int i;281t = malloc(sizeof(*t));282if (t == NULL)283return ENOMEM;284for (i = 0; i < K5_KEY_MAX; i++)285t->values[i] = 0;286/* add to global linked list */287/* t->next = 0; */288err = TlsSetValue(tls_idx, t);289if (!err) {290free(t);291return GetLastError();292}293}294295#else /* POSIX */296297if (K5_PTHREADS_LOADED) {298t = pthread_getspecific(key);299if (t == NULL) {300int i;301t = malloc(sizeof(*t));302if (t == NULL)303return ENOMEM;304for (i = 0; i < K5_KEY_MAX; i++)305t->values[i] = 0;306/* add to global linked list */307t->next = 0;308err = pthread_setspecific(key, t);309if (err) {310free(t);311return err;312}313}314} else {315t = GET_NO_PTHREAD_TSD();316}317318#endif319320t->values[keynum] = value;321return 0;322}323324int k5_key_register (k5_key_t keynum, void (*destructor)(void *))325{326int err;327328err = CALL_INIT_FUNCTION(krb5int_thread_support_init);329if (err)330return err;331332#ifndef ENABLE_THREADS333334assert(destructors_set[keynum] == 0);335destructors[keynum] = destructor;336destructors_set[keynum] = 1;337338#elif defined(_WIN32)339340/* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK. */341EnterCriticalSection(&key_lock);342assert(destructors_set[keynum] == 0);343destructors_set[keynum] = 1;344destructors[keynum] = destructor;345LeaveCriticalSection(&key_lock);346347#else /* POSIX */348349k5_mutex_lock(&key_lock);350assert(destructors_set[keynum] == 0);351destructors_set[keynum] = 1;352destructors[keynum] = destructor;353k5_mutex_unlock(&key_lock);354355#endif356return 0;357}358359int k5_key_delete (k5_key_t keynum)360{361#ifndef ENABLE_THREADS362363assert(destructors_set[keynum] == 1);364if (destructors[keynum] && tsd_no_threads.values[keynum])365(*destructors[keynum])(tsd_no_threads.values[keynum]);366destructors[keynum] = 0;367tsd_no_threads.values[keynum] = 0;368destructors_set[keynum] = 0;369370#elif defined(_WIN32)371372/* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK. */373EnterCriticalSection(&key_lock);374/* XXX Memory leak here!375Need to destroy the associated data for all threads.376But watch for race conditions in case threads are going away too. */377assert(destructors_set[keynum] == 1);378destructors_set[keynum] = 0;379destructors[keynum] = 0;380LeaveCriticalSection(&key_lock);381382#else /* POSIX */383384/* XXX RESOURCE LEAK: Need to destroy the allocated objects first! */385k5_mutex_lock(&key_lock);386assert(destructors_set[keynum] == 1);387destructors_set[keynum] = 0;388destructors[keynum] = NULL;389k5_mutex_unlock(&key_lock);390391#endif392393return 0;394}395396int krb5int_call_thread_support_init (void)397{398return CALL_INIT_FUNCTION(krb5int_thread_support_init);399}400401#include "cache-addrinfo.h"402403int krb5int_thread_support_init (void)404{405int err;406407#ifdef SHOW_INITFINI_FUNCS408printf("krb5int_thread_support_init\n");409#endif410411#ifndef ENABLE_THREADS412413/* Nothing to do for TLS initialization. */414415#elif defined(_WIN32)416417tls_idx = TlsAlloc();418/* XXX This can raise an exception if memory is low! */419InitializeCriticalSection(&key_lock);420421#else /* POSIX */422423err = k5_mutex_finish_init(&key_lock);424if (err)425return err;426if (K5_PTHREADS_LOADED) {427err = pthread_key_create(&key, thread_termination);428if (err)429return err;430}431432#endif433434err = krb5int_init_fac();435if (err)436return err;437438err = krb5int_err_init();439if (err)440return err;441442return 0;443}444445void krb5int_thread_support_fini (void)446{447if (! INITIALIZER_RAN (krb5int_thread_support_init))448return;449450#ifdef SHOW_INITFINI_FUNCS451printf("krb5int_thread_support_fini\n");452#endif453454#ifndef ENABLE_THREADS455456/* Do nothing. */457458#elif defined(_WIN32)459460/* ... free stuff ... */461TlsFree(tls_idx);462DeleteCriticalSection(&key_lock);463464#else /* POSIX */465466if (! INITIALIZER_RAN(krb5int_thread_support_init))467return;468if (K5_PTHREADS_LOADED)469pthread_key_delete(key);470/* ... delete stuff ... */471k5_mutex_destroy(&key_lock);472473#endif474475krb5int_fini_fac();476}477478/* Mutex allocation functions, for use in plugins that may not know479what options a given set of libraries was compiled with. */480int KRB5_CALLCONV481krb5int_mutex_alloc (k5_mutex_t **m)482{483k5_mutex_t *ptr;484int err;485486ptr = malloc (sizeof (k5_mutex_t));487if (ptr == NULL)488return ENOMEM;489err = k5_mutex_init (ptr);490if (err) {491free (ptr);492return err;493}494*m = ptr;495return 0;496}497498void KRB5_CALLCONV499krb5int_mutex_free (k5_mutex_t *m)500{501(void) k5_mutex_destroy (m);502free (m);503}504505/* Callable versions of the various macros. */506void KRB5_CALLCONV507krb5int_mutex_lock (k5_mutex_t *m)508{509k5_mutex_lock (m);510}511void KRB5_CALLCONV512krb5int_mutex_unlock (k5_mutex_t *m)513{514k5_mutex_unlock (m);515}516517#ifdef USE_CONDITIONAL_PTHREADS518519int520k5_os_mutex_init(k5_os_mutex *m)521{522if (krb5int_pthread_loaded())523return pthread_mutex_init(m, 0);524else525return 0;526}527528int529k5_os_mutex_destroy(k5_os_mutex *m)530{531if (krb5int_pthread_loaded())532return pthread_mutex_destroy(m);533else534return 0;535}536537int538k5_os_mutex_lock(k5_os_mutex *m)539{540if (krb5int_pthread_loaded())541return pthread_mutex_lock(m);542else543return 0;544}545546int547k5_os_mutex_unlock(k5_os_mutex *m)548{549if (krb5int_pthread_loaded())550return pthread_mutex_unlock(m);551else552return 0;553}554555int556k5_once(k5_once_t *once, void (*fn)(void))557{558if (krb5int_pthread_loaded())559return pthread_once(&once->o, fn);560else561return k5_os_nothread_once(&once->n, fn);562}563564#else /* USE_CONDITIONAL_PTHREADS */565566#undef k5_os_mutex_init567#undef k5_os_mutex_destroy568#undef k5_os_mutex_lock569#undef k5_os_mutex_unlock570#undef k5_once571572int k5_os_mutex_init(k5_os_mutex *m);573int k5_os_mutex_destroy(k5_os_mutex *m);574int k5_os_mutex_lock(k5_os_mutex *m);575int k5_os_mutex_unlock(k5_os_mutex *m);576int k5_once(k5_once_t *once, void (*fn)(void));577578/* Stub functions */579int580k5_os_mutex_init(k5_os_mutex *m)581{582return 0;583}584int585k5_os_mutex_destroy(k5_os_mutex *m)586{587return 0;588}589int590k5_os_mutex_lock(k5_os_mutex *m)591{592return 0;593}594int595k5_os_mutex_unlock(k5_os_mutex *m)596{597return 0;598}599int600k5_once(k5_once_t *once, void (*fn)(void))601{602return 0;603}604605#endif /* not USE_CONDITIONAL_PTHREADS */606607608