xref: /titanic_52/usr/src/lib/gss_mechs/mech_krb5/support/threads.c (revision 159d09a20817016f09b3ea28d1bdada4a336bb91)
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 
37 MAKE_INIT_FUNCTION(krb5int_thread_support_init);
38 MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
39 
40 #ifndef ENABLE_THREADS /* no thread support */
41 
42 static void (*destructors[K5_KEY_MAX])(void *);
43 struct tsd_block { void *values[K5_KEY_MAX]; };
44 static struct tsd_block tsd_no_threads;
45 static unsigned char destructors_set[K5_KEY_MAX];
46 
47 int krb5int_pthread_loaded (void)
48 {
49     return 0;
50 }
51 
52 #elif defined(_WIN32)
53 
54 static DWORD tls_idx;
55 static CRITICAL_SECTION key_lock;
56 struct tsd_block {
57   void *values[K5_KEY_MAX];
58 };
59 static void (*destructors[K5_KEY_MAX])(void *);
60 static unsigned char destructors_set[K5_KEY_MAX];
61 
62 void 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. */
86 int 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 
96 static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;
97 static void (*destructors[K5_KEY_MAX])(void *);
98 static 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 
110 struct 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
122 static volatile int flag_pthread_loaded = -1;
123 static 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 }
131 static pthread_once_t loaded_test_once = PTHREAD_ONCE_INIT;
132 int 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 }
170 static 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 
176 static pthread_key_t key;
177 static void thread_termination(void *);
178 
179 static 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 
216 void *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 
250 int 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 
314 int 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 
355 int 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 
403 int 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>
412 static FILE *stats_logfile;
413 #endif
414 
415 int 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 
464 void 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
504 void KRB5_CALLCONV
505 k5_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 
525 void KRB5_CALLCONV
526 krb5int_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>
541 static double
542 get_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 
556 void KRB5_CALLCONV
557 krb5int_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
598 void KRB5_CALLCONV
599 krb5int_mutex_lock_update_stats(k5_debug_mutex_stats *m,
600 				k5_mutex_stats_tmp startwait)
601 {
602 }
603 #undef krb5int_mutex_unlock_update_stats
604 void KRB5_CALLCONV
605 krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
606 {
607 }
608 #undef krb5int_mutex_report_stats
609 void KRB5_CALLCONV
610 krb5int_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.  */
617 int KRB5_CALLCONV
618 krb5int_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 
635 void KRB5_CALLCONV
636 krb5int_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.  */
643 int KRB5_CALLCONV
644 krb5int_mutex_lock (k5_mutex_t *m)
645 {
646     return k5_mutex_lock (m);
647 }
648 int KRB5_CALLCONV
649 krb5int_mutex_unlock (k5_mutex_t *m)
650 {
651     return k5_mutex_unlock (m);
652 }
653