diff options
Diffstat (limited to 'src/util/support/threads.c')
| -rw-r--r-- | src/util/support/threads.c | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/src/util/support/threads.c b/src/util/support/threads.c new file mode 100644 index 000000000000..bb8e287ecf75 --- /dev/null +++ b/src/util/support/threads.c @@ -0,0 +1,614 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* util/support/threads.c - Portable thread support */ +/* + * Copyright 2004,2005,2006,2007,2008 by the Massachusetts Institute of + * Technology. All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + */ + +#define THREAD_SUPPORT_IMPL +#include "k5-platform.h" +#include "k5-thread.h" +#include "supp-int.h" + +MAKE_INIT_FUNCTION(krb5int_thread_support_init); +MAKE_FINI_FUNCTION(krb5int_thread_support_fini); + +/* This function used to be referenced from elsewhere in the tree, but is now + * only used internally. Keep it linker-visible for now. */ +int krb5int_pthread_loaded(void); + +#ifndef ENABLE_THREADS /* no thread support */ + +static void (*destructors[K5_KEY_MAX])(void *); +struct tsd_block { void *values[K5_KEY_MAX]; }; +static struct tsd_block tsd_no_threads; +static unsigned char destructors_set[K5_KEY_MAX]; + +int krb5int_pthread_loaded (void) +{ + return 0; +} + +#elif defined(_WIN32) + +static DWORD tls_idx; +static CRITICAL_SECTION key_lock; +struct tsd_block { + void *values[K5_KEY_MAX]; +}; +static void (*destructors[K5_KEY_MAX])(void *); +static unsigned char destructors_set[K5_KEY_MAX]; + +void krb5int_thread_detach_hook (void) +{ + /* XXX Memory leak here! + Need to destroy all TLS objects we know about for this thread. */ + struct tsd_block *t; + int i, err; + + err = CALL_INIT_FUNCTION(krb5int_thread_support_init); + if (err) + return; + + t = TlsGetValue(tls_idx); + if (t == NULL) + return; + for (i = 0; i < K5_KEY_MAX; i++) { + if (destructors_set[i] && destructors[i] && t->values[i]) { + void *v = t->values[i]; + t->values[i] = 0; + (*destructors[i])(v); + } + } +} + +/* Stub function not used on Windows. */ +int krb5int_pthread_loaded (void) +{ + return 0; +} +#else /* POSIX threads */ + +/* Must support register/delete/register sequence, e.g., if krb5 is + loaded so this support code stays in the process, and gssapi is + loaded, unloaded, and loaded again. */ + +static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER; +static void (*destructors[K5_KEY_MAX])(void *); +static unsigned char destructors_set[K5_KEY_MAX]; + +/* This is not safe yet! + + Thread termination concurrent with key deletion can cause two + threads to interfere. It's a bit tricky, since one of the threads + will want to remove this structure from the list being walked by + the other. + + Other cases, like looking up data while the library owning the key + is in the process of being unloaded, we don't worry about. */ + +struct tsd_block { + struct tsd_block *next; + void *values[K5_KEY_MAX]; +}; + +#ifdef HAVE_PRAGMA_WEAK_REF +# pragma weak pthread_once +# pragma weak pthread_mutex_lock +# pragma weak pthread_mutex_unlock +# pragma weak pthread_mutex_destroy +# pragma weak pthread_mutex_init +# pragma weak pthread_self +# pragma weak pthread_equal +# pragma weak pthread_getspecific +# pragma weak pthread_setspecific +# pragma weak pthread_key_create +# pragma weak pthread_key_delete +# pragma weak pthread_create +# pragma weak pthread_join +# define K5_PTHREADS_LOADED (krb5int_pthread_loaded()) +static volatile int flag_pthread_loaded = -1; +static void loaded_test_aux(void) +{ + if (flag_pthread_loaded == -1) + flag_pthread_loaded = 1; + else + /* Could we have been called twice? */ + flag_pthread_loaded = 0; +} +static pthread_once_t loaded_test_once = PTHREAD_ONCE_INIT; +int krb5int_pthread_loaded (void) +{ + int x = flag_pthread_loaded; + if (x != -1) + return x; + if (&pthread_getspecific == 0 + || &pthread_setspecific == 0 + || &pthread_key_create == 0 + || &pthread_key_delete == 0 + || &pthread_once == 0 + || &pthread_mutex_lock == 0 + || &pthread_mutex_unlock == 0 + || &pthread_mutex_destroy == 0 + || &pthread_mutex_init == 0 + || &pthread_self == 0 + || &pthread_equal == 0 + /* Any program that's really multithreaded will have to be + able to create threads. */ + || &pthread_create == 0 + || &pthread_join == 0 + /* Okay, all the interesting functions -- or stubs for them -- + seem to be present. If we call pthread_once, does it + actually seem to cause the indicated function to get called + exactly one time? */ + || pthread_once(&loaded_test_once, loaded_test_aux) != 0 + || pthread_once(&loaded_test_once, loaded_test_aux) != 0 + /* This catches cases where pthread_once does nothing, and + never causes the function to get called. That's a pretty + clear violation of the POSIX spec, but hey, it happens. */ + || flag_pthread_loaded < 0) { + flag_pthread_loaded = 0; + return 0; + } + /* If we wanted to be super-paranoid, we could try testing whether + pthread_get/setspecific work, too. I don't know -- so far -- + of any system with non-functional stubs for those. */ + return flag_pthread_loaded; +} + +static struct tsd_block tsd_if_single; +# define GET_NO_PTHREAD_TSD() (&tsd_if_single) +#else +# define K5_PTHREADS_LOADED (1) +int krb5int_pthread_loaded (void) +{ + return 1; +} + +# define GET_NO_PTHREAD_TSD() (abort(),(struct tsd_block *)0) +#endif + +static pthread_key_t key; +static void thread_termination(void *); + +static void thread_termination (void *tptr) +{ + int i, pass, none_found; + struct tsd_block *t = tptr; + + k5_mutex_lock(&key_lock); + + /* + * Make multiple passes in case, for example, a libkrb5 cleanup + * function wants to print out an error message, which causes + * com_err to allocate a thread-specific buffer, after we just + * freed up the old one. + * + * Shouldn't actually happen, if we're careful, but check just in + * case. + */ + + pass = 0; + none_found = 0; + while (pass < 4 && !none_found) { + none_found = 1; + for (i = 0; i < K5_KEY_MAX; i++) { + if (destructors_set[i] && destructors[i] && t->values[i]) { + void *v = t->values[i]; + t->values[i] = 0; + (*destructors[i])(v); + none_found = 0; + } + } + } + free (t); + k5_mutex_unlock(&key_lock); + + /* remove thread from global linked list */ +} + +#endif /* no threads vs Win32 vs POSIX */ + +void *k5_getspecific (k5_key_t keynum) +{ + struct tsd_block *t; + int err; + + err = CALL_INIT_FUNCTION(krb5int_thread_support_init); + if (err) + return NULL; + + assert(keynum >= 0 && keynum < K5_KEY_MAX); + assert(destructors_set[keynum] == 1); + +#ifndef ENABLE_THREADS + + t = &tsd_no_threads; + +#elif defined(_WIN32) + + t = TlsGetValue(tls_idx); + +#else /* POSIX */ + + if (K5_PTHREADS_LOADED) + t = pthread_getspecific(key); + else + t = GET_NO_PTHREAD_TSD(); + +#endif + + if (t == NULL) + return NULL; + return t->values[keynum]; +} + +int k5_setspecific (k5_key_t keynum, void *value) +{ + struct tsd_block *t; + int err; + + err = CALL_INIT_FUNCTION(krb5int_thread_support_init); + if (err) + return err; + + assert(keynum >= 0 && keynum < K5_KEY_MAX); + assert(destructors_set[keynum] == 1); + +#ifndef ENABLE_THREADS + + t = &tsd_no_threads; + +#elif defined(_WIN32) + + t = TlsGetValue(tls_idx); + if (t == NULL) { + int i; + t = malloc(sizeof(*t)); + if (t == NULL) + return ENOMEM; + for (i = 0; i < K5_KEY_MAX; i++) + t->values[i] = 0; + /* add to global linked list */ + /* t->next = 0; */ + err = TlsSetValue(tls_idx, t); + if (!err) { + free(t); + return GetLastError(); + } + } + +#else /* POSIX */ + + if (K5_PTHREADS_LOADED) { + t = pthread_getspecific(key); + if (t == NULL) { + int i; + t = malloc(sizeof(*t)); + if (t == NULL) + return ENOMEM; + for (i = 0; i < K5_KEY_MAX; i++) + t->values[i] = 0; + /* add to global linked list */ + t->next = 0; + err = pthread_setspecific(key, t); + if (err) { + free(t); + return err; + } + } + } else { + t = GET_NO_PTHREAD_TSD(); + } + +#endif + + t->values[keynum] = value; + return 0; +} + +int k5_key_register (k5_key_t keynum, void (*destructor)(void *)) +{ + int err; + + err = CALL_INIT_FUNCTION(krb5int_thread_support_init); + if (err) + return err; + + assert(keynum >= 0 && keynum < K5_KEY_MAX); + +#ifndef ENABLE_THREADS + + assert(destructors_set[keynum] == 0); + destructors[keynum] = destructor; + destructors_set[keynum] = 1; + +#elif defined(_WIN32) + + /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK. */ + EnterCriticalSection(&key_lock); + assert(destructors_set[keynum] == 0); + destructors_set[keynum] = 1; + destructors[keynum] = destructor; + LeaveCriticalSection(&key_lock); + +#else /* POSIX */ + + k5_mutex_lock(&key_lock); + assert(destructors_set[keynum] == 0); + destructors_set[keynum] = 1; + destructors[keynum] = destructor; + k5_mutex_unlock(&key_lock); + +#endif + return 0; +} + +int k5_key_delete (k5_key_t keynum) +{ + assert(keynum >= 0 && keynum < K5_KEY_MAX); + +#ifndef ENABLE_THREADS + + assert(destructors_set[keynum] == 1); + if (destructors[keynum] && tsd_no_threads.values[keynum]) + (*destructors[keynum])(tsd_no_threads.values[keynum]); + destructors[keynum] = 0; + tsd_no_threads.values[keynum] = 0; + destructors_set[keynum] = 0; + +#elif defined(_WIN32) + + /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK. */ + EnterCriticalSection(&key_lock); + /* XXX Memory leak here! + Need to destroy the associated data for all threads. + But watch for race conditions in case threads are going away too. */ + assert(destructors_set[keynum] == 1); + destructors_set[keynum] = 0; + destructors[keynum] = 0; + LeaveCriticalSection(&key_lock); + +#else /* POSIX */ + + /* XXX RESOURCE LEAK: Need to destroy the allocated objects first! */ + k5_mutex_lock(&key_lock); + assert(destructors_set[keynum] == 1); + destructors_set[keynum] = 0; + destructors[keynum] = NULL; + k5_mutex_unlock(&key_lock); + +#endif + + return 0; +} + +int krb5int_call_thread_support_init (void) +{ + return CALL_INIT_FUNCTION(krb5int_thread_support_init); +} + +#include "cache-addrinfo.h" + +int krb5int_thread_support_init (void) +{ + int err; + +#ifdef SHOW_INITFINI_FUNCS + printf("krb5int_thread_support_init\n"); +#endif + +#ifndef ENABLE_THREADS + + /* Nothing to do for TLS initialization. */ + +#elif defined(_WIN32) + + tls_idx = TlsAlloc(); + /* XXX This can raise an exception if memory is low! */ + InitializeCriticalSection(&key_lock); + +#else /* POSIX */ + + err = k5_mutex_finish_init(&key_lock); + if (err) + return err; + if (K5_PTHREADS_LOADED) { + err = pthread_key_create(&key, thread_termination); + if (err) + return err; + } + +#endif + + err = krb5int_init_fac(); + if (err) + return err; + + err = krb5int_err_init(); + if (err) + return err; + + return 0; +} + +void krb5int_thread_support_fini (void) +{ + if (! INITIALIZER_RAN (krb5int_thread_support_init)) + return; + +#ifdef SHOW_INITFINI_FUNCS + printf("krb5int_thread_support_fini\n"); +#endif + +#ifndef ENABLE_THREADS + + /* Do nothing. */ + +#elif defined(_WIN32) + + /* ... free stuff ... */ + TlsFree(tls_idx); + DeleteCriticalSection(&key_lock); + +#else /* POSIX */ + + if (! INITIALIZER_RAN(krb5int_thread_support_init)) + return; + if (K5_PTHREADS_LOADED) + pthread_key_delete(key); + /* ... delete stuff ... */ + k5_mutex_destroy(&key_lock); + +#endif + + krb5int_fini_fac(); +} + +/* Mutex allocation functions, for use in plugins that may not know + what options a given set of libraries was compiled with. */ +int KRB5_CALLCONV +krb5int_mutex_alloc (k5_mutex_t **m) +{ + k5_mutex_t *ptr; + int err; + + ptr = malloc (sizeof (k5_mutex_t)); + if (ptr == NULL) + return ENOMEM; + err = k5_mutex_init (ptr); + if (err) { + free (ptr); + return err; + } + *m = ptr; + return 0; +} + +void KRB5_CALLCONV +krb5int_mutex_free (k5_mutex_t *m) +{ + (void) k5_mutex_destroy (m); + free (m); +} + +/* Callable versions of the various macros. */ +void KRB5_CALLCONV +krb5int_mutex_lock (k5_mutex_t *m) +{ + k5_mutex_lock (m); +} +void KRB5_CALLCONV +krb5int_mutex_unlock (k5_mutex_t *m) +{ + k5_mutex_unlock (m); +} + +#ifdef USE_CONDITIONAL_PTHREADS + +int +k5_os_mutex_init(k5_os_mutex *m) +{ + if (krb5int_pthread_loaded()) + return pthread_mutex_init(m, 0); + else + return 0; +} + +int +k5_os_mutex_destroy(k5_os_mutex *m) +{ + if (krb5int_pthread_loaded()) + return pthread_mutex_destroy(m); + else + return 0; +} + +int +k5_os_mutex_lock(k5_os_mutex *m) +{ + if (krb5int_pthread_loaded()) + return pthread_mutex_lock(m); + else + return 0; +} + +int +k5_os_mutex_unlock(k5_os_mutex *m) +{ + if (krb5int_pthread_loaded()) + return pthread_mutex_unlock(m); + else + return 0; +} + +int +k5_once(k5_once_t *once, void (*fn)(void)) +{ + if (krb5int_pthread_loaded()) + return pthread_once(&once->o, fn); + else + return k5_os_nothread_once(&once->n, fn); +} + +#else /* USE_CONDITIONAL_PTHREADS */ + +#undef k5_os_mutex_init +#undef k5_os_mutex_destroy +#undef k5_os_mutex_lock +#undef k5_os_mutex_unlock +#undef k5_once + +int k5_os_mutex_init(k5_os_mutex *m); +int k5_os_mutex_destroy(k5_os_mutex *m); +int k5_os_mutex_lock(k5_os_mutex *m); +int k5_os_mutex_unlock(k5_os_mutex *m); +int k5_once(k5_once_t *once, void (*fn)(void)); + +/* Stub functions */ +int +k5_os_mutex_init(k5_os_mutex *m) +{ + return 0; +} +int +k5_os_mutex_destroy(k5_os_mutex *m) +{ + return 0; +} +int +k5_os_mutex_lock(k5_os_mutex *m) +{ + return 0; +} +int +k5_os_mutex_unlock(k5_os_mutex *m) +{ + return 0; +} +int +k5_once(k5_once_t *once, void (*fn)(void)) +{ + return 0; +} + +#endif /* not USE_CONDITIONAL_PTHREADS */ |
