1/*
2 * util/support/threads.c
3 *
4 * Copyright 2004,2005,2006 by the Massachusetts Institute of Technology.
5 * All Rights Reserved.
6 *
7 * Export of this software from the United States of America may
8 *   require a specific license from the United States Government.
9 *   It is the responsibility of any person or organization contemplating
10 *   export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission.  Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose.  It is provided "as is" without express
24 * or implied warranty.
25 *
26 *
27 * Preliminary thread support.
28 */
29
30#include <assert.h>
31#include <stdlib.h>
32#include <errno.h>
33#include "k5-thread.h"
34#include "k5-platform.h"
35#include "supp-int.h"
36
37MAKE_INIT_FUNCTION(krb5int_thread_support_init);
38MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
39
40#ifndef ENABLE_THREADS /* no thread support */
41
42static void (*destructors[K5_KEY_MAX])(void *);
43struct tsd_block { void *values[K5_KEY_MAX]; };
44static struct tsd_block tsd_no_threads;
45static unsigned char destructors_set[K5_KEY_MAX];
46
47int krb5int_pthread_loaded (void)
48{
49    return 0;
50}
51
52#elif defined(_WIN32)
53
54static DWORD tls_idx;
55static CRITICAL_SECTION key_lock;
56struct tsd_block {
57  void *values[K5_KEY_MAX];
58};
59static void (*destructors[K5_KEY_MAX])(void *);
60static unsigned char destructors_set[K5_KEY_MAX];
61
62void krb5int_thread_detach_hook (void)
63{
64    /* XXX Memory leak here!
65       Need to destroy all TLS objects we know about for this thread.  */
66    struct tsd_block *t;
67    int i, err;
68
69    err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
70    if (err)
71	return;
72
73    t = TlsGetValue(tls_idx);
74    if (t == NULL)
75	return;
76    for (i = 0; i < K5_KEY_MAX; i++) {
77	if (destructors_set[i] && destructors[i] && t->values[i]) {
78	    void *v = t->values[i];
79	    t->values[i] = 0;
80	    (*destructors[i])(v);
81	}
82    }
83}
84
85/* Stub function not used on Windows. */
86int krb5int_pthread_loaded (void)
87{
88    return 0;
89}
90#else /* POSIX threads */
91
92/* Must support register/delete/register sequence, e.g., if krb5 is
93   loaded so this support code stays in the process, and gssapi is
94   loaded, unloaded, and loaded again.  */
95
96static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;
97static void (*destructors[K5_KEY_MAX])(void *);
98static unsigned char destructors_set[K5_KEY_MAX];
99
100/* This is not safe yet!
101
102   Thread termination concurrent with key deletion can cause two
103   threads to interfere.  It's a bit tricky, since one of the threads
104   will want to remove this structure from the list being walked by
105   the other.
106
107   Other cases, like looking up data while the library owning the key
108   is in the process of being unloaded, we don't worry about.  */
109
110struct tsd_block {
111    struct tsd_block *next;
112    void *values[K5_KEY_MAX];
113};
114
115#ifdef HAVE_PRAGMA_WEAK_REF
116# pragma weak pthread_getspecific
117# pragma weak pthread_setspecific
118# pragma weak pthread_key_create
119# pragma weak pthread_key_delete
120# pragma weak pthread_create
121# pragma weak pthread_join
122static volatile int flag_pthread_loaded = -1;
123static void loaded_test_aux(void)
124{
125    if (flag_pthread_loaded == -1)
126	flag_pthread_loaded = 1;
127    else
128	/* Could we have been called twice?  */
129	flag_pthread_loaded = 0;
130}
131static pthread_once_t loaded_test_once = PTHREAD_ONCE_INIT;
132int krb5int_pthread_loaded (void)
133{
134    int x = flag_pthread_loaded;
135    if (x != -1)
136	return x;
137    if (&pthread_getspecific == 0
138	|| &pthread_setspecific == 0
139	|| &pthread_key_create == 0
140	|| &pthread_key_delete == 0
141	|| &pthread_once == 0
142	|| &pthread_mutex_lock == 0
143	|| &pthread_mutex_unlock == 0
144	|| &pthread_mutex_destroy == 0
145	|| &pthread_mutex_init == 0
146	|| &pthread_self == 0
147	|| &pthread_equal == 0
148	/* Any program that's really multithreaded will have to be
149	   able to create threads.  */
150	|| &pthread_create == 0
151	|| &pthread_join == 0
152	/* Okay, all the interesting functions -- or stubs for them --
153	   seem to be present.  If we call pthread_once, does it
154	   actually seem to cause the indicated function to get called
155	   exactly one time?  */
156	|| pthread_once(&loaded_test_once, loaded_test_aux) != 0
157	|| pthread_once(&loaded_test_once, loaded_test_aux) != 0
158	/* This catches cases where pthread_once does nothing, and
159	   never causes the function to get called.  That's a pretty
160	   clear violation of the POSIX spec, but hey, it happens.  */
161	|| flag_pthread_loaded < 0) {
162	flag_pthread_loaded = 0;
163	return 0;
164    }
165    /* If we wanted to be super-paranoid, we could try testing whether
166       pthread_get/setspecific work, too.  I don't know -- so far --
167       of any system with non-functional stubs for those.  */
168    return flag_pthread_loaded;
169}
170static struct tsd_block tsd_if_single;
171# define GET_NO_PTHREAD_TSD()	(&tsd_if_single)
172#else
173# define GET_NO_PTHREAD_TSD()	(abort(),(struct tsd_block *)0)
174#endif
175
176static pthread_key_t key;
177static void thread_termination(void *);
178
179static void thread_termination (void *tptr)
180{
181    int err = k5_mutex_lock(&key_lock);
182    if (err == 0) {
183        int i, pass, none_found;
184        struct tsd_block *t = tptr;
185
186        /* Make multiple passes in case, for example, a libkrb5 cleanup
187            function wants to print out an error message, which causes
188            com_err to allocate a thread-specific buffer, after we just
189            freed up the old one.
190
191            Shouldn't actually happen, if we're careful, but check just in
192            case.  */
193
194        pass = 0;
195        none_found = 0;
196        while (pass < 4 && !none_found) {
197            none_found = 1;
198            for (i = 0; i < K5_KEY_MAX; i++) {
199                if (destructors_set[i] && destructors[i] && t->values[i]) {
200                    void *v = t->values[i];
201                    t->values[i] = 0;
202                    (*destructors[i])(v);
203                    none_found = 0;
204                }
205            }
206        }
207        free (t);
208        err = k5_mutex_unlock(&key_lock);
209   }
210
211    /* remove thread from global linked list */
212}
213
214#endif /* no threads vs Win32 vs POSIX */
215
216void *k5_getspecific (k5_key_t keynum)
217{
218    struct tsd_block *t;
219    int err;
220
221    err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
222    if (err)
223	return NULL;
224
225    assert(keynum >= 0 && keynum < K5_KEY_MAX);
226    assert(destructors_set[keynum] == 1);
227
228#ifndef ENABLE_THREADS
229
230    t = &tsd_no_threads;
231
232#elif defined(_WIN32)
233
234    t = TlsGetValue(tls_idx);
235
236#else /* POSIX */
237
238    if (K5_PTHREADS_LOADED)
239	t = pthread_getspecific(key);
240    else
241	t = GET_NO_PTHREAD_TSD();
242
243#endif
244
245    if (t == NULL)
246	return NULL;
247    return t->values[keynum];
248}
249
250int k5_setspecific (k5_key_t keynum, void *value)
251{
252    struct tsd_block *t;
253    int err;
254
255    err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
256    if (err)
257	return err;
258
259    assert(keynum >= 0 && keynum < K5_KEY_MAX);
260    assert(destructors_set[keynum] == 1);
261
262#ifndef ENABLE_THREADS
263
264    t = &tsd_no_threads;
265
266#elif defined(_WIN32)
267
268    t = TlsGetValue(tls_idx);
269    if (t == NULL) {
270	int i;
271	t = malloc(sizeof(*t));
272	if (t == NULL)
273	    return errno;
274	for (i = 0; i < K5_KEY_MAX; i++)
275	    t->values[i] = 0;
276	/* add to global linked list */
277	/*	t->next = 0; */
278	err = TlsSetValue(tls_idx, t);
279	if (!err) {
280	    free(t);
281	    return GetLastError();
282	}
283    }
284
285#else /* POSIX */
286
287    if (K5_PTHREADS_LOADED) {
288	t = pthread_getspecific(key);
289	if (t == NULL) {
290	    int i;
291	    t = malloc(sizeof(*t));
292	    if (t == NULL)
293		return errno;
294	    for (i = 0; i < K5_KEY_MAX; i++)
295		t->values[i] = 0;
296	    /* add to global linked list */
297	    t->next = 0;
298	    err = pthread_setspecific(key, t);
299	    if (err) {
300		free(t);
301		return err;
302	    }
303	}
304    } else {
305	t = GET_NO_PTHREAD_TSD();
306    }
307
308#endif
309
310    t->values[keynum] = value;
311    return 0;
312}
313
314int k5_key_register (k5_key_t keynum, void (*destructor)(void *))
315{
316    int err;
317
318    err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
319    if (err)
320	return err;
321
322    assert(keynum >= 0 && keynum < K5_KEY_MAX);
323
324#ifndef ENABLE_THREADS
325
326    assert(destructors_set[keynum] == 0);
327    destructors[keynum] = destructor;
328    destructors_set[keynum] = 1;
329    err = 0;
330
331#elif defined(_WIN32)
332
333    /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
334    EnterCriticalSection(&key_lock);
335    assert(destructors_set[keynum] == 0);
336    destructors_set[keynum] = 1;
337    destructors[keynum] = destructor;
338    LeaveCriticalSection(&key_lock);
339    err = 0;
340
341#else /* POSIX */
342
343    err = k5_mutex_lock(&key_lock);
344    if (err == 0) {
345	assert(destructors_set[keynum] == 0);
346	destructors_set[keynum] = 1;
347	destructors[keynum] = destructor;
348	err = k5_mutex_unlock(&key_lock);
349    }
350
351#endif
352    return 0;
353}
354
355int k5_key_delete (k5_key_t keynum)
356{
357    assert(keynum >= 0 && keynum < K5_KEY_MAX);
358
359#ifndef ENABLE_THREADS
360
361    assert(destructors_set[keynum] == 1);
362    if (destructors[keynum] && tsd_no_threads.values[keynum])
363	(*destructors[keynum])(tsd_no_threads.values[keynum]);
364    destructors[keynum] = 0;
365    tsd_no_threads.values[keynum] = 0;
366    destructors_set[keynum] = 0;
367
368#elif defined(_WIN32)
369
370    /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
371    EnterCriticalSection(&key_lock);
372    /* XXX Memory leak here!
373       Need to destroy the associated data for all threads.
374       But watch for race conditions in case threads are going away too.  */
375    assert(destructors_set[keynum] == 1);
376    destructors_set[keynum] = 0;
377    destructors[keynum] = 0;
378    LeaveCriticalSection(&key_lock);
379
380#else /* POSIX */
381
382    {
383	int err;
384
385	/* XXX RESOURCE LEAK:
386
387	   Need to destroy the allocated objects first!  */
388
389	err = k5_mutex_lock(&key_lock);
390	if (err == 0) {
391	    assert(destructors_set[keynum] == 1);
392	    destructors_set[keynum] = 0;
393	    destructors[keynum] = NULL;
394	    k5_mutex_unlock(&key_lock);
395	}
396    }
397
398#endif
399
400    return 0;
401}
402
403int krb5int_call_thread_support_init (void)
404{
405    return CALL_INIT_FUNCTION(krb5int_thread_support_init);
406}
407
408#include "cache-addrinfo.h"
409
410#ifdef DEBUG_THREADS_STATS
411#include <stdio.h>
412static FILE *stats_logfile;
413#endif
414
415int krb5int_thread_support_init (void)
416{
417    int err;
418
419#ifdef SHOW_INITFINI_FUNCS
420    printf("krb5int_thread_support_init\n");
421#endif
422
423#ifdef DEBUG_THREADS_STATS
424    /*    stats_logfile = stderr; */
425    stats_logfile = fopen("/dev/tty", "w+");
426    if (stats_logfile == NULL)
427      stats_logfile = stderr;
428#endif
429
430#ifndef ENABLE_THREADS
431
432    /* Nothing to do for TLS initialization.  */
433
434#elif defined(_WIN32)
435
436    tls_idx = TlsAlloc();
437    /* XXX This can raise an exception if memory is low!  */
438    InitializeCriticalSection(&key_lock);
439
440#else /* POSIX */
441
442    err = k5_mutex_finish_init(&key_lock);
443    if (err)
444	return err;
445    if (K5_PTHREADS_LOADED) {
446	err = pthread_key_create(&key, thread_termination);
447	if (err)
448	    return err;
449    }
450
451#endif
452
453    err = krb5int_init_fac();
454    if (err)
455	return err;
456
457    err = krb5int_err_init();
458    if (err)
459	return err;
460
461    return 0;
462}
463
464void krb5int_thread_support_fini (void)
465{
466    if (! INITIALIZER_RAN (krb5int_thread_support_init))
467	return;
468
469#ifdef SHOW_INITFINI_FUNCS
470    printf("krb5int_thread_support_fini\n");
471#endif
472
473#ifndef ENABLE_THREADS
474
475    /* Do nothing.  */
476
477#elif defined(_WIN32)
478
479    /* ... free stuff ... */
480    TlsFree(tls_idx);
481    DeleteCriticalSection(&key_lock);
482
483#else /* POSIX */
484
485    if (! INITIALIZER_RAN(krb5int_thread_support_init))
486	return;
487    if (K5_PTHREADS_LOADED)
488	pthread_key_delete(key);
489    /* ... delete stuff ... */
490    k5_mutex_destroy(&key_lock);
491
492#endif
493
494#ifdef DEBUG_THREADS_STATS
495    fflush(stats_logfile);
496    /* XXX Should close if not stderr, in case unloading library but
497       not exiting.  */
498#endif
499
500    krb5int_fini_fac();
501}
502
503#ifdef DEBUG_THREADS_STATS
504void KRB5_CALLCONV
505k5_mutex_lock_update_stats(k5_debug_mutex_stats *m,
506			   k5_mutex_stats_tmp startwait)
507{
508  k5_debug_time_t now;
509  k5_debug_timediff_t tdiff, tdiff2;
510
511  now = get_current_time();
512  (void) krb5int_call_thread_support_init();
513  m->count++;
514  m->time_acquired = now;
515  tdiff = timediff(now, startwait);
516  tdiff2 = tdiff * tdiff;
517  if (m->count == 1 || m->lockwait.valmin > tdiff)
518    m->lockwait.valmin = tdiff;
519  if (m->count == 1 || m->lockwait.valmax < tdiff)
520    m->lockwait.valmax = tdiff;
521  m->lockwait.valsum += tdiff;
522  m->lockwait.valsqsum += tdiff2;
523}
524
525void KRB5_CALLCONV
526krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
527{
528  k5_debug_time_t now = get_current_time();
529  k5_debug_timediff_t tdiff, tdiff2;
530  tdiff = timediff(now, m->time_acquired);
531  tdiff2 = tdiff * tdiff;
532  if (m->count == 1 || m->lockheld.valmin > tdiff)
533    m->lockheld.valmin = tdiff;
534  if (m->count == 1 || m->lockheld.valmax < tdiff)
535    m->lockheld.valmax = tdiff;
536  m->lockheld.valsum += tdiff;
537  m->lockheld.valsqsum += tdiff2;
538}
539
540#include <math.h>
541static double
542get_stddev(struct k5_timediff_stats sp, int count)
543{
544  long double mu, mu_squared, rho_squared;
545  mu = (long double) sp.valsum / count;
546  mu_squared = mu * mu;
547  /* SUM((x_i - mu)^2)
548     = SUM(x_i^2 - 2*mu*x_i + mu^2)
549     = SUM(x_i^2) - 2*mu*SUM(x_i) + N*mu^2
550
551     Standard deviation rho^2 = SUM(...) / N.  */
552  rho_squared = (sp.valsqsum - 2 * mu * sp.valsum + count * mu_squared) / count;
553  return sqrt(rho_squared);
554}
555
556void KRB5_CALLCONV
557krb5int_mutex_report_stats(k5_mutex_t *m)
558{
559  char *p;
560
561  /* Tweak this to only record data on "interesting" locks.  */
562  if (m->stats.count < 10)
563    return;
564  if (m->stats.lockwait.valsum < 10 * m->stats.count)
565    return;
566
567  p = strrchr(m->loc_created.filename, '/');
568  if (p == NULL)
569    p = m->loc_created.filename;
570  else
571    p++;
572  fprintf(stats_logfile, "mutex @%p: created at line %d of %s\n",
573	  (void *) m, m->loc_created.lineno, p);
574  if (m->stats.count == 0)
575    fprintf(stats_logfile, "\tnever locked\n");
576  else {
577    double sd_wait, sd_hold;
578    sd_wait = get_stddev(m->stats.lockwait, m->stats.count);
579    sd_hold = get_stddev(m->stats.lockheld, m->stats.count);
580    fprintf(stats_logfile,
581	    "\tlocked %d time%s; wait %lu/%f/%lu/%fus, hold %lu/%f/%lu/%fus\n",
582	    m->stats.count, m->stats.count == 1 ? "" : "s",
583	    (unsigned long) m->stats.lockwait.valmin,
584	    (double) m->stats.lockwait.valsum / m->stats.count,
585	    (unsigned long) m->stats.lockwait.valmax,
586	    sd_wait,
587	    (unsigned long) m->stats.lockheld.valmin,
588	    (double) m->stats.lockheld.valsum / m->stats.count,
589	    (unsigned long) m->stats.lockheld.valmax,
590	    sd_hold);
591  }
592}
593#else
594/* On Windows, everything defined in the export list must be defined.
595   The UNIX systems where we're using the export list don't seem to
596   care.  */
597#undef krb5int_mutex_lock_update_stats
598void KRB5_CALLCONV
599krb5int_mutex_lock_update_stats(k5_debug_mutex_stats *m,
600				k5_mutex_stats_tmp startwait)
601{
602}
603#undef krb5int_mutex_unlock_update_stats
604void KRB5_CALLCONV
605krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
606{
607}
608#undef krb5int_mutex_report_stats
609void KRB5_CALLCONV
610krb5int_mutex_report_stats(k5_mutex_t *m)
611{
612}
613#endif
614
615/* Mutex allocation functions, for use in plugins that may not know
616   what options a given set of libraries was compiled with.  */
617int KRB5_CALLCONV
618krb5int_mutex_alloc (k5_mutex_t **m)
619{
620    k5_mutex_t *ptr;
621    int err;
622
623    ptr = malloc (sizeof (k5_mutex_t));
624    if (ptr == NULL)
625	return errno;
626    err = k5_mutex_init (ptr);
627    if (err) {
628	free (ptr);
629	return err;
630    }
631    *m = ptr;
632    return 0;
633}
634
635void KRB5_CALLCONV
636krb5int_mutex_free (k5_mutex_t *m)
637{
638    (void) k5_mutex_destroy (m);
639    free (m);
640}
641
642/* Callable versions of the various macros.  */
643int KRB5_CALLCONV
644krb5int_mutex_lock (k5_mutex_t *m)
645{
646    return k5_mutex_lock (m);
647}
648int KRB5_CALLCONV
649krb5int_mutex_unlock (k5_mutex_t *m)
650{
651    return k5_mutex_unlock (m);
652}
653