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
krb5int_pthread_loaded(void)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
krb5int_thread_detach_hook(void)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. */
krb5int_pthread_loaded(void)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;
loaded_test_aux(void)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;
krb5int_pthread_loaded(void)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
thread_termination(void * tptr)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
k5_getspecific(k5_key_t keynum)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
k5_setspecific(k5_key_t keynum,void * value)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
k5_key_register(k5_key_t keynum,void (* destructor)(void *))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
k5_key_delete(k5_key_t keynum)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
krb5int_call_thread_support_init(void)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
krb5int_thread_support_init(void)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
krb5int_thread_support_fini(void)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
k5_mutex_lock_update_stats(k5_debug_mutex_stats * m,k5_mutex_stats_tmp startwait)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
krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats * m)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
get_stddev(struct k5_timediff_stats sp,int count)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
krb5int_mutex_report_stats(k5_mutex_t * m)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
krb5int_mutex_lock_update_stats(k5_debug_mutex_stats * m,k5_mutex_stats_tmp startwait)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
krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats * m)605 krb5int_mutex_unlock_update_stats(k5_debug_mutex_stats *m)
606 {
607 }
608 #undef krb5int_mutex_report_stats
609 void KRB5_CALLCONV
krb5int_mutex_report_stats(k5_mutex_t * m)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
krb5int_mutex_alloc(k5_mutex_t ** m)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
krb5int_mutex_free(k5_mutex_t * m)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
krb5int_mutex_lock(k5_mutex_t * m)644 krb5int_mutex_lock (k5_mutex_t *m)
645 {
646 return k5_mutex_lock (m);
647 }
648 int KRB5_CALLCONV
krb5int_mutex_unlock(k5_mutex_t * m)649 krb5int_mutex_unlock (k5_mutex_t *m)
650 {
651 return k5_mutex_unlock (m);
652 }
653