1/* 2 * Copyright (c) 2010 Kungliga Tekniska H��gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2010 Apple Inc. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "baselocl.h" 37#include <syslog.h> 38 39static heim_base_atomic_type tidglobal = HEIM_TID_USER; 40 41struct heim_base { 42 heim_type_t isa; 43 heim_base_atomic_type ref_cnt; 44 HEIM_TAILQ_ENTRY(heim_base) autorel; 45 heim_auto_release_t autorelpool; 46 uintptr_t isaextra[3]; 47}; 48 49/* specialized version of base */ 50struct heim_base_mem { 51 heim_type_t isa; 52 heim_base_atomic_type ref_cnt; 53 HEIM_TAILQ_ENTRY(heim_base) autorel; 54 heim_auto_release_t autorelpool; 55 const char *name; 56 void (*dealloc)(void *); 57 uintptr_t isaextra[1]; 58}; 59 60#define PTR2BASE(ptr) (((struct heim_base *)ptr) - 1) 61#define BASE2PTR(ptr) ((void *)(((struct heim_base *)ptr) + 1)) 62 63#ifdef HEIM_BASE_NEED_ATOMIC_MUTEX 64HEIMDAL_MUTEX _heim_base_mutex = HEIMDAL_MUTEX_INITIALIZER; 65#endif 66 67/* 68 * Auto release structure 69 */ 70 71struct heim_auto_release { 72 HEIM_TAILQ_HEAD(, heim_base) pool; 73 HEIMDAL_MUTEX pool_mutex; 74 struct heim_auto_release *parent; 75}; 76 77 78/** 79 * Retain object 80 * 81 * @param object to be released, NULL is ok 82 * 83 * @return the same object as passed in 84 */ 85 86void * 87heim_retain(void *ptr) 88{ 89 struct heim_base *p = PTR2BASE(ptr); 90 91 if (ptr == NULL || heim_base_is_tagged(ptr)) 92 return ptr; 93 94 if (p->ref_cnt == heim_base_atomic_max) 95 return ptr; 96 97 if ((heim_base_atomic_inc(&p->ref_cnt) - 1) == 0) 98 heim_abort("resurection"); 99 return ptr; 100} 101 102/** 103 * Release object, free is reference count reaches zero 104 * 105 * @param object to be released 106 */ 107 108void 109heim_release(void *ptr) 110{ 111 heim_base_atomic_type old; 112 struct heim_base *p = PTR2BASE(ptr); 113 114 if (ptr == NULL || heim_base_is_tagged(ptr)) 115 return; 116 117 if (p->ref_cnt == heim_base_atomic_max) 118 return; 119 120 old = heim_base_atomic_dec(&p->ref_cnt) + 1; 121 122 if (old > 1) 123 return; 124 125 if (old == 1) { 126 heim_auto_release_t ar = p->autorelpool; 127 /* remove from autorel pool list */ 128 if (ar) { 129 p->autorelpool = NULL; 130 HEIMDAL_MUTEX_lock(&ar->pool_mutex); 131 HEIM_TAILQ_REMOVE(&ar->pool, p, autorel); 132 HEIMDAL_MUTEX_unlock(&ar->pool_mutex); 133 } 134 if (p->isa->dealloc) 135 p->isa->dealloc(ptr); 136 free(p); 137 } else 138 heim_abort("over release"); 139} 140 141static heim_type_t tagged_isa[9] = { 142 &_heim_number_object, 143 &_heim_null_object, 144 &_heim_bool_object, 145 146 NULL, 147 NULL, 148 NULL, 149 150 NULL, 151 NULL, 152 NULL 153}; 154 155heim_type_t 156_heim_get_isa(heim_object_t ptr) 157{ 158 struct heim_base *p; 159 if (heim_base_is_tagged(ptr)) { 160 if (heim_base_is_tagged_object(ptr)) 161 return tagged_isa[heim_base_tagged_object_tid(ptr)]; 162 heim_abort("not a supported tagged type"); 163 } 164 p = PTR2BASE(ptr); 165 return p->isa; 166} 167 168/** 169 * Get type ID of object 170 * 171 * @param object object to get type id of 172 * 173 * @return type id of object 174 */ 175 176heim_tid_t 177heim_get_tid(heim_object_t ptr) 178{ 179 heim_type_t isa = _heim_get_isa(ptr); 180 return isa->tid; 181} 182 183/** 184 * Get hash value of object 185 * 186 * @param object object to get hash value for 187 * 188 * @return a hash value 189 */ 190 191unsigned long 192heim_get_hash(heim_object_t ptr) 193{ 194 heim_type_t isa = _heim_get_isa(ptr); 195 if (isa->hash) 196 return isa->hash(ptr); 197 return (unsigned long)ptr; 198} 199 200/** 201 * Compare two objects, returns 0 if equal, can use used for qsort() 202 * and friends. 203 * 204 * @param a first object to compare 205 * @param b first object to compare 206 * 207 * @return 0 if objects are equal 208 */ 209 210int 211heim_cmp(heim_object_t a, heim_object_t b) 212{ 213 heim_tid_t ta, tb; 214 heim_type_t isa; 215 216 ta = heim_get_tid(a); 217 tb = heim_get_tid(b); 218 219 if (ta != tb) 220 return ta - tb; 221 222 isa = _heim_get_isa(a); 223 224 if (isa->cmp) 225 return isa->cmp(a, b); 226 227 return (uintptr_t)a - (uintptr_t)b; 228} 229 230/* 231 * Private - allocates an memory object 232 */ 233 234static void 235memory_dealloc(void *ptr) 236{ 237 struct heim_base_mem *p = (struct heim_base_mem *)PTR2BASE(ptr); 238 if (p->dealloc) 239 p->dealloc(ptr); 240} 241 242struct heim_type_data memory_object = { 243 HEIM_TID_MEMORY, 244 "memory-object", 245 NULL, 246 memory_dealloc, 247 NULL, 248 NULL, 249 NULL 250}; 251 252void * 253heim_alloc(size_t size, const char *name, heim_type_dealloc dealloc) 254{ 255 /* XXX use posix_memalign */ 256 257 struct heim_base_mem *p = calloc(1, size + sizeof(*p)); 258 if (p == NULL) 259 return NULL; 260 p->isa = &memory_object; 261 p->ref_cnt = 1; 262 p->name = name; 263 p->dealloc = dealloc; 264 return BASE2PTR(p); 265} 266 267heim_type_t 268_heim_create_type(const char *name, 269 heim_type_init init, 270 heim_type_dealloc dealloc, 271 heim_type_copy copy, 272 heim_type_cmp cmp, 273 heim_type_hash hash) 274{ 275 heim_type_t type; 276 277 type = calloc(1, sizeof(*type)); 278 if (type == NULL) 279 return NULL; 280 281 type->tid = heim_base_atomic_inc(&tidglobal); 282 type->name = name; 283 type->init = init; 284 type->dealloc = dealloc; 285 type->copy = copy; 286 type->cmp = cmp; 287 type->hash = hash; 288 289 return type; 290} 291 292heim_object_t 293_heim_alloc_object(heim_type_t type, size_t size) 294{ 295 /* XXX should use posix_memalign */ 296 struct heim_base *p = calloc(1, size + sizeof(*p)); 297 if (p == NULL) 298 return NULL; 299 p->isa = type; 300 p->ref_cnt = 1; 301 302 return BASE2PTR(p); 303} 304 305heim_tid_t 306_heim_type_get_tid(heim_type_t type) 307{ 308 return type->tid; 309} 310 311/** 312 * Call func once and only once 313 * 314 * @param once pointer to a heim_base_once_t 315 * @param ctx context passed to func 316 * @param func function to be called 317 */ 318 319void 320heim_base_once_f(heim_base_once_t *once, void *ctx, void (*func)(void *)) 321{ 322#ifdef HAVE_DISPATCH_DISPATCH_H 323 dispatch_once_f(once, ctx, func); 324#else 325 static HEIMDAL_MUTEX mutex = HEIMDAL_MUTEX_INITIALIZER; 326 HEIMDAL_MUTEX_lock(&mutex); 327 if (*once == 0) { 328 *once = 1; 329 HEIMDAL_MUTEX_unlock(&mutex); 330 func(ctx); 331 HEIMDAL_MUTEX_lock(&mutex); 332 *once = 2; 333 HEIMDAL_MUTEX_unlock(&mutex); 334 } else if (*once == 2) { 335 HEIMDAL_MUTEX_unlock(&mutex); 336 } else { 337 HEIMDAL_MUTEX_unlock(&mutex); 338 while (1) { 339 struct timeval tv = { 0, 1000 }; 340 select(0, NULL, NULL, NULL, &tv); 341 HEIMDAL_MUTEX_lock(&mutex); 342 if (*once == 2) 343 break; 344 HEIMDAL_MUTEX_unlock(&mutex); 345 } 346 HEIMDAL_MUTEX_unlock(&mutex); 347 } 348#endif 349} 350 351/** 352 * Abort and log the failure (using syslog) 353 */ 354 355void 356heim_abort(const char *fmt, ...) 357{ 358 va_list ap; 359 va_start(ap, fmt); 360 heim_abortv(fmt, ap); 361 va_end(ap); 362} 363 364/** 365 * Abort and log the failure (using syslog) 366 */ 367 368void 369heim_abortv(const char *fmt, va_list ap) 370{ 371 static char str[1024]; 372 373 vsnprintf(str, sizeof(str), fmt, ap); 374 syslog(LOG_ERR, "heim_abort: %s", str); 375 abort(); 376} 377 378/* 379 * 380 */ 381 382static int ar_created = 0; 383static HEIMDAL_thread_key ar_key; 384 385struct ar_tls { 386 struct heim_auto_release *head; 387 struct heim_auto_release *current; 388 HEIMDAL_MUTEX tls_mutex; 389}; 390 391static void 392ar_tls_delete(void *ptr) 393{ 394 struct ar_tls *tls = ptr; 395 if (tls->head) 396 heim_release(tls->head); 397 free(tls); 398} 399 400static void 401init_ar_tls(void *ptr) 402{ 403 int ret; 404 HEIMDAL_key_create(&ar_key, ar_tls_delete, ret); 405 if (ret == 0) 406 ar_created = 1; 407} 408 409static struct ar_tls * 410autorel_tls(void) 411{ 412 static heim_base_once_t once = HEIM_BASE_ONCE_INIT; 413 struct ar_tls *arp; 414 int ret; 415 416 heim_base_once_f(&once, NULL, init_ar_tls); 417 if (!ar_created) 418 return NULL; 419 420 arp = HEIMDAL_getspecific(ar_key); 421 if (arp == NULL) { 422 423 arp = calloc(1, sizeof(*arp)); 424 if (arp == NULL) 425 return NULL; 426 HEIMDAL_setspecific(ar_key, arp, ret); 427 if (ret) { 428 free(arp); 429 return NULL; 430 } 431 } 432 return arp; 433 434} 435 436static void 437autorel_dealloc(void *ptr) 438{ 439 heim_auto_release_t ar = ptr; 440 struct ar_tls *tls; 441 442 tls = autorel_tls(); 443 if (tls == NULL) 444 heim_abort("autorelease pool released on thread w/o autorelease inited"); 445 446 heim_auto_release_drain(ar); 447 448 if (!HEIM_TAILQ_EMPTY(&ar->pool)) 449 heim_abort("pool not empty after draining"); 450 451 HEIMDAL_MUTEX_lock(&tls->tls_mutex); 452 if (tls->current != ptr) 453 heim_abort("autorelease not releaseing top pool"); 454 455 if (tls->current != tls->head) 456 tls->current = ar->parent; 457 HEIMDAL_MUTEX_unlock(&tls->tls_mutex); 458} 459 460static int 461autorel_cmp(void *a, void *b) 462{ 463 return (a == b); 464} 465 466static unsigned long 467autorel_hash(void *ptr) 468{ 469 return (unsigned long)ptr; 470} 471 472 473static struct heim_type_data _heim_autorel_object = { 474 HEIM_TID_AUTORELEASE, 475 "autorelease-pool", 476 NULL, 477 autorel_dealloc, 478 NULL, 479 autorel_cmp, 480 autorel_hash 481}; 482 483/** 484 * 485 */ 486 487heim_auto_release_t 488heim_auto_release_create(void) 489{ 490 struct ar_tls *tls = autorel_tls(); 491 heim_auto_release_t ar; 492 493 if (tls == NULL) 494 heim_abort("Failed to create/get autorelease head"); 495 496 ar = _heim_alloc_object(&_heim_autorel_object, sizeof(struct heim_auto_release)); 497 if (ar) { 498 HEIMDAL_MUTEX_lock(&tls->tls_mutex); 499 if (tls->head == NULL) 500 tls->head = ar; 501 ar->parent = tls->current; 502 tls->current = ar; 503 HEIMDAL_MUTEX_unlock(&tls->tls_mutex); 504 } 505 506 return ar; 507} 508 509/** 510 * Mark the current object as a 511 */ 512 513void 514heim_auto_release(heim_object_t ptr) 515{ 516 struct heim_base *p = PTR2BASE(ptr); 517 struct ar_tls *tls = autorel_tls(); 518 heim_auto_release_t ar; 519 520 if (ptr == NULL || heim_base_is_tagged(ptr)) 521 return; 522 523 /* drop from old pool */ 524 if ((ar = p->autorelpool) != NULL) { 525 HEIMDAL_MUTEX_lock(&ar->pool_mutex); 526 HEIM_TAILQ_REMOVE(&ar->pool, p, autorel); 527 p->autorelpool = NULL; 528 HEIMDAL_MUTEX_unlock(&ar->pool_mutex); 529 } 530 531 if (tls == NULL || (ar = tls->current) == NULL) 532 heim_abort("no auto relase pool in place, would leak"); 533 534 HEIMDAL_MUTEX_lock(&ar->pool_mutex); 535 HEIM_TAILQ_INSERT_HEAD(&ar->pool, p, autorel); 536 p->autorelpool = ar; 537 HEIMDAL_MUTEX_unlock(&ar->pool_mutex); 538} 539 540/** 541 * 542 */ 543 544void 545heim_auto_release_drain(heim_auto_release_t autorel) 546{ 547 heim_object_t obj; 548 549 /* release all elements on the tail queue */ 550 551 HEIMDAL_MUTEX_lock(&autorel->pool_mutex); 552 while(!HEIM_TAILQ_EMPTY(&autorel->pool)) { 553 obj = HEIM_TAILQ_FIRST(&autorel->pool); 554 HEIMDAL_MUTEX_unlock(&autorel->pool_mutex); 555 heim_release(BASE2PTR(obj)); 556 HEIMDAL_MUTEX_lock(&autorel->pool_mutex); 557 } 558 HEIMDAL_MUTEX_unlock(&autorel->pool_mutex); 559} 560