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 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 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. */ 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; 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; 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) 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 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 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 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 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 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 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 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 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 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 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 510 krb5int_mutex_lock (k5_mutex_t *m) 511 { 512 k5_mutex_lock (m); 513 } 514 void KRB5_CALLCONV 515 krb5int_mutex_unlock (k5_mutex_t *m) 516 { 517 k5_mutex_unlock (m); 518 } 519 520 #ifdef USE_CONDITIONAL_PTHREADS 521 522 int 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 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 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 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 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 583 k5_os_mutex_init(k5_os_mutex *m) 584 { 585 return 0; 586 } 587 int 588 k5_os_mutex_destroy(k5_os_mutex *m) 589 { 590 return 0; 591 } 592 int 593 k5_os_mutex_lock(k5_os_mutex *m) 594 { 595 return 0; 596 } 597 int 598 k5_os_mutex_unlock(k5_os_mutex *m) 599 { 600 return 0; 601 } 602 int 603 k5_once(k5_once_t *once, void (*fn)(void)) 604 { 605 return 0; 606 } 607 608 #endif /* not USE_CONDITIONAL_PTHREADS */ 609