#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include "k5-thread.h"
#include "k5-platform.h"
#include "supp-int.h"
MAKE_INIT_FUNCTION(krb5int_thread_support_init);
MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
#ifndef ENABLE_THREADS
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)
{
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);
}
}
}
int krb5int_pthread_loaded (void)
{
return 0;
}
#else
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];
struct tsd_block {
struct tsd_block *next;
void *values[K5_KEY_MAX];
};
#ifdef HAVE_PRAGMA_WEAK_REF
# 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
static volatile int flag_pthread_loaded = -1;
static void loaded_test_aux(void)
{
if (flag_pthread_loaded == -1)
flag_pthread_loaded = 1;
else
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
|| &pthread_create == 0
|| &pthread_join == 0
|| pthread_once(&loaded_test_once, loaded_test_aux) != 0
|| pthread_once(&loaded_test_once, loaded_test_aux) != 0
|| flag_pthread_loaded < 0) {
flag_pthread_loaded = 0;
return 0;
}
return flag_pthread_loaded;
}
static struct tsd_block tsd_if_single;
# define GET_NO_PTHREAD_TSD() (&tsd_if_single)
#else
# 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 err = k5_mutex_lock(&key_lock);
if (err == 0) {
int i, pass, none_found;
struct tsd_block *t = tptr;
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);
err = k5_mutex_unlock(&key_lock);
}
}
#endif
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
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 errno;
for (i = 0; i < K5_KEY_MAX; i++)
t->values[i] = 0;
err = TlsSetValue(tls_idx, t);
if (!err) {
free(t);
return GetLastError();
}
}
#else
if (K5_PTHREADS_LOADED) {
t = pthread_getspecific(key);
if (t == NULL) {
int i;
t = malloc(sizeof(*t));
if (t == NULL)
return errno;
for (i = 0; i < K5_KEY_MAX; i++)
t->values[i] = 0;
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;
err = 0;
#elif defined(_WIN32)
EnterCriticalSection(&key_lock);
assert(destructors_set[keynum] == 0);
destructors_set[keynum] = 1;
destructors[keynum] = destructor;
LeaveCriticalSection(&key_lock);
err = 0;
#else
err = k5_mutex_lock(&key_lock);
if (err == 0) {
assert(destructors_set[keynum] == 0);
destructors_set[keynum] = 1;
destructors[keynum] = destructor;
err = 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)
EnterCriticalSection(&key_lock);
assert(destructors_set[keynum] == 1);
destructors_set[keynum] = 0;
destructors[keynum] = 0;
LeaveCriticalSection(&key_lock);
#else
{
int err;
err = k5_mutex_lock(&key_lock);
if (err == 0) {
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"
#ifdef DEBUG_THREADS_STATS
#include <stdio.h>
static FILE *stats_logfile;
#endif
int krb5int_thread_support_init (void)
{
int err;
#ifdef SHOW_INITFINI_FUNCS
printf("krb5int_thread_support_init\n");
#endif
#ifdef DEBUG_THREADS_STATS
stats_logfile = fopen("/dev/tty", "w+");
if (stats_logfile == NULL)
stats_logfile = stderr;
#endif
#ifndef ENABLE_THREADS
#elif defined(_WIN32)
tls_idx = TlsAlloc();
InitializeCriticalSection(&key_lock);
#else
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
#elif defined(_WIN32)
TlsFree(tls_idx);
DeleteCriticalSection(&key_lock);
#else
if (! INITIALIZER_RAN(krb5int_thread_support_init))
return;
if (K5_PTHREADS_LOADED)
pthread_key_delete(key);
k5_mutex_destroy(&key_lock);
#endif
#ifdef DEBUG_THREADS_STATS
fflush(stats_logfile);
#endif
krb5int_fini_fac();
}
#ifdef DEBUG_THREADS_STATS
void KRB5_CALLCONV
k5_mutex_lock_update_stats(k5_debug_mutex_stats *m,
k5_mutex_stats_tmp startwait)
{
k5_debug_time_t now;
k5_debug_timediff_t tdiff, tdiff2;
now = get_current_time();
(void) krb5int_call_thread_support_init();
m->count++;
m->time_acquired = now;
tdiff = timediff(now, startwait);
tdiff2 = tdiff * tdiff;
if (m->count == 1 || m->lockwait.valmin > tdiff)
m->lockwait.valmin = tdiff;
if (m->count == 1 || m->lockwait.valmax < tdiff)
m->lockwait.valmax = tdiff;
m->lockwait.valsum += tdiff;
m->lockwait.valsqsum += tdiff2;
}
void KRB5_CALLCONV
krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
{
k5_debug_time_t now = get_current_time();
k5_debug_timediff_t tdiff, tdiff2;
tdiff = timediff(now, m->time_acquired);
tdiff2 = tdiff * tdiff;
if (m->count == 1 || m->lockheld.valmin > tdiff)
m->lockheld.valmin = tdiff;
if (m->count == 1 || m->lockheld.valmax < tdiff)
m->lockheld.valmax = tdiff;
m->lockheld.valsum += tdiff;
m->lockheld.valsqsum += tdiff2;
}
#include <math.h>
static inline double
get_stddev(struct k5_timediff_stats sp, int count)
{
long double mu, mu_squared, rho_squared;
mu = (long double) sp.valsum / count;
mu_squared = mu * mu;
rho_squared = (sp.valsqsum - 2 * mu * sp.valsum + count * mu_squared) / count;
return sqrt(rho_squared);
}
void KRB5_CALLCONV
krb5int_mutex_report_stats(k5_mutex_t *m)
{
char *p;
if (m->stats.count < 10)
return;
if (m->stats.lockwait.valsum < 10 * m->stats.count)
return;
p = strrchr(m->loc_created.filename, '/');
if (p == NULL)
p = m->loc_created.filename;
else
p++;
fprintf(stats_logfile, "mutex @%p: created at line %d of %s\n",
(void *) m, m->loc_created.lineno, p);
if (m->stats.count == 0)
fprintf(stats_logfile, "\tnever locked\n");
else {
double sd_wait, sd_hold;
sd_wait = get_stddev(m->stats.lockwait, m->stats.count);
sd_hold = get_stddev(m->stats.lockheld, m->stats.count);
fprintf(stats_logfile,
"\tlocked %d time%s; wait %lu/%f/%lu/%fus, hold %lu/%f/%lu/%fus\n",
m->stats.count, m->stats.count == 1 ? "" : "s",
(unsigned long) m->stats.lockwait.valmin,
(double) m->stats.lockwait.valsum / m->stats.count,
(unsigned long) m->stats.lockwait.valmax,
sd_wait,
(unsigned long) m->stats.lockheld.valmin,
(double) m->stats.lockheld.valsum / m->stats.count,
(unsigned long) m->stats.lockheld.valmax,
sd_hold);
}
}
#else
#undef krb5int_mutex_lock_update_stats
void KRB5_CALLCONV
krb5int_mutex_lock_update_stats(k5_debug_mutex_stats *m,
k5_mutex_stats_tmp startwait)
{
}
#undef krb5int_mutex_unlock_update_stats
void KRB5_CALLCONV
krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
{
}
#undef krb5int_mutex_report_stats
void KRB5_CALLCONV
krb5int_mutex_report_stats(k5_mutex_t *m)
{
}
#endif
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 errno;
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);
}
int KRB5_CALLCONV
krb5int_mutex_lock (k5_mutex_t *m)
{
return k5_mutex_lock (m);
}
int KRB5_CALLCONV
krb5int_mutex_unlock (k5_mutex_t *m)
{
return k5_mutex_unlock (m);
}