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