1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* util/support/threads.c - Portable thread support */
3 /*
4 * Copyright 2004,2005,2006,2007,2008 by the Massachusetts Institute of
5 * Technology. 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 #define THREAD_SUPPORT_IMPL
28 #include "k5-platform.h"
29 #include "k5-thread.h"
30 #include "supp-int.h"
31
32 MAKE_INIT_FUNCTION(krb5int_thread_support_init);
33 MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
34
35 /* This function used to be referenced from elsewhere in the tree, but is now
36 * only used internally. Keep it linker-visible for now. */
37 int krb5int_pthread_loaded(void);
38
39 #ifndef ENABLE_THREADS /* no thread support */
40
41 static void (*destructors[K5_KEY_MAX])(void *);
42 struct tsd_block { void *values[K5_KEY_MAX]; };
43 static struct tsd_block tsd_no_threads;
44 static unsigned char destructors_set[K5_KEY_MAX];
45
krb5int_pthread_loaded(void)46 int krb5int_pthread_loaded (void)
47 {
48 return 0;
49 }
50
51 #elif defined(_WIN32)
52
53 static DWORD tls_idx;
54 static CRITICAL_SECTION key_lock;
55 struct tsd_block {
56 void *values[K5_KEY_MAX];
57 };
58 static void (*destructors[K5_KEY_MAX])(void *);
59 static unsigned char destructors_set[K5_KEY_MAX];
60
krb5int_thread_detach_hook(void)61 void krb5int_thread_detach_hook (void)
62 {
63 /* XXX Memory leak here!
64 Need to destroy all TLS objects we know about for this thread. */
65 struct tsd_block *t;
66 int i, err;
67
68 err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
69 if (err)
70 return;
71
72 t = TlsGetValue(tls_idx);
73 if (t == NULL)
74 return;
75 for (i = 0; i < K5_KEY_MAX; i++) {
76 if (destructors_set[i] && destructors[i] && t->values[i]) {
77 void *v = t->values[i];
78 t->values[i] = 0;
79 (*destructors[i])(v);
80 }
81 }
82 }
83
84 /* Stub function not used on Windows. */
krb5int_pthread_loaded(void)85 int krb5int_pthread_loaded (void)
86 {
87 return 0;
88 }
89 #else /* POSIX threads */
90
91 /* Must support register/delete/register sequence, e.g., if krb5 is
92 loaded so this support code stays in the process, and gssapi is
93 loaded, unloaded, and loaded again. */
94
95 static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;
96 static void (*destructors[K5_KEY_MAX])(void *);
97 static unsigned char destructors_set[K5_KEY_MAX];
98
99 /* This is not safe yet!
100
101 Thread termination concurrent with key deletion can cause two
102 threads to interfere. It's a bit tricky, since one of the threads
103 will want to remove this structure from the list being walked by
104 the other.
105
106 Other cases, like looking up data while the library owning the key
107 is in the process of being unloaded, we don't worry about. */
108
109 struct tsd_block {
110 struct tsd_block *next;
111 void *values[K5_KEY_MAX];
112 };
113
114 #ifdef HAVE_PRAGMA_WEAK_REF
115 # pragma weak pthread_once
116 # pragma weak pthread_mutex_lock
117 # pragma weak pthread_mutex_unlock
118 # pragma weak pthread_mutex_destroy
119 # pragma weak pthread_mutex_init
120 # pragma weak pthread_self
121 # pragma weak pthread_equal
122 # pragma weak pthread_getspecific
123 # pragma weak pthread_setspecific
124 # pragma weak pthread_key_create
125 # pragma weak pthread_key_delete
126 # pragma weak pthread_create
127 # pragma weak pthread_join
128 # define K5_PTHREADS_LOADED (krb5int_pthread_loaded())
129 static volatile int flag_pthread_loaded = -1;
loaded_test_aux(void)130 static void loaded_test_aux(void)
131 {
132 if (flag_pthread_loaded == -1)
133 flag_pthread_loaded = 1;
134 else
135 /* Could we have been called twice? */
136 flag_pthread_loaded = 0;
137 }
138 static pthread_once_t loaded_test_once = PTHREAD_ONCE_INIT;
krb5int_pthread_loaded(void)139 int krb5int_pthread_loaded (void)
140 {
141 int x = flag_pthread_loaded;
142 if (x != -1)
143 return x;
144 if (&pthread_getspecific == 0
145 || &pthread_setspecific == 0
146 || &pthread_key_create == 0
147 || &pthread_key_delete == 0
148 || &pthread_once == 0
149 || &pthread_mutex_lock == 0
150 || &pthread_mutex_unlock == 0
151 || &pthread_mutex_destroy == 0
152 || &pthread_mutex_init == 0
153 || &pthread_self == 0
154 || &pthread_equal == 0
155 /* Any program that's really multithreaded will have to be
156 able to create threads. */
157 || &pthread_create == 0
158 || &pthread_join == 0
159 /* Okay, all the interesting functions -- or stubs for them --
160 seem to be present. If we call pthread_once, does it
161 actually seem to cause the indicated function to get called
162 exactly one time? */
163 || pthread_once(&loaded_test_once, loaded_test_aux) != 0
164 || pthread_once(&loaded_test_once, loaded_test_aux) != 0
165 /* This catches cases where pthread_once does nothing, and
166 never causes the function to get called. That's a pretty
167 clear violation of the POSIX spec, but hey, it happens. */
168 || flag_pthread_loaded < 0) {
169 flag_pthread_loaded = 0;
170 return 0;
171 }
172 /* If we wanted to be super-paranoid, we could try testing whether
173 pthread_get/setspecific work, too. I don't know -- so far --
174 of any system with non-functional stubs for those. */
175 return flag_pthread_loaded;
176 }
177
178 static struct tsd_block tsd_if_single;
179 # define GET_NO_PTHREAD_TSD() (&tsd_if_single)
180 #else
181 # define K5_PTHREADS_LOADED (1)
krb5int_pthread_loaded(void)182 int krb5int_pthread_loaded (void)
183 {
184 return 1;
185 }
186
187 # define GET_NO_PTHREAD_TSD() (abort(),(struct tsd_block *)0)
188 #endif
189
190 static pthread_key_t key;
191 static void thread_termination(void *);
192
thread_termination(void * tptr)193 static void thread_termination (void *tptr)
194 {
195 int i, pass, none_found;
196 struct tsd_block *t = tptr;
197
198 k5_mutex_lock(&key_lock);
199
200 /*
201 * Make multiple passes in case, for example, a libkrb5 cleanup
202 * function wants to print out an error message, which causes
203 * com_err to allocate a thread-specific buffer, after we just
204 * freed up the old one.
205 *
206 * Shouldn't actually happen, if we're careful, but check just in
207 * case.
208 */
209
210 pass = 0;
211 none_found = 0;
212 while (pass < 4 && !none_found) {
213 none_found = 1;
214 for (i = 0; i < K5_KEY_MAX; i++) {
215 if (destructors_set[i] && destructors[i] && t->values[i]) {
216 void *v = t->values[i];
217 t->values[i] = 0;
218 (*destructors[i])(v);
219 none_found = 0;
220 }
221 }
222 }
223 free (t);
224 k5_mutex_unlock(&key_lock);
225
226 /* remove thread from global linked list */
227 }
228
229 #endif /* no threads vs Win32 vs POSIX */
230
k5_getspecific(k5_key_t keynum)231 void *k5_getspecific (k5_key_t keynum)
232 {
233 struct tsd_block *t;
234 int err;
235
236 err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
237 if (err)
238 return NULL;
239
240 assert(destructors_set[keynum] == 1);
241
242 #ifndef ENABLE_THREADS
243
244 t = &tsd_no_threads;
245
246 #elif defined(_WIN32)
247
248 t = TlsGetValue(tls_idx);
249
250 #else /* POSIX */
251
252 if (K5_PTHREADS_LOADED)
253 t = pthread_getspecific(key);
254 else
255 t = GET_NO_PTHREAD_TSD();
256
257 #endif
258
259 if (t == NULL)
260 return NULL;
261 return t->values[keynum];
262 }
263
k5_setspecific(k5_key_t keynum,void * value)264 int k5_setspecific (k5_key_t keynum, void *value)
265 {
266 struct tsd_block *t;
267 int err;
268
269 err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
270 if (err)
271 return err;
272
273 assert(destructors_set[keynum] == 1);
274
275 #ifndef ENABLE_THREADS
276
277 t = &tsd_no_threads;
278
279 #elif defined(_WIN32)
280
281 t = TlsGetValue(tls_idx);
282 if (t == NULL) {
283 int i;
284 t = malloc(sizeof(*t));
285 if (t == NULL)
286 return ENOMEM;
287 for (i = 0; i < K5_KEY_MAX; i++)
288 t->values[i] = 0;
289 /* add to global linked list */
290 /* t->next = 0; */
291 err = TlsSetValue(tls_idx, t);
292 if (!err) {
293 free(t);
294 return GetLastError();
295 }
296 }
297
298 #else /* POSIX */
299
300 if (K5_PTHREADS_LOADED) {
301 t = pthread_getspecific(key);
302 if (t == NULL) {
303 int i;
304 t = malloc(sizeof(*t));
305 if (t == NULL)
306 return ENOMEM;
307 for (i = 0; i < K5_KEY_MAX; i++)
308 t->values[i] = 0;
309 /* add to global linked list */
310 t->next = 0;
311 err = pthread_setspecific(key, t);
312 if (err) {
313 free(t);
314 return err;
315 }
316 }
317 } else {
318 t = GET_NO_PTHREAD_TSD();
319 }
320
321 #endif
322
323 t->values[keynum] = value;
324 return 0;
325 }
326
k5_key_register(k5_key_t keynum,void (* destructor)(void *))327 int k5_key_register (k5_key_t keynum, void (*destructor)(void *))
328 {
329 int err;
330
331 err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
332 if (err)
333 return err;
334
335 #ifndef ENABLE_THREADS
336
337 assert(destructors_set[keynum] == 0);
338 destructors[keynum] = destructor;
339 destructors_set[keynum] = 1;
340
341 #elif defined(_WIN32)
342
343 /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK. */
344 EnterCriticalSection(&key_lock);
345 assert(destructors_set[keynum] == 0);
346 destructors_set[keynum] = 1;
347 destructors[keynum] = destructor;
348 LeaveCriticalSection(&key_lock);
349
350 #else /* POSIX */
351
352 k5_mutex_lock(&key_lock);
353 assert(destructors_set[keynum] == 0);
354 destructors_set[keynum] = 1;
355 destructors[keynum] = destructor;
356 k5_mutex_unlock(&key_lock);
357
358 #endif
359 return 0;
360 }
361
k5_key_delete(k5_key_t keynum)362 int k5_key_delete (k5_key_t keynum)
363 {
364 #ifndef ENABLE_THREADS
365
366 assert(destructors_set[keynum] == 1);
367 if (destructors[keynum] && tsd_no_threads.values[keynum])
368 (*destructors[keynum])(tsd_no_threads.values[keynum]);
369 destructors[keynum] = 0;
370 tsd_no_threads.values[keynum] = 0;
371 destructors_set[keynum] = 0;
372
373 #elif defined(_WIN32)
374
375 /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK. */
376 EnterCriticalSection(&key_lock);
377 /* XXX Memory leak here!
378 Need to destroy the associated data for all threads.
379 But watch for race conditions in case threads are going away too. */
380 assert(destructors_set[keynum] == 1);
381 destructors_set[keynum] = 0;
382 destructors[keynum] = 0;
383 LeaveCriticalSection(&key_lock);
384
385 #else /* POSIX */
386
387 /* XXX RESOURCE LEAK: Need to destroy the allocated objects first! */
388 k5_mutex_lock(&key_lock);
389 assert(destructors_set[keynum] == 1);
390 destructors_set[keynum] = 0;
391 destructors[keynum] = NULL;
392 k5_mutex_unlock(&key_lock);
393
394 #endif
395
396 return 0;
397 }
398
krb5int_call_thread_support_init(void)399 int krb5int_call_thread_support_init (void)
400 {
401 return CALL_INIT_FUNCTION(krb5int_thread_support_init);
402 }
403
404 #include "cache-addrinfo.h"
405
krb5int_thread_support_init(void)406 int krb5int_thread_support_init (void)
407 {
408 int err;
409
410 #ifdef SHOW_INITFINI_FUNCS
411 printf("krb5int_thread_support_init\n");
412 #endif
413
414 #ifndef ENABLE_THREADS
415
416 /* Nothing to do for TLS initialization. */
417
418 #elif defined(_WIN32)
419
420 tls_idx = TlsAlloc();
421 /* XXX This can raise an exception if memory is low! */
422 InitializeCriticalSection(&key_lock);
423
424 #else /* POSIX */
425
426 err = k5_mutex_finish_init(&key_lock);
427 if (err)
428 return err;
429 if (K5_PTHREADS_LOADED) {
430 err = pthread_key_create(&key, thread_termination);
431 if (err)
432 return err;
433 }
434
435 #endif
436
437 err = krb5int_init_fac();
438 if (err)
439 return err;
440
441 err = krb5int_err_init();
442 if (err)
443 return err;
444
445 return 0;
446 }
447
krb5int_thread_support_fini(void)448 void krb5int_thread_support_fini (void)
449 {
450 if (! INITIALIZER_RAN (krb5int_thread_support_init))
451 return;
452
453 #ifdef SHOW_INITFINI_FUNCS
454 printf("krb5int_thread_support_fini\n");
455 #endif
456
457 #ifndef ENABLE_THREADS
458
459 /* Do nothing. */
460
461 #elif defined(_WIN32)
462
463 /* ... free stuff ... */
464 TlsFree(tls_idx);
465 DeleteCriticalSection(&key_lock);
466
467 #else /* POSIX */
468
469 if (! INITIALIZER_RAN(krb5int_thread_support_init))
470 return;
471 if (K5_PTHREADS_LOADED)
472 pthread_key_delete(key);
473 /* ... delete stuff ... */
474 k5_mutex_destroy(&key_lock);
475
476 #endif
477
478 krb5int_fini_fac();
479 }
480
481 /* Mutex allocation functions, for use in plugins that may not know
482 what options a given set of libraries was compiled with. */
483 int KRB5_CALLCONV
krb5int_mutex_alloc(k5_mutex_t ** m)484 krb5int_mutex_alloc (k5_mutex_t **m)
485 {
486 k5_mutex_t *ptr;
487 int err;
488
489 ptr = malloc (sizeof (k5_mutex_t));
490 if (ptr == NULL)
491 return ENOMEM;
492 err = k5_mutex_init (ptr);
493 if (err) {
494 free (ptr);
495 return err;
496 }
497 *m = ptr;
498 return 0;
499 }
500
501 void KRB5_CALLCONV
krb5int_mutex_free(k5_mutex_t * m)502 krb5int_mutex_free (k5_mutex_t *m)
503 {
504 (void) k5_mutex_destroy (m);
505 free (m);
506 }
507
508 /* Callable versions of the various macros. */
509 void KRB5_CALLCONV
krb5int_mutex_lock(k5_mutex_t * m)510 krb5int_mutex_lock (k5_mutex_t *m)
511 {
512 k5_mutex_lock (m);
513 }
514 void KRB5_CALLCONV
krb5int_mutex_unlock(k5_mutex_t * m)515 krb5int_mutex_unlock (k5_mutex_t *m)
516 {
517 k5_mutex_unlock (m);
518 }
519
520 #ifdef USE_CONDITIONAL_PTHREADS
521
522 int
k5_os_mutex_init(k5_os_mutex * m)523 k5_os_mutex_init(k5_os_mutex *m)
524 {
525 if (krb5int_pthread_loaded())
526 return pthread_mutex_init(m, 0);
527 else
528 return 0;
529 }
530
531 int
k5_os_mutex_destroy(k5_os_mutex * m)532 k5_os_mutex_destroy(k5_os_mutex *m)
533 {
534 if (krb5int_pthread_loaded())
535 return pthread_mutex_destroy(m);
536 else
537 return 0;
538 }
539
540 int
k5_os_mutex_lock(k5_os_mutex * m)541 k5_os_mutex_lock(k5_os_mutex *m)
542 {
543 if (krb5int_pthread_loaded())
544 return pthread_mutex_lock(m);
545 else
546 return 0;
547 }
548
549 int
k5_os_mutex_unlock(k5_os_mutex * m)550 k5_os_mutex_unlock(k5_os_mutex *m)
551 {
552 if (krb5int_pthread_loaded())
553 return pthread_mutex_unlock(m);
554 else
555 return 0;
556 }
557
558 int
k5_once(k5_once_t * once,void (* fn)(void))559 k5_once(k5_once_t *once, void (*fn)(void))
560 {
561 if (krb5int_pthread_loaded())
562 return pthread_once(&once->o, fn);
563 else
564 return k5_os_nothread_once(&once->n, fn);
565 }
566
567 #else /* USE_CONDITIONAL_PTHREADS */
568
569 #undef k5_os_mutex_init
570 #undef k5_os_mutex_destroy
571 #undef k5_os_mutex_lock
572 #undef k5_os_mutex_unlock
573 #undef k5_once
574
575 int k5_os_mutex_init(k5_os_mutex *m);
576 int k5_os_mutex_destroy(k5_os_mutex *m);
577 int k5_os_mutex_lock(k5_os_mutex *m);
578 int k5_os_mutex_unlock(k5_os_mutex *m);
579 int k5_once(k5_once_t *once, void (*fn)(void));
580
581 /* Stub functions */
582 int
k5_os_mutex_init(k5_os_mutex * m)583 k5_os_mutex_init(k5_os_mutex *m)
584 {
585 return 0;
586 }
587 int
k5_os_mutex_destroy(k5_os_mutex * m)588 k5_os_mutex_destroy(k5_os_mutex *m)
589 {
590 return 0;
591 }
592 int
k5_os_mutex_lock(k5_os_mutex * m)593 k5_os_mutex_lock(k5_os_mutex *m)
594 {
595 return 0;
596 }
597 int
k5_os_mutex_unlock(k5_os_mutex * m)598 k5_os_mutex_unlock(k5_os_mutex *m)
599 {
600 return 0;
601 }
602 int
k5_once(k5_once_t * once,void (* fn)(void))603 k5_once(k5_once_t *once, void (*fn)(void))
604 {
605 return 0;
606 }
607
608 #endif /* not USE_CONDITIONAL_PTHREADS */
609