1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* include/k5-thread.h - Preliminary portable thread support */ 3 /* 4 * Copyright 2004,2005,2006,2007,2008 by the Massachusetts Institute of Technology. 5 * 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 #ifndef K5_THREAD_H 28 #define K5_THREAD_H 29 30 #include "autoconf.h" 31 #ifndef KRB5_CALLCONV 32 # define KRB5_CALLCONV 33 #endif 34 #ifndef KRB5_CALLCONV_C 35 # define KRB5_CALLCONV_C 36 #endif 37 38 /* Interface (tentative): 39 40 Mutex support: 41 42 // Between these two, we should be able to do pure compile-time 43 // and pure run-time initialization. 44 // POSIX: partial initializer is PTHREAD_MUTEX_INITIALIZER, 45 // finish does nothing 46 // Windows: partial initializer is an invalid handle, 47 // finish does the real initialization work 48 k5_mutex_t foo_mutex = K5_MUTEX_PARTIAL_INITIALIZER; 49 int k5_mutex_finish_init(k5_mutex_t *); 50 // for dynamic allocation 51 int k5_mutex_init(k5_mutex_t *); 52 // Must work for both kinds of alloc, even if it means adding flags. 53 int k5_mutex_destroy(k5_mutex_t *); 54 55 // As before. 56 int k5_mutex_lock(k5_mutex_t *); 57 int k5_mutex_unlock(k5_mutex_t *); 58 59 In each library, one new function to finish the static mutex init, 60 and any other library-wide initialization that might be desired. 61 On POSIX, this function would be called via the second support 62 function (see below). On Windows, it would be called at library 63 load time. These functions, or functions they calls, should be the 64 only places that k5_mutex_finish_init gets called. 65 66 A second function or macro called at various possible "first" entry 67 points which either calls pthread_once on the first function 68 (POSIX), or checks some flag set by the first function (Windows), 69 and possibly returns an error. (In the non-threaded case, a simple 70 flag can be used to avoid multiple invocations, and the mutexes 71 don't need run-time initialization anyways.) 72 73 A third function for library termination calls mutex_destroy on 74 each mutex for the library. This function would be called 75 automatically at library unload time. If it turns out to be needed 76 at exit time for libraries that don't get unloaded, perhaps we 77 should also use atexit(). Any static mutexes should be cleaned up 78 with k5_mutex_destroy here. 79 80 How does that second support function invoke the first support 81 function only once? Through something modelled on pthread_once 82 that I haven't written up yet. Probably: 83 84 k5_once_t foo_once = K5_ONCE_INIT; 85 k5_once(k5_once_t *, void (*)(void)); 86 87 For POSIX: Map onto pthread_once facility. 88 For non-threaded case: A simple flag. 89 For Windows: Not needed; library init code takes care of it. 90 91 XXX: A general k5_once mechanism isn't possible for Windows, 92 without faking it through named mutexes or mutexes initialized at 93 startup. I was only using it in one place outside these headers, 94 so I'm dropping the general scheme. Eventually the existing uses 95 in k5-thread.h and k5-platform.h will be converted to pthread_once 96 or static variables. 97 98 99 Thread-specific data: 100 101 // TSD keys are limited in number in gssapi/krb5/com_err; enumerate 102 // them all. This allows support code init to allocate the 103 // necessary storage for pointers all at once, and avoids any 104 // possible error in key creation. 105 enum { ... } k5_key_t; 106 // Register destructor function. Called in library init code. 107 int k5_key_register(k5_key_t, void (*destructor)(void *)); 108 // Returns NULL or data. 109 void *k5_getspecific(k5_key_t); 110 // Returns error if key out of bounds, or the pointer table can't 111 // be allocated. A call to k5_key_register must have happened first. 112 // This may trigger the calling of pthread_setspecific on POSIX. 113 int k5_setspecific(k5_key_t, void *); 114 // Called in library termination code. 115 // Trashes data in all threads, calling the registered destructor 116 // (but calling it from the current thread). 117 int k5_key_delete(k5_key_t); 118 119 For the non-threaded version, the support code will have a static 120 array indexed by k5_key_t values, and get/setspecific simply access 121 the array elements. 122 123 The TSD destructor table is global state, protected by a mutex if 124 threads are enabled. 125 126 127 Any actual external symbols will use the krb5int_ prefix. The k5_ 128 names will be simple macros or inline functions to rename the 129 external symbols, or slightly more complex ones to expand the 130 implementation inline (e.g., map to POSIX versions and/or debug 131 code using __FILE__ and the like). 132 133 134 More to be added, perhaps. */ 135 136 #include <assert.h> 137 #ifndef NDEBUG 138 #include <stdio.h> 139 #include <string.h> 140 #endif 141 142 /* The mutex structure we use, k5_mutex_t, is defined to some 143 OS-specific bits. The use of multiple layers of typedefs are an 144 artifact resulting from debugging code we once used, implemented as 145 wrappers around the OS mutex scheme. 146 147 The OS specific bits, in k5_os_mutex, break down into three primary 148 implementations, POSIX threads, Windows threads, and no thread 149 support. However, the POSIX thread version is further subdivided: 150 In one case, we can determine at run time whether the thread 151 library is linked into the application, and use it only if it is 152 present; in the other case, we cannot, and the thread library must 153 be linked in always, but can be used unconditionally. In the 154 former case, the k5_os_mutex structure needs to hold both the POSIX 155 and the non-threaded versions. 156 157 The various k5_os_mutex_* operations are the OS-specific versions, 158 applied to the OS-specific data, and k5_mutex_* uses k5_os_mutex_* 159 to do the OS-specific parts of the work. */ 160 161 /* Define the OS mutex bit. */ 162 163 typedef char k5_os_nothread_mutex; 164 # define K5_OS_NOTHREAD_MUTEX_PARTIAL_INITIALIZER 0 165 /* Empty inline functions avoid the "statement with no effect" 166 warnings, and do better type-checking than functions that don't use 167 their arguments. */ 168 static inline int k5_os_nothread_mutex_finish_init(k5_os_nothread_mutex *m) { 169 return 0; 170 } 171 static inline int k5_os_nothread_mutex_init(k5_os_nothread_mutex *m) { 172 return 0; 173 } 174 static inline int k5_os_nothread_mutex_destroy(k5_os_nothread_mutex *m) { 175 return 0; 176 } 177 static inline int k5_os_nothread_mutex_lock(k5_os_nothread_mutex *m) { 178 return 0; 179 } 180 static inline int k5_os_nothread_mutex_unlock(k5_os_nothread_mutex *m) { 181 return 0; 182 } 183 184 /* Values: 185 2 - function has not been run 186 3 - function has been run 187 4 - function is being run -- deadlock detected */ 188 typedef unsigned char k5_os_nothread_once_t; 189 # define K5_OS_NOTHREAD_ONCE_INIT 2 190 # define k5_os_nothread_once(O,F) \ 191 (*(O) == 3 ? 0 \ 192 : *(O) == 2 ? (*(O) = 4, (F)(), *(O) = 3, 0) \ 193 : (assert(*(O) != 4), assert(*(O) == 2 || *(O) == 3), 0)) 194 195 196 197 #ifndef ENABLE_THREADS 198 199 typedef k5_os_nothread_mutex k5_os_mutex; 200 # define K5_OS_MUTEX_PARTIAL_INITIALIZER \ 201 K5_OS_NOTHREAD_MUTEX_PARTIAL_INITIALIZER 202 # define k5_os_mutex_finish_init k5_os_nothread_mutex_finish_init 203 # define k5_os_mutex_init k5_os_nothread_mutex_init 204 # define k5_os_mutex_destroy k5_os_nothread_mutex_destroy 205 # define k5_os_mutex_lock k5_os_nothread_mutex_lock 206 # define k5_os_mutex_unlock k5_os_nothread_mutex_unlock 207 208 # define k5_once_t k5_os_nothread_once_t 209 # define K5_ONCE_INIT K5_OS_NOTHREAD_ONCE_INIT 210 # define k5_once k5_os_nothread_once 211 212 #elif HAVE_PTHREAD 213 214 # include <pthread.h> 215 216 /* Weak reference support, etc. 217 218 Linux: Stub mutex routines exist, but pthread_once does not. 219 220 Solaris <10: In libc there's a pthread_once that doesn't seem to do 221 anything. Bleah. But pthread_mutexattr_setrobust_np is defined 222 only in libpthread. However, some version of GNU libc (Red Hat's 223 Fedora Core 5, reportedly) seems to have that function, but no 224 declaration, so we'd have to declare it in order to test for its 225 address. We now have tests to see if pthread_once actually works, 226 so stick with that for now. 227 228 Solaris 10: The real thread support now lives in libc, and 229 libpthread is just a filter object. So we might as well use the 230 real functions unconditionally. Since we haven't got a test for 231 this property yet, we use NO_WEAK_PTHREADS defined in aclocal.m4 232 depending on the OS type. 233 234 IRIX 6.5 stub pthread support in libc is really annoying. The 235 pthread_mutex_lock function returns ENOSYS for a program not linked 236 against -lpthread. No link-time failure, no weak symbols, etc. 237 The C library doesn't provide pthread_once; we can use weak 238 reference support for that. 239 240 If weak references are not available, then for now, we assume that 241 the pthread support routines will always be available -- either the 242 real thing, or functional stubs that merely prohibit creating 243 threads. 244 245 If we find a platform with non-functional stubs and no weak 246 references, we may have to resort to some hack like dlsym on the 247 symbol tables of the current process. */ 248 249 #if defined(HAVE_PRAGMA_WEAK_REF) && !defined(NO_WEAK_PTHREADS) 250 # define USE_CONDITIONAL_PTHREADS 251 #endif 252 253 #ifdef USE_CONDITIONAL_PTHREADS 254 255 /* Can't rely on useful stubs -- see above regarding Solaris. */ 256 typedef struct { 257 pthread_once_t o; 258 k5_os_nothread_once_t n; 259 } k5_once_t; 260 # define K5_ONCE_INIT { PTHREAD_ONCE_INIT, K5_OS_NOTHREAD_ONCE_INIT } 261 262 int k5_once(k5_once_t *once, void (*fn)(void)); 263 #else 264 265 /* no pragma weak support */ 266 267 typedef pthread_once_t k5_once_t; 268 # define K5_ONCE_INIT PTHREAD_ONCE_INIT 269 # define k5_once pthread_once 270 271 #endif 272 273 #if defined(__mips) && defined(__sgi) && (defined(_SYSTYPE_SVR4) || defined(__SYSTYPE_SVR4__)) 274 # ifndef HAVE_PRAGMA_WEAK_REF 275 # if defined(__GNUC__) && __GNUC__ < 3 276 # error "Please update to a newer gcc with weak symbol support, or switch to native cc, reconfigure and recompile." 277 # else 278 # error "Weak reference support is required" 279 # endif 280 # endif 281 #endif 282 283 typedef pthread_mutex_t k5_os_mutex; 284 # define K5_OS_MUTEX_PARTIAL_INITIALIZER \ 285 PTHREAD_MUTEX_INITIALIZER 286 287 #ifdef USE_CONDITIONAL_PTHREADS 288 289 # define k5_os_mutex_finish_init(M) (0) 290 int k5_os_mutex_init(k5_os_mutex *m); 291 int k5_os_mutex_destroy(k5_os_mutex *m); 292 int k5_os_mutex_lock(k5_os_mutex *m); 293 int k5_os_mutex_unlock(k5_os_mutex *m); 294 295 #else 296 297 static inline int k5_os_mutex_finish_init(k5_os_mutex *m) { return 0; } 298 # define k5_os_mutex_init(M) pthread_mutex_init((M), 0) 299 # define k5_os_mutex_destroy(M) pthread_mutex_destroy((M)) 300 # define k5_os_mutex_lock(M) pthread_mutex_lock(M) 301 # define k5_os_mutex_unlock(M) pthread_mutex_unlock(M) 302 303 #endif /* is pthreads always available? */ 304 305 #elif defined _WIN32 306 307 # define k5_once_t k5_os_nothread_once_t 308 309 typedef struct { 310 HANDLE h; 311 int is_locked; 312 } k5_os_mutex; 313 314 # define K5_OS_MUTEX_PARTIAL_INITIALIZER { INVALID_HANDLE_VALUE, 0 } 315 316 # define k5_os_mutex_finish_init(M) \ 317 (assert((M)->h == INVALID_HANDLE_VALUE), \ 318 ((M)->h = CreateMutex(NULL, FALSE, NULL)) ? 0 : GetLastError()) 319 # define k5_os_mutex_init(M) \ 320 ((M)->is_locked = 0, \ 321 ((M)->h = CreateMutex(NULL, FALSE, NULL)) ? 0 : GetLastError()) 322 # define k5_os_mutex_destroy(M) \ 323 (CloseHandle((M)->h) ? ((M)->h = 0, 0) : GetLastError()) 324 # define k5_os_mutex_lock k5_win_mutex_lock 325 326 static inline int k5_win_mutex_lock(k5_os_mutex *m) 327 { 328 DWORD res; 329 res = WaitForSingleObject(m->h, INFINITE); 330 if (res == WAIT_FAILED) 331 return GetLastError(); 332 /* Eventually these should be turned into some reasonable error 333 code. */ 334 assert(res != WAIT_TIMEOUT); 335 assert(res != WAIT_ABANDONED); 336 assert(res == WAIT_OBJECT_0); 337 /* Avoid locking twice. */ 338 assert(m->is_locked == 0); 339 m->is_locked = 1; 340 return 0; 341 } 342 343 # define k5_os_mutex_unlock(M) \ 344 (assert((M)->is_locked == 1), \ 345 (M)->is_locked = 0, \ 346 ReleaseMutex((M)->h) ? 0 : GetLastError()) 347 348 #else 349 350 # error "Thread support enabled, but thread system unknown" 351 352 #endif 353 354 typedef k5_os_mutex k5_mutex_t; 355 #define K5_MUTEX_PARTIAL_INITIALIZER K5_OS_MUTEX_PARTIAL_INITIALIZER 356 static inline int k5_mutex_init(k5_mutex_t *m) 357 { 358 return k5_os_mutex_init(m); 359 } 360 static inline int k5_mutex_finish_init(k5_mutex_t *m) 361 { 362 return k5_os_mutex_finish_init(m); 363 } 364 #define k5_mutex_destroy(M) \ 365 (k5_os_mutex_destroy(M)) 366 367 static inline void k5_mutex_lock(k5_mutex_t *m) 368 { 369 int r = k5_os_mutex_lock(m); 370 #ifndef NDEBUG 371 if (r != 0) { 372 fprintf(stderr, "k5_mutex_lock: Received error %d (%s)\n", 373 r, strerror(r)); 374 } 375 #endif 376 assert(r == 0); 377 } 378 379 static inline void k5_mutex_unlock(k5_mutex_t *m) 380 { 381 int r = k5_os_mutex_unlock(m); 382 #ifndef NDEBUG 383 if (r != 0) { 384 fprintf(stderr, "k5_mutex_unlock: Received error %d (%s)\n", 385 r, strerror(r)); 386 } 387 #endif 388 assert(r == 0); 389 } 390 391 #define k5_mutex_assert_locked(M) ((void)(M)) 392 #define k5_mutex_assert_unlocked(M) ((void)(M)) 393 #define k5_assert_locked k5_mutex_assert_locked 394 #define k5_assert_unlocked k5_mutex_assert_unlocked 395 396 /* Thread-specific data; implemented in a support file, because we'll 397 need to keep track of some global data for cleanup purposes. 398 399 Note that the callback function type is such that the C library 400 routine free() is a valid callback. */ 401 typedef enum { 402 K5_KEY_COM_ERR, 403 K5_KEY_GSS_KRB5_SET_CCACHE_OLD_NAME, 404 K5_KEY_GSS_KRB5_CCACHE_NAME, 405 K5_KEY_GSS_KRB5_ERROR_MESSAGE, 406 K5_KEY_GSS_SPNEGO_STATUS, 407 #if defined(__MACH__) && defined(__APPLE__) 408 K5_KEY_IPC_CONNECTION_INFO, 409 #endif 410 K5_KEY_MAX 411 } k5_key_t; 412 /* rename shorthand symbols for export */ 413 #define k5_key_register krb5int_key_register 414 #define k5_getspecific krb5int_getspecific 415 #define k5_setspecific krb5int_setspecific 416 #define k5_key_delete krb5int_key_delete 417 extern int k5_key_register(k5_key_t, void (*)(void *)); 418 extern void *k5_getspecific(k5_key_t); 419 extern int k5_setspecific(k5_key_t, void *); 420 extern int k5_key_delete(k5_key_t); 421 422 extern int KRB5_CALLCONV krb5int_mutex_alloc (k5_mutex_t **); 423 extern void KRB5_CALLCONV krb5int_mutex_free (k5_mutex_t *); 424 extern void KRB5_CALLCONV krb5int_mutex_lock (k5_mutex_t *); 425 extern void KRB5_CALLCONV krb5int_mutex_unlock (k5_mutex_t *); 426 427 /* In time, many of the definitions above should move into the support 428 library, and this file should be greatly simplified. For type 429 definitions, that'll take some work, since other data structures 430 incorporate mutexes directly, and our mutex type is dependent on 431 configuration options and system attributes. For most functions, 432 though, it should be relatively easy. 433 434 For now, plugins should use the exported functions, and not the 435 above macros, and use krb5int_mutex_alloc for allocations. */ 436 #if defined(PLUGIN) || (defined(CONFIG_SMALL) && !defined(THREAD_SUPPORT_IMPL)) 437 #undef k5_mutex_lock 438 #define k5_mutex_lock krb5int_mutex_lock 439 #undef k5_mutex_unlock 440 #define k5_mutex_unlock krb5int_mutex_unlock 441 #endif 442 443 #endif /* multiple inclusion? */ 444