xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/support/threads.c (revision 355b4669e025ff377602b6fc7caaf30dbc218371)
1 #pragma ident	"%Z%%M%	%I%	%E% SMI"
2 
3 /*
4  * util/support/threads.c
5  *
6  * Copyright 2004 by the Massachusetts Institute of Technology.
7  * All Rights Reserved.
8  *
9  * Export of this software from the United States of America may
10  *   require a specific license from the United States Government.
11  *   It is the responsibility of any person or organization contemplating
12  *   export to obtain such a license before exporting.
13  *
14  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
15  * distribute this software and its documentation for any purpose and
16  * without fee is hereby granted, provided that the above copyright
17  * notice appear in all copies and that both that copyright notice and
18  * this permission notice appear in supporting documentation, and that
19  * the name of M.I.T. not be used in advertising or publicity pertaining
20  * to distribution of the software without specific, written prior
21  * permission.  Furthermore if you modify this software you must label
22  * your software as modified software and not distribute it in such a
23  * fashion that it might be confused with the original M.I.T. software.
24  * M.I.T. makes no representations about the suitability of
25  * this software for any purpose.  It is provided "as is" without express
26  * or implied warranty.
27  *
28  *
29  * Preliminary thread support.
30  */
31 
32 #include <assert.h>
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <k5-thread.h>
36 #include <k5-platform.h>
37 
38 MAKE_INIT_FUNCTION(krb5int_thread_support_init);
39 MAKE_FINI_FUNCTION(krb5int_thread_support_fini);
40 
41 #ifndef ENABLE_THREADS /* no thread support */
42 
43 static void (*destructors[K5_KEY_MAX])(void *);
44 struct tsd_block { void *values[K5_KEY_MAX]; };
45 static struct tsd_block tsd_no_threads;
46 static unsigned char destructors_set[K5_KEY_MAX];
47 
48 #elif defined(_WIN32)
49 
50 static DWORD tls_idx;
51 static CRITICAL_SECTION key_lock;
52 struct tsd_block {
53   void *values[K5_KEY_MAX];
54 };
55 static void (*destructors[K5_KEY_MAX])(void *);
56 static unsigned char destructors_set[K5_KEY_MAX];
57 
58 void krb5int_thread_detach_hook (void)
59 {
60     /* XXX Memory leak here!
61        Need to destroy all TLS objects we know about for this thread.  */
62     struct tsd_block *t;
63     int i, err;
64 
65     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
66     if (err)
67 	return;
68 
69     t = TlsGetValue(tls_idx);
70     if (t == NULL)
71 	return;
72     for (i = 0; i < K5_KEY_MAX; i++) {
73 	if (destructors_set[i] && destructors[i] && t->values[i]) {
74 	    void *v = t->values[i];
75 	    t->values[i] = 0;
76 	    (*destructors[i])(v);
77 	}
78     }
79 }
80 
81 #else /* POSIX threads */
82 
83 /* Must support register/delete/register sequence, e.g., if krb5 is
84    loaded so this support code stays in the process, and gssapi is
85    loaded, unloaded, and loaded again.  */
86 
87 static k5_mutex_t key_lock = K5_MUTEX_PARTIAL_INITIALIZER;
88 static void (*destructors[K5_KEY_MAX])(void *);
89 static unsigned char destructors_set[K5_KEY_MAX];
90 
91 /* This is not safe yet!
92 
93    Thread termination concurrent with key deletion can cause two
94    threads to interfere.  It's a bit tricky, since one of the threads
95    will want to remove this structure from the list being walked by
96    the other.
97 
98    Other cases, like looking up data while the library owning the key
99    is in the process of being unloaded, we don't worry about.  */
100 
101 struct tsd_block {
102     struct tsd_block *next;
103     void *values[K5_KEY_MAX];
104 };
105 
106 #ifdef HAVE_PRAGMA_WEAK_REF
107 # pragma weak pthread_getspecific
108 # pragma weak pthread_setspecific
109 # pragma weak pthread_key_create
110 # pragma weak pthread_key_delete
111 static struct tsd_block tsd_if_single;
112 # define GET_NO_PTHREAD_TSD()	(&tsd_if_single)
113 #else
114 # define GET_NO_PTHREAD_TSD()	(abort(),(struct tsd_block *)0)
115 #endif
116 
117 static pthread_key_t key;
118 static void thread_termination(void *);
119 
120 static void thread_termination (void *tptr)
121 {
122     int i, pass, none_found;
123     struct tsd_block *t = tptr;
124 
125     /* Make multiple passes in case, for example, a libkrb5 cleanup
126        function wants to print out an error message, which causes
127        com_err to allocate a thread-specific buffer, after we just
128        freed up the old one.
129 
130        Shouldn't actually happen, if we're careful, but check just in
131        case.  */
132 
133     pass = 0;
134     none_found = 0;
135     while (pass < 4 && !none_found) {
136 	none_found = 1;
137 	for (i = 0; i < K5_KEY_MAX; i++) {
138 	    if (destructors_set[i] && destructors[i] && t->values[i]) {
139 		void *v = t->values[i];
140 		t->values[i] = 0;
141 		(*destructors[i])(v);
142 		none_found = 0;
143 	    }
144 	}
145     }
146     /* remove thread from global linked list */
147 }
148 
149 #endif /* no threads vs Win32 vs POSIX */
150 
151 void *k5_getspecific (k5_key_t keynum)
152 {
153     struct tsd_block *t;
154     int err;
155 
156     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
157     if (err)
158 	return NULL;
159 
160     assert(keynum >= 0 && keynum < K5_KEY_MAX);
161     assert(destructors_set[keynum] == 1);
162 
163 #ifndef ENABLE_THREADS
164 
165     t = &tsd_no_threads;
166 
167 #elif defined(_WIN32)
168 
169     t = TlsGetValue(tls_idx);
170 
171 #else /* POSIX */
172 
173     if (K5_PTHREADS_LOADED)
174 	t = pthread_getspecific(key);
175     else
176 	t = GET_NO_PTHREAD_TSD();
177 
178 #endif
179 
180     if (t == NULL)
181 	return NULL;
182     return t->values[keynum];
183 }
184 
185 int k5_setspecific (k5_key_t keynum, void *value)
186 {
187     struct tsd_block *t;
188     int err;
189 
190     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
191     if (err)
192 	return err;
193 
194     assert(keynum >= 0 && keynum < K5_KEY_MAX);
195     assert(destructors_set[keynum] == 1);
196 
197 #ifndef ENABLE_THREADS
198 
199     t = &tsd_no_threads;
200 
201 #elif defined(_WIN32)
202 
203     t = TlsGetValue(tls_idx);
204     if (t == NULL) {
205 	int i;
206 	t = malloc(sizeof(*t));
207 	if (t == NULL)
208 	    return errno;
209 	for (i = 0; i < K5_KEY_MAX; i++)
210 	    t->values[i] = 0;
211 	/* add to global linked list */
212 	/*	t->next = 0; */
213 	err = TlsSetValue(tls_idx, t);
214 	if (err) {
215 	    free(t);
216 	    return err;
217 	}
218     }
219 
220 #else /* POSIX */
221 
222     if (K5_PTHREADS_LOADED) {
223 	t = pthread_getspecific(key);
224 	if (t == NULL) {
225 	    int i;
226 	    t = malloc(sizeof(*t));
227 	    if (t == NULL)
228 		return errno;
229 	    for (i = 0; i < K5_KEY_MAX; i++)
230 		t->values[i] = 0;
231 	    /* add to global linked list */
232 	    t->next = 0;
233 	    err = pthread_setspecific(key, t);
234 	    if (err) {
235 		free(t);
236 		return err;
237 	    }
238 	}
239     } else {
240 	t = GET_NO_PTHREAD_TSD();
241     }
242 
243 #endif
244 
245     t->values[keynum] = value;
246     return 0;
247 }
248 
249 int k5_key_register (k5_key_t keynum, void (*destructor)(void *))
250 {
251     int err;
252 
253     err = CALL_INIT_FUNCTION(krb5int_thread_support_init);
254     if (err)
255 	return err;
256 
257     assert(keynum >= 0 && keynum < K5_KEY_MAX);
258 
259 #ifndef ENABLE_THREADS
260 
261     assert(destructors_set[keynum] == 0);
262     destructors[keynum] = destructor;
263     destructors_set[keynum] = 1;
264     err = 0;
265 
266 #elif defined(_WIN32)
267 
268     /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
269     EnterCriticalSection(&key_lock);
270     assert(destructors_set[keynum] == 0);
271     destructors_set[keynum] = 1;
272     destructors[keynum] = destructor;
273     LeaveCriticalSection(&key_lock);
274     err = 0;
275 
276 #else /* POSIX */
277 
278     err = k5_mutex_lock(&key_lock);
279     if (err == 0) {
280 	assert(destructors_set[keynum] == 0);
281 	destructors_set[keynum] = 1;
282 	destructors[keynum] = destructor;
283 	err = k5_mutex_unlock(&key_lock);
284     }
285 
286 #endif
287     return 0;
288 }
289 
290 int k5_key_delete (k5_key_t keynum)
291 {
292     assert(keynum >= 0 && keynum < K5_KEY_MAX);
293 
294 #ifndef ENABLE_THREADS
295 
296     assert(destructors_set[keynum] == 1);
297     if (destructors[keynum] && tsd_no_threads.values[keynum])
298 	(*destructors[keynum])(tsd_no_threads.values[keynum]);
299     destructors[keynum] = 0;
300     tsd_no_threads.values[keynum] = 0;
301     destructors_set[keynum] = 0;
302 
303 #elif defined(_WIN32)
304 
305     /* XXX: This can raise EXCEPTION_POSSIBLE_DEADLOCK.  */
306     EnterCriticalSection(&key_lock);
307     /* XXX Memory leak here!
308        Need to destroy the associated data for all threads.
309        But watch for race conditions in case threads are going away too.  */
310     LeaveCriticalSection(&key_lock);
311 
312 #else /* POSIX */
313 
314     /* Not written yet.  */
315     abort();
316 
317 #endif
318 
319     return 0;
320 }
321 
322 int krb5int_call_thread_support_init (void)
323 {
324     return CALL_INIT_FUNCTION(krb5int_thread_support_init);
325 }
326 
327 extern int krb5int_init_fac(void);
328 extern void krb5int_fini_fac(void);
329 
330 int krb5int_thread_support_init (void)
331 {
332     int err;
333 
334 #ifndef ENABLE_THREADS
335 
336     /* Nothing to do for TLS initialization.  */
337 
338 #elif defined(_WIN32)
339 
340     tls_idx = TlsAlloc();
341     /* XXX This can raise an exception if memory is low!  */
342     InitializeCriticalSection(&key_lock);
343 
344 #else /* POSIX */
345 
346     err = k5_mutex_finish_init(&key_lock);
347     if (err)
348 	return err;
349     if (K5_PTHREADS_LOADED) {
350 	err = pthread_key_create(&key, thread_termination);
351 	if (err)
352 	    return err;
353     }
354 
355 #endif
356 
357     err = krb5int_init_fac();
358     if (err)
359 	return err;
360 
361     return 0;
362 }
363 
364 void krb5int_thread_support_fini (void)
365 {
366     if (! INITIALIZER_RAN (krb5int_thread_support_init))
367 	return;
368 
369 #ifndef ENABLE_THREADS
370 
371     /* Do nothing.  */
372 
373 #elif defined(_WIN32)
374 
375     /* ... free stuff ... */
376     TlsFree(tls_idx);
377     DeleteCriticalSection(&key_lock);
378 
379 #else /* POSIX */
380 
381     if (! INITIALIZER_RAN(krb5int_thread_support_init))
382 	return;
383     if (K5_PTHREADS_LOADED)
384 	pthread_key_delete(key);
385     /* ... delete stuff ... */
386     k5_mutex_destroy(&key_lock);
387 
388 #endif
389 
390     krb5int_fini_fac();
391 }
392 
393