xref: /freebsd/crypto/krb5/src/util/support/threads.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
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