1505d05c7Sgtb #pragma ident	"%Z%%M%	%I%	%E% SMI"
2505d05c7Sgtb 
3505d05c7Sgtb /*
4505d05c7Sgtb  * util/support/threads.c
5505d05c7Sgtb  *
6505d05c7Sgtb  * Copyright 2004 by the Massachusetts Institute of Technology.
7505d05c7Sgtb  * All Rights Reserved.
8505d05c7Sgtb  *
9505d05c7Sgtb  * Export of this software from the United States of America may
10505d05c7Sgtb  *   require a specific license from the United States Government.
11505d05c7Sgtb  *   It is the responsibility of any person or organization contemplating
12505d05c7Sgtb  *   export to obtain such a license before exporting.
13505d05c7Sgtb  *
14505d05c7Sgtb  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
15505d05c7Sgtb  * distribute this software and its documentation for any purpose and
16505d05c7Sgtb  * without fee is hereby granted, provided that the above copyright
17505d05c7Sgtb  * notice appear in all copies and that both that copyright notice and
18505d05c7Sgtb  * this permission notice appear in supporting documentation, and that
19505d05c7Sgtb  * the name of M.I.T. not be used in advertising or publicity pertaining
20505d05c7Sgtb  * to distribution of the software without specific, written prior
21505d05c7Sgtb  * permission.  Furthermore if you modify this software you must label
22505d05c7Sgtb  * your software as modified software and not distribute it in such a
23505d05c7Sgtb  * fashion that it might be confused with the original M.I.T. software.
24505d05c7Sgtb  * M.I.T. makes no representations about the suitability of
25505d05c7Sgtb  * this software for any purpose.  It is provided "as is" without express
26505d05c7Sgtb  * or implied warranty.
27505d05c7Sgtb  *
28505d05c7Sgtb  *
29505d05c7Sgtb  * Preliminary thread support.
30505d05c7Sgtb  */
31505d05c7Sgtb 
32505d05c7Sgtb #include <assert.h>
33505d05c7Sgtb #include <stdlib.h>
34505d05c7Sgtb #include <errno.h>
35505d05c7Sgtb #include <k5-thread.h>
36505d05c7Sgtb #include <k5-platform.h>
37505d05c7Sgtb 
38505d05c7Sgtb MAKE_INIT_FUNCTION(krb5int_thread_support_init);
39505d05c7Sgtb MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
40505d05c7Sgtb 
41505d05c7Sgtb #ifndef ENABLE_THREADS /* no thread support */
42505d05c7Sgtb 
43505d05c7Sgtb static void (*destructors[K5_KEY_MAX])(void *);
44505d05c7Sgtb struct tsd_block { void *values[K5_KEY_MAX]; };
45505d05c7Sgtb static struct tsd_block tsd_no_threads;
46505d05c7Sgtb static unsigned char destructors_set[K5_KEY_MAX];
47505d05c7Sgtb 
48505d05c7Sgtb #elif defined(_WIN32)
49505d05c7Sgtb 
50505d05c7Sgtb static DWORD tls_idx;
51505d05c7Sgtb static CRITICAL_SECTION key_lock;
52505d05c7Sgtb struct tsd_block {
53505d05c7Sgtb   void *values[K5_KEY_MAX];
54505d05c7Sgtb };
55505d05c7Sgtb static void (*destructors[K5_KEY_MAX])(void *);
56505d05c7Sgtb static unsigned char destructors_set[K5_KEY_MAX];
57505d05c7Sgtb 
58505d05c7Sgtb void krb5int_thread_detach_hook (void)
59505d05c7Sgtb {
60505d05c7Sgtb     /* XXX Memory leak here!
61505d05c7Sgtb        Need to destroy all TLS objects we know about for this thread.  */
62505d05c7Sgtb     struct tsd_block *t;
63505d05c7Sgtb     int i, err;
64505d05c7Sgtb 
65505d05c7Sgtb     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
66505d05c7Sgtb     if (err)
67505d05c7Sgtb 	return;
68505d05c7Sgtb 
69505d05c7Sgtb     t = TlsGetValue(tls_idx);
70505d05c7Sgtb     if (t == NULL)
71505d05c7Sgtb 	return;
72505d05c7Sgtb     for (i = 0; i < K5_KEY_MAX; i++) {
73505d05c7Sgtb 	if (destructors_set[i] && destructors[i] && t->values[i]) {
74505d05c7Sgtb 	    void *v = t->values[i];
75505d05c7Sgtb 	    t->values[i] = 0;
76505d05c7Sgtb 	    (*destructors[i])(v);
77505d05c7Sgtb 	}
78505d05c7Sgtb     }
79505d05c7Sgtb }
80505d05c7Sgtb 
81505d05c7Sgtb #else /* POSIX threads */
82505d05c7Sgtb 
83505d05c7Sgtb /* Must support register/delete/register sequence, e.g., if krb5 is
84505d05c7Sgtb    loaded so this support code stays in the process, and gssapi is
85505d05c7Sgtb    loaded, unloaded, and loaded again.  */
86505d05c7Sgtb 
87505d05c7Sgtb static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;
88505d05c7Sgtb static void (*destructors[K5_KEY_MAX])(void *);
89505d05c7Sgtb static unsigned char destructors_set[K5_KEY_MAX];
90505d05c7Sgtb 
91505d05c7Sgtb /* This is not safe yet!
92505d05c7Sgtb 
93505d05c7Sgtb    Thread termination concurrent with key deletion can cause two
94505d05c7Sgtb    threads to interfere.  It's a bit tricky, since one of the threads
95505d05c7Sgtb    will want to remove this structure from the list being walked by
96505d05c7Sgtb    the other.
97505d05c7Sgtb 
98505d05c7Sgtb    Other cases, like looking up data while the library owning the key
99505d05c7Sgtb    is in the process of being unloaded, we don't worry about.  */
100505d05c7Sgtb 
101505d05c7Sgtb struct tsd_block {
102505d05c7Sgtb     struct tsd_block *next;
103505d05c7Sgtb     void *values[K5_KEY_MAX];
104505d05c7Sgtb };
105505d05c7Sgtb 
106505d05c7Sgtb #ifdef HAVE_PRAGMA_WEAK_REF
107505d05c7Sgtb # pragma weak pthread_getspecific
108505d05c7Sgtb # pragma weak pthread_setspecific
109505d05c7Sgtb # pragma weak pthread_key_create
110505d05c7Sgtb # pragma weak pthread_key_delete
111505d05c7Sgtb static struct tsd_block tsd_if_single;
112505d05c7Sgtb # define GET_NO_PTHREAD_TSD()	(&tsd_if_single)
113505d05c7Sgtb #else
114505d05c7Sgtb # define GET_NO_PTHREAD_TSD()	(abort(),(struct tsd_block *)0)
115505d05c7Sgtb #endif
116505d05c7Sgtb 
117505d05c7Sgtb static pthread_key_t key;
118505d05c7Sgtb static void thread_termination(void *);
119505d05c7Sgtb 
120505d05c7Sgtb static void thread_termination (void *tptr)
121505d05c7Sgtb {
122505d05c7Sgtb     int i, pass, none_found;
123505d05c7Sgtb     struct tsd_block *t = tptr;
124505d05c7Sgtb 
125505d05c7Sgtb     /* Make multiple passes in case, for example, a libkrb5 cleanup
126505d05c7Sgtb        function wants to print out an error message, which causes
127505d05c7Sgtb        com_err to allocate a thread-specific buffer, after we just
128505d05c7Sgtb        freed up the old one.
129505d05c7Sgtb 
130505d05c7Sgtb        Shouldn't actually happen, if we're careful, but check just in
131505d05c7Sgtb        case.  */
132505d05c7Sgtb 
133505d05c7Sgtb     pass = 0;
134505d05c7Sgtb     none_found = 0;
135505d05c7Sgtb     while (pass < 4 && !none_found) {
136505d05c7Sgtb 	none_found = 1;
137505d05c7Sgtb 	for (i = 0; i < K5_KEY_MAX; i++) {
138505d05c7Sgtb 	    if (destructors_set[i] && destructors[i] && t->values[i]) {
139505d05c7Sgtb 		void *v = t->values[i];
140505d05c7Sgtb 		t->values[i] = 0;
141505d05c7Sgtb 		(*destructors[i])(v);
142505d05c7Sgtb 		none_found = 0;
143505d05c7Sgtb 	    }
144505d05c7Sgtb 	}
145505d05c7Sgtb     }
146505d05c7Sgtb     /* remove thread from global linked list */
147505d05c7Sgtb }
148505d05c7Sgtb 
149505d05c7Sgtb #endif /* no threads vs Win32 vs POSIX */
150505d05c7Sgtb 
151505d05c7Sgtb void *k5_getspecific (k5_key_t keynum)
152505d05c7Sgtb {
153505d05c7Sgtb     struct tsd_block *t;
154505d05c7Sgtb     int err;
155505d05c7Sgtb 
156505d05c7Sgtb     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
157505d05c7Sgtb     if (err)
158505d05c7Sgtb 	return NULL;
159505d05c7Sgtb 
160505d05c7Sgtb     assert(keynum >= 0 && keynum < K5_KEY_MAX);
161505d05c7Sgtb     assert(destructors_set[keynum] == 1);
162505d05c7Sgtb 
163505d05c7Sgtb #ifndef ENABLE_THREADS
164505d05c7Sgtb 
165505d05c7Sgtb     t = &tsd_no_threads;
166505d05c7Sgtb 
167505d05c7Sgtb #elif defined(_WIN32)
168505d05c7Sgtb 
169505d05c7Sgtb     t = TlsGetValue(tls_idx);
170505d05c7Sgtb 
171505d05c7Sgtb #else /* POSIX */
172505d05c7Sgtb 
173505d05c7Sgtb     if (K5_PTHREADS_LOADED)
174505d05c7Sgtb 	t = pthread_getspecific(key);
175505d05c7Sgtb     else
176505d05c7Sgtb 	t = GET_NO_PTHREAD_TSD();
177505d05c7Sgtb 
178505d05c7Sgtb #endif
179505d05c7Sgtb 
180505d05c7Sgtb     if (t == NULL)
181505d05c7Sgtb 	return NULL;
182505d05c7Sgtb     return t->values[keynum];
183505d05c7Sgtb }
184505d05c7Sgtb 
185505d05c7Sgtb int k5_setspecific (k5_key_t keynum, void *value)
186505d05c7Sgtb {
187505d05c7Sgtb     struct tsd_block *t;
188505d05c7Sgtb     int err;
189505d05c7Sgtb 
190505d05c7Sgtb     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
191505d05c7Sgtb     if (err)
192505d05c7Sgtb 	return err;
193505d05c7Sgtb 
194505d05c7Sgtb     assert(keynum >= 0 && keynum < K5_KEY_MAX);
195505d05c7Sgtb     assert(destructors_set[keynum] == 1);
196505d05c7Sgtb 
197505d05c7Sgtb #ifndef ENABLE_THREADS
198505d05c7Sgtb 
199505d05c7Sgtb     t = &tsd_no_threads;
200505d05c7Sgtb 
201505d05c7Sgtb #elif defined(_WIN32)
202505d05c7Sgtb 
203505d05c7Sgtb     t = TlsGetValue(tls_idx);
204505d05c7Sgtb     if (t == NULL) {
205505d05c7Sgtb 	int i;
206505d05c7Sgtb 	t = malloc(sizeof(*t));
207505d05c7Sgtb 	if (t == NULL)
208505d05c7Sgtb 	    return errno;
209505d05c7Sgtb 	for (i = 0; i < K5_KEY_MAX; i++)
210505d05c7Sgtb 	    t->values[i] = 0;
211505d05c7Sgtb 	/* add to global linked list */
212505d05c7Sgtb 	/*	t->next = 0; */
213505d05c7Sgtb 	err = TlsSetValue(tls_idx, t);
214505d05c7Sgtb 	if (err) {
215505d05c7Sgtb 	    free(t);
216505d05c7Sgtb 	    return err;
217505d05c7Sgtb 	}
218505d05c7Sgtb     }
219505d05c7Sgtb 
220505d05c7Sgtb #else /* POSIX */
221505d05c7Sgtb 
222505d05c7Sgtb     if (K5_PTHREADS_LOADED) {
223505d05c7Sgtb 	t = pthread_getspecific(key);
224505d05c7Sgtb 	if (t == NULL) {
225505d05c7Sgtb 	    int i;
226505d05c7Sgtb 	    t = malloc(sizeof(*t));
227505d05c7Sgtb 	    if (t == NULL)
228505d05c7Sgtb 		return errno;
229505d05c7Sgtb 	    for (i = 0; i < K5_KEY_MAX; i++)
230505d05c7Sgtb 		t->values[i] = 0;
231505d05c7Sgtb 	    /* add to global linked list */
232505d05c7Sgtb 	    t->next = 0;
233505d05c7Sgtb 	    err = pthread_setspecific(key, t);
234505d05c7Sgtb 	    if (err) {
235505d05c7Sgtb 		free(t);
236505d05c7Sgtb 		return err;
237505d05c7Sgtb 	    }
238505d05c7Sgtb 	}
239505d05c7Sgtb     } else {
240505d05c7Sgtb 	t = GET_NO_PTHREAD_TSD();
241505d05c7Sgtb     }
242505d05c7Sgtb 
243505d05c7Sgtb #endif
244505d05c7Sgtb 
245505d05c7Sgtb     t->values[keynum] = value;
246505d05c7Sgtb     return 0;
247505d05c7Sgtb }
248505d05c7Sgtb 
249505d05c7Sgtb int k5_key_register (k5_key_t keynum, void (*destructor)(void *))
250505d05c7Sgtb {
251505d05c7Sgtb     int err;
252505d05c7Sgtb 
253505d05c7Sgtb     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
254505d05c7Sgtb     if (err)
255505d05c7Sgtb 	return err;
256505d05c7Sgtb 
257505d05c7Sgtb     assert(keynum >= 0 && keynum < K5_KEY_MAX);
258505d05c7Sgtb 
259505d05c7Sgtb #ifndef ENABLE_THREADS
260505d05c7Sgtb 
261505d05c7Sgtb     assert(destructors_set[keynum] == 0);
262505d05c7Sgtb     destructors[keynum] = destructor;
263505d05c7Sgtb     destructors_set[keynum] = 1;
264505d05c7Sgtb     err = 0;
265505d05c7Sgtb 
266505d05c7Sgtb #elif defined(_WIN32)
267505d05c7Sgtb 
268505d05c7Sgtb     /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
269505d05c7Sgtb     EnterCriticalSection(&key_lock);
270505d05c7Sgtb     assert(destructors_set[keynum] == 0);
271505d05c7Sgtb     destructors_set[keynum] = 1;
272505d05c7Sgtb     destructors[keynum] = destructor;
273505d05c7Sgtb     LeaveCriticalSection(&key_lock);
274505d05c7Sgtb     err = 0;
275505d05c7Sgtb 
276505d05c7Sgtb #else /* POSIX */
277505d05c7Sgtb 
278505d05c7Sgtb     err = k5_mutex_lock(&key_lock);
279505d05c7Sgtb     if (err == 0) {
280505d05c7Sgtb 	assert(destructors_set[keynum] == 0);
281505d05c7Sgtb 	destructors_set[keynum] = 1;
282505d05c7Sgtb 	destructors[keynum] = destructor;
283505d05c7Sgtb 	err = k5_mutex_unlock(&key_lock);
284505d05c7Sgtb     }
285505d05c7Sgtb 
286505d05c7Sgtb #endif
287505d05c7Sgtb     return 0;
288505d05c7Sgtb }
289505d05c7Sgtb 
290505d05c7Sgtb int k5_key_delete (k5_key_t keynum)
291505d05c7Sgtb {
292505d05c7Sgtb     assert(keynum >= 0 && keynum < K5_KEY_MAX);
293505d05c7Sgtb 
294505d05c7Sgtb #ifndef ENABLE_THREADS
295505d05c7Sgtb 
296505d05c7Sgtb     assert(destructors_set[keynum] == 1);
297505d05c7Sgtb     if (destructors[keynum] && tsd_no_threads.values[keynum])
298505d05c7Sgtb 	(*destructors[keynum])(tsd_no_threads.values[keynum]);
299505d05c7Sgtb     destructors[keynum] = 0;
300505d05c7Sgtb     tsd_no_threads.values[keynum] = 0;
301505d05c7Sgtb     destructors_set[keynum] = 0;
302505d05c7Sgtb 
303505d05c7Sgtb #elif defined(_WIN32)
304505d05c7Sgtb 
305505d05c7Sgtb     /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
306505d05c7Sgtb     EnterCriticalSection(&key_lock);
307505d05c7Sgtb     /* XXX Memory leak here!
308505d05c7Sgtb        Need to destroy the associated data for all threads.
309505d05c7Sgtb        But watch for race conditions in case threads are going away too.  */
310505d05c7Sgtb     LeaveCriticalSection(&key_lock);
311505d05c7Sgtb 
312505d05c7Sgtb #else /* POSIX */
313505d05c7Sgtb 
314505d05c7Sgtb     /* Not written yet.  */
315505d05c7Sgtb     abort();
316505d05c7Sgtb 
317505d05c7Sgtb #endif
318505d05c7Sgtb 
319505d05c7Sgtb     return 0;
320505d05c7Sgtb }
321505d05c7Sgtb 
322505d05c7Sgtb int krb5int_call_thread_support_init (void)
323505d05c7Sgtb {
324505d05c7Sgtb     return CALL_INIT_FUNCTION(krb5int_thread_support_init);
325505d05c7Sgtb }
326505d05c7Sgtb 
327505d05c7Sgtb extern int krb5int_init_fac(void);
328505d05c7Sgtb extern void krb5int_fini_fac(void);
329505d05c7Sgtb 
330505d05c7Sgtb int krb5int_thread_support_init (void)
331505d05c7Sgtb {
332505d05c7Sgtb     int err;
333505d05c7Sgtb 
334505d05c7Sgtb #ifndef ENABLE_THREADS
335505d05c7Sgtb 
336505d05c7Sgtb     /* Nothing to do for TLS initialization.  */
337505d05c7Sgtb 
338505d05c7Sgtb #elif defined(_WIN32)
339505d05c7Sgtb 
340505d05c7Sgtb     tls_idx = TlsAlloc();
341505d05c7Sgtb     /* XXX This can raise an exception if memory is low!  */
342505d05c7Sgtb     InitializeCriticalSection(&key_lock);
343505d05c7Sgtb 
344505d05c7Sgtb #else /* POSIX */
345505d05c7Sgtb 
346505d05c7Sgtb     err = k5_mutex_finish_init(&key_lock);
347505d05c7Sgtb     if (err)
348505d05c7Sgtb 	return err;
349505d05c7Sgtb     if (K5_PTHREADS_LOADED) {
350505d05c7Sgtb 	err = pthread_key_create(&key, thread_termination);
351505d05c7Sgtb 	if (err)
352505d05c7Sgtb 	    return err;
353505d05c7Sgtb     }
354505d05c7Sgtb 
355505d05c7Sgtb #endif
356505d05c7Sgtb 
357505d05c7Sgtb     err = krb5int_init_fac();
358505d05c7Sgtb     if (err)
359505d05c7Sgtb 	return err;
360505d05c7Sgtb 
361505d05c7Sgtb     return 0;
362505d05c7Sgtb }
363505d05c7Sgtb 
364505d05c7Sgtb void krb5int_thread_support_fini (void)
365505d05c7Sgtb {
366505d05c7Sgtb     if (! INITIALIZER_RAN (krb5int_thread_support_init))
367505d05c7Sgtb 	return;
368505d05c7Sgtb 
369505d05c7Sgtb #ifndef ENABLE_THREADS
370505d05c7Sgtb 
371505d05c7Sgtb     /* Do nothing.  */
372505d05c7Sgtb 
373505d05c7Sgtb #elif defined(_WIN32)
374505d05c7Sgtb 
375505d05c7Sgtb     /* ... free stuff ... */
376505d05c7Sgtb     TlsFree(tls_idx);
377505d05c7Sgtb     DeleteCriticalSection(&key_lock);
378505d05c7Sgtb 
379505d05c7Sgtb #else /* POSIX */
380505d05c7Sgtb 
381505d05c7Sgtb     if (! INITIALIZER_RAN(krb5int_thread_support_init))
382505d05c7Sgtb 	return;
383505d05c7Sgtb     if (K5_PTHREADS_LOADED)
384505d05c7Sgtb 	pthread_key_delete(key);
385505d05c7Sgtb     /* ... delete stuff ... */
386505d05c7Sgtb     k5_mutex_destroy(&key_lock);
387505d05c7Sgtb 
388505d05c7Sgtb #endif
389505d05c7Sgtb 
390505d05c7Sgtb     krb5int_fini_fac();
391505d05c7Sgtb }
392*54925bf6Swillf /* Mutex allocation functions, for use in plugins that may not know
393*54925bf6Swillf    what options a given set of libraries was compiled with.  */
394*54925bf6Swillf int KRB5_CALLCONV
395*54925bf6Swillf krb5int_mutex_alloc (k5_mutex_t **m)
396*54925bf6Swillf {
397*54925bf6Swillf     k5_mutex_t *ptr;
398*54925bf6Swillf     int err;
399505d05c7Sgtb 
400*54925bf6Swillf     ptr = malloc (sizeof (k5_mutex_t));
401*54925bf6Swillf     if (ptr == NULL)
402*54925bf6Swillf 	return errno;
403*54925bf6Swillf     err = k5_mutex_init (ptr);
404*54925bf6Swillf     if (err) {
405*54925bf6Swillf 	free (ptr);
406*54925bf6Swillf 	return err;
407*54925bf6Swillf     }
408*54925bf6Swillf     *m = ptr;
409*54925bf6Swillf     return 0;
410*54925bf6Swillf }
411*54925bf6Swillf 
412*54925bf6Swillf void KRB5_CALLCONV
413*54925bf6Swillf krb5int_mutex_free (k5_mutex_t *m)
414*54925bf6Swillf {
415*54925bf6Swillf     (void) k5_mutex_destroy (m);
416*54925bf6Swillf     free (m);
417*54925bf6Swillf }
418*54925bf6Swillf 
419*54925bf6Swillf /* Callable versions of the various macros.  */
420*54925bf6Swillf int KRB5_CALLCONV
421*54925bf6Swillf krb5int_mutex_lock (k5_mutex_t *m)
422*54925bf6Swillf {
423*54925bf6Swillf     return k5_mutex_lock (m);
424*54925bf6Swillf }
425*54925bf6Swillf int KRB5_CALLCONV
426*54925bf6Swillf krb5int_mutex_unlock (k5_mutex_t *m)
427*54925bf6Swillf {
428*54925bf6Swillf     return k5_mutex_unlock (m);
429*54925bf6Swillf }
430