1226031Sstas/*
2226031Sstas * Copyright (c) 2010 Kungliga Tekniska H��gskolan
3226031Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4226031Sstas * All rights reserved.
5226031Sstas *
6226031Sstas * Portions Copyright (c) 2010 Apple Inc. All rights reserved.
7226031Sstas *
8226031Sstas * Redistribution and use in source and binary forms, with or without
9226031Sstas * modification, are permitted provided that the following conditions
10226031Sstas * are met:
11226031Sstas *
12226031Sstas * 1. Redistributions of source code must retain the above copyright
13226031Sstas *    notice, this list of conditions and the following disclaimer.
14226031Sstas *
15226031Sstas * 2. Redistributions in binary form must reproduce the above copyright
16226031Sstas *    notice, this list of conditions and the following disclaimer in the
17226031Sstas *    documentation and/or other materials provided with the distribution.
18226031Sstas *
19226031Sstas * 3. Neither the name of the Institute nor the names of its contributors
20226031Sstas *    may be used to endorse or promote products derived from this software
21226031Sstas *    without specific prior written permission.
22226031Sstas *
23226031Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24226031Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25226031Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26226031Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27226031Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28226031Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29226031Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30226031Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31226031Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32226031Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33226031Sstas * SUCH DAMAGE.
34226031Sstas */
35226031Sstas
36226031Sstas#include "baselocl.h"
37226031Sstas#include <syslog.h>
38226031Sstas
39226031Sstasstatic heim_base_atomic_type tidglobal = HEIM_TID_USER;
40226031Sstas
41226031Sstasstruct heim_base {
42226031Sstas    heim_type_t isa;
43226031Sstas    heim_base_atomic_type ref_cnt;
44226031Sstas    HEIM_TAILQ_ENTRY(heim_base) autorel;
45226031Sstas    heim_auto_release_t autorelpool;
46226031Sstas    uintptr_t isaextra[3];
47226031Sstas};
48226031Sstas
49226031Sstas/* specialized version of base */
50226031Sstasstruct heim_base_mem {
51226031Sstas    heim_type_t isa;
52226031Sstas    heim_base_atomic_type ref_cnt;
53226031Sstas    HEIM_TAILQ_ENTRY(heim_base) autorel;
54226031Sstas    heim_auto_release_t autorelpool;
55226031Sstas    const char *name;
56226031Sstas    void (*dealloc)(void *);
57226031Sstas    uintptr_t isaextra[1];
58226031Sstas};
59226031Sstas
60226031Sstas#define PTR2BASE(ptr) (((struct heim_base *)ptr) - 1)
61226031Sstas#define BASE2PTR(ptr) ((void *)(((struct heim_base *)ptr) + 1))
62226031Sstas
63226031Sstas#ifdef HEIM_BASE_NEED_ATOMIC_MUTEX
64226031SstasHEIMDAL_MUTEX _heim_base_mutex = HEIMDAL_MUTEX_INITIALIZER;
65226031Sstas#endif
66226031Sstas
67226031Sstas/*
68226031Sstas * Auto release structure
69226031Sstas */
70226031Sstas
71226031Sstasstruct heim_auto_release {
72226031Sstas    HEIM_TAILQ_HEAD(, heim_base) pool;
73226031Sstas    HEIMDAL_MUTEX pool_mutex;
74226031Sstas    struct heim_auto_release *parent;
75226031Sstas};
76226031Sstas
77226031Sstas
78226031Sstas/**
79226031Sstas * Retain object
80226031Sstas *
81226031Sstas * @param object to be released, NULL is ok
82226031Sstas *
83226031Sstas * @return the same object as passed in
84226031Sstas */
85226031Sstas
86226031Sstasvoid *
87226031Sstasheim_retain(void *ptr)
88226031Sstas{
89226031Sstas    struct heim_base *p = PTR2BASE(ptr);
90226031Sstas
91226031Sstas    if (ptr == NULL || heim_base_is_tagged(ptr))
92226031Sstas	return ptr;
93226031Sstas
94226031Sstas    if (p->ref_cnt == heim_base_atomic_max)
95226031Sstas	return ptr;
96226031Sstas
97226031Sstas    if ((heim_base_atomic_inc(&p->ref_cnt) - 1) == 0)
98226031Sstas	heim_abort("resurection");
99226031Sstas    return ptr;
100226031Sstas}
101226031Sstas
102226031Sstas/**
103226031Sstas * Release object, free is reference count reaches zero
104226031Sstas *
105226031Sstas * @param object to be released
106226031Sstas */
107226031Sstas
108226031Sstasvoid
109226031Sstasheim_release(void *ptr)
110226031Sstas{
111226031Sstas    heim_base_atomic_type old;
112226031Sstas    struct heim_base *p = PTR2BASE(ptr);
113226031Sstas
114226031Sstas    if (ptr == NULL || heim_base_is_tagged(ptr))
115226031Sstas	return;
116226031Sstas
117226031Sstas    if (p->ref_cnt == heim_base_atomic_max)
118226031Sstas	return;
119226031Sstas
120226031Sstas    old = heim_base_atomic_dec(&p->ref_cnt) + 1;
121226031Sstas
122226031Sstas    if (old > 1)
123226031Sstas	return;
124226031Sstas
125226031Sstas    if (old == 1) {
126226031Sstas	heim_auto_release_t ar = p->autorelpool;
127226031Sstas	/* remove from autorel pool list */
128226031Sstas	if (ar) {
129226031Sstas	    p->autorelpool = NULL;
130226031Sstas	    HEIMDAL_MUTEX_lock(&ar->pool_mutex);
131226031Sstas	    HEIM_TAILQ_REMOVE(&ar->pool, p, autorel);
132226031Sstas	    HEIMDAL_MUTEX_unlock(&ar->pool_mutex);
133226031Sstas	}
134226031Sstas	if (p->isa->dealloc)
135226031Sstas	    p->isa->dealloc(ptr);
136226031Sstas	free(p);
137226031Sstas    } else
138226031Sstas	heim_abort("over release");
139226031Sstas}
140226031Sstas
141226031Sstasstatic heim_type_t tagged_isa[9] = {
142226031Sstas    &_heim_number_object,
143226031Sstas    &_heim_null_object,
144226031Sstas    &_heim_bool_object,
145226031Sstas
146226031Sstas    NULL,
147226031Sstas    NULL,
148226031Sstas    NULL,
149226031Sstas
150226031Sstas    NULL,
151226031Sstas    NULL,
152226031Sstas    NULL
153226031Sstas};
154226031Sstas
155226031Sstasheim_type_t
156226031Sstas_heim_get_isa(heim_object_t ptr)
157226031Sstas{
158226031Sstas    struct heim_base *p;
159226031Sstas    if (heim_base_is_tagged(ptr)) {
160226031Sstas	if (heim_base_is_tagged_object(ptr))
161226031Sstas	    return tagged_isa[heim_base_tagged_object_tid(ptr)];
162226031Sstas	heim_abort("not a supported tagged type");
163226031Sstas    }
164226031Sstas    p = PTR2BASE(ptr);
165226031Sstas    return p->isa;
166226031Sstas}
167226031Sstas
168226031Sstas/**
169226031Sstas * Get type ID of object
170226031Sstas *
171226031Sstas * @param object object to get type id of
172226031Sstas *
173226031Sstas * @return type id of object
174226031Sstas */
175226031Sstas
176226031Sstasheim_tid_t
177226031Sstasheim_get_tid(heim_object_t ptr)
178226031Sstas{
179226031Sstas    heim_type_t isa = _heim_get_isa(ptr);
180226031Sstas    return isa->tid;
181226031Sstas}
182226031Sstas
183226031Sstas/**
184226031Sstas * Get hash value of object
185226031Sstas *
186226031Sstas * @param object object to get hash value for
187226031Sstas *
188226031Sstas * @return a hash value
189226031Sstas */
190226031Sstas
191226031Sstasunsigned long
192226031Sstasheim_get_hash(heim_object_t ptr)
193226031Sstas{
194226031Sstas    heim_type_t isa = _heim_get_isa(ptr);
195226031Sstas    if (isa->hash)
196226031Sstas	return isa->hash(ptr);
197226031Sstas    return (unsigned long)ptr;
198226031Sstas}
199226031Sstas
200226031Sstas/**
201226031Sstas * Compare two objects, returns 0 if equal, can use used for qsort()
202226031Sstas * and friends.
203226031Sstas *
204226031Sstas * @param a first object to compare
205226031Sstas * @param b first object to compare
206226031Sstas *
207226031Sstas * @return 0 if objects are equal
208226031Sstas */
209226031Sstas
210226031Sstasint
211226031Sstasheim_cmp(heim_object_t a, heim_object_t b)
212226031Sstas{
213226031Sstas    heim_tid_t ta, tb;
214226031Sstas    heim_type_t isa;
215226031Sstas
216226031Sstas    ta = heim_get_tid(a);
217226031Sstas    tb = heim_get_tid(b);
218226031Sstas
219226031Sstas    if (ta != tb)
220226031Sstas	return ta - tb;
221226031Sstas
222226031Sstas    isa = _heim_get_isa(a);
223226031Sstas
224226031Sstas    if (isa->cmp)
225226031Sstas	return isa->cmp(a, b);
226226031Sstas
227226031Sstas    return (uintptr_t)a - (uintptr_t)b;
228226031Sstas}
229226031Sstas
230226031Sstas/*
231226031Sstas * Private - allocates an memory object
232226031Sstas */
233226031Sstas
234226031Sstasstatic void
235226031Sstasmemory_dealloc(void *ptr)
236226031Sstas{
237226031Sstas    struct heim_base_mem *p = (struct heim_base_mem *)PTR2BASE(ptr);
238226031Sstas    if (p->dealloc)
239226031Sstas	p->dealloc(ptr);
240226031Sstas}
241226031Sstas
242226031Sstasstruct heim_type_data memory_object = {
243226031Sstas    HEIM_TID_MEMORY,
244226031Sstas    "memory-object",
245226031Sstas    NULL,
246226031Sstas    memory_dealloc,
247226031Sstas    NULL,
248226031Sstas    NULL,
249226031Sstas    NULL
250226031Sstas};
251226031Sstas
252226031Sstasvoid *
253226031Sstasheim_alloc(size_t size, const char *name, heim_type_dealloc dealloc)
254226031Sstas{
255226031Sstas    /* XXX use posix_memalign */
256226031Sstas
257226031Sstas    struct heim_base_mem *p = calloc(1, size + sizeof(*p));
258226031Sstas    if (p == NULL)
259226031Sstas	return NULL;
260226031Sstas    p->isa = &memory_object;
261226031Sstas    p->ref_cnt = 1;
262226031Sstas    p->name = name;
263226031Sstas    p->dealloc = dealloc;
264226031Sstas    return BASE2PTR(p);
265226031Sstas}
266226031Sstas
267226031Sstasheim_type_t
268226031Sstas_heim_create_type(const char *name,
269226031Sstas		  heim_type_init init,
270226031Sstas		  heim_type_dealloc dealloc,
271226031Sstas		  heim_type_copy copy,
272226031Sstas		  heim_type_cmp cmp,
273226031Sstas		  heim_type_hash hash)
274226031Sstas{
275226031Sstas    heim_type_t type;
276226031Sstas
277226031Sstas    type = calloc(1, sizeof(*type));
278226031Sstas    if (type == NULL)
279226031Sstas	return NULL;
280226031Sstas
281226031Sstas    type->tid = heim_base_atomic_inc(&tidglobal);
282226031Sstas    type->name = name;
283226031Sstas    type->init = init;
284226031Sstas    type->dealloc = dealloc;
285226031Sstas    type->copy = copy;
286226031Sstas    type->cmp = cmp;
287226031Sstas    type->hash = hash;
288226031Sstas
289226031Sstas    return type;
290226031Sstas}
291226031Sstas
292226031Sstasheim_object_t
293226031Sstas_heim_alloc_object(heim_type_t type, size_t size)
294226031Sstas{
295226031Sstas    /* XXX should use posix_memalign */
296226031Sstas    struct heim_base *p = calloc(1, size + sizeof(*p));
297226031Sstas    if (p == NULL)
298226031Sstas	return NULL;
299226031Sstas    p->isa = type;
300226031Sstas    p->ref_cnt = 1;
301226031Sstas
302226031Sstas    return BASE2PTR(p);
303226031Sstas}
304226031Sstas
305226031Sstasheim_tid_t
306226031Sstas_heim_type_get_tid(heim_type_t type)
307226031Sstas{
308226031Sstas    return type->tid;
309226031Sstas}
310226031Sstas
311226031Sstas/**
312226031Sstas * Call func once and only once
313226031Sstas *
314226031Sstas * @param once pointer to a heim_base_once_t
315226031Sstas * @param ctx context passed to func
316226031Sstas * @param func function to be called
317226031Sstas */
318226031Sstas
319226031Sstasvoid
320226031Sstasheim_base_once_f(heim_base_once_t *once, void *ctx, void (*func)(void *))
321226031Sstas{
322226031Sstas#ifdef HAVE_DISPATCH_DISPATCH_H
323226031Sstas    dispatch_once_f(once, ctx, func);
324226031Sstas#else
325226031Sstas    static HEIMDAL_MUTEX mutex = HEIMDAL_MUTEX_INITIALIZER;
326226031Sstas    HEIMDAL_MUTEX_lock(&mutex);
327226031Sstas    if (*once == 0) {
328226031Sstas	*once = 1;
329226031Sstas	HEIMDAL_MUTEX_unlock(&mutex);
330226031Sstas	func(ctx);
331226031Sstas	HEIMDAL_MUTEX_lock(&mutex);
332226031Sstas	*once = 2;
333226031Sstas	HEIMDAL_MUTEX_unlock(&mutex);
334226031Sstas    } else if (*once == 2) {
335226031Sstas	HEIMDAL_MUTEX_unlock(&mutex);
336226031Sstas    } else {
337226031Sstas	HEIMDAL_MUTEX_unlock(&mutex);
338226031Sstas	while (1) {
339226031Sstas	    struct timeval tv = { 0, 1000 };
340226031Sstas	    select(0, NULL, NULL, NULL, &tv);
341226031Sstas	    HEIMDAL_MUTEX_lock(&mutex);
342226031Sstas	    if (*once == 2)
343226031Sstas		break;
344226031Sstas	    HEIMDAL_MUTEX_unlock(&mutex);
345226031Sstas	}
346226031Sstas	HEIMDAL_MUTEX_unlock(&mutex);
347226031Sstas    }
348226031Sstas#endif
349226031Sstas}
350226031Sstas
351226031Sstas/**
352226031Sstas * Abort and log the failure (using syslog)
353226031Sstas */
354226031Sstas
355226031Sstasvoid
356226031Sstasheim_abort(const char *fmt, ...)
357226031Sstas{
358226031Sstas    va_list ap;
359226031Sstas    va_start(ap, fmt);
360226031Sstas    heim_abortv(fmt, ap);
361226031Sstas    va_end(ap);
362226031Sstas}
363226031Sstas
364226031Sstas/**
365226031Sstas * Abort and log the failure (using syslog)
366226031Sstas */
367226031Sstas
368226031Sstasvoid
369226031Sstasheim_abortv(const char *fmt, va_list ap)
370226031Sstas{
371226031Sstas    static char str[1024];
372226031Sstas
373226031Sstas    vsnprintf(str, sizeof(str), fmt, ap);
374226031Sstas    syslog(LOG_ERR, "heim_abort: %s", str);
375226031Sstas    abort();
376226031Sstas}
377226031Sstas
378226031Sstas/*
379226031Sstas *
380226031Sstas */
381226031Sstas
382226031Sstasstatic int ar_created = 0;
383226031Sstasstatic HEIMDAL_thread_key ar_key;
384226031Sstas
385226031Sstasstruct ar_tls {
386226031Sstas    struct heim_auto_release *head;
387226031Sstas    struct heim_auto_release *current;
388226031Sstas    HEIMDAL_MUTEX tls_mutex;
389226031Sstas};
390226031Sstas
391226031Sstasstatic void
392226031Sstasar_tls_delete(void *ptr)
393226031Sstas{
394226031Sstas    struct ar_tls *tls = ptr;
395226031Sstas    if (tls->head)
396226031Sstas	heim_release(tls->head);
397226031Sstas    free(tls);
398226031Sstas}
399226031Sstas
400226031Sstasstatic void
401226031Sstasinit_ar_tls(void *ptr)
402226031Sstas{
403226031Sstas    int ret;
404226031Sstas    HEIMDAL_key_create(&ar_key, ar_tls_delete, ret);
405226031Sstas    if (ret == 0)
406226031Sstas	ar_created = 1;
407226031Sstas}
408226031Sstas
409226031Sstasstatic struct ar_tls *
410226031Sstasautorel_tls(void)
411226031Sstas{
412226031Sstas    static heim_base_once_t once = HEIM_BASE_ONCE_INIT;
413226031Sstas    struct ar_tls *arp;
414226031Sstas    int ret;
415226031Sstas
416226031Sstas    heim_base_once_f(&once, NULL, init_ar_tls);
417226031Sstas    if (!ar_created)
418226031Sstas	return NULL;
419226031Sstas
420226031Sstas    arp = HEIMDAL_getspecific(ar_key);
421226031Sstas    if (arp == NULL) {
422226031Sstas
423226031Sstas	arp = calloc(1, sizeof(*arp));
424226031Sstas	if (arp == NULL)
425226031Sstas	    return NULL;
426226031Sstas	HEIMDAL_setspecific(ar_key, arp, ret);
427226031Sstas	if (ret) {
428226031Sstas	    free(arp);
429226031Sstas	    return NULL;
430226031Sstas	}
431226031Sstas    }
432226031Sstas    return arp;
433226031Sstas
434226031Sstas}
435226031Sstas
436226031Sstasstatic void
437226031Sstasautorel_dealloc(void *ptr)
438226031Sstas{
439226031Sstas    heim_auto_release_t ar = ptr;
440226031Sstas    struct ar_tls *tls;
441226031Sstas
442226031Sstas    tls = autorel_tls();
443226031Sstas    if (tls == NULL)
444226031Sstas	heim_abort("autorelease pool released on thread w/o autorelease inited");
445226031Sstas
446226031Sstas    heim_auto_release_drain(ar);
447226031Sstas
448226031Sstas    if (!HEIM_TAILQ_EMPTY(&ar->pool))
449226031Sstas	heim_abort("pool not empty after draining");
450226031Sstas
451226031Sstas    HEIMDAL_MUTEX_lock(&tls->tls_mutex);
452226031Sstas    if (tls->current != ptr)
453226031Sstas	heim_abort("autorelease not releaseing top pool");
454226031Sstas
455226031Sstas    if (tls->current != tls->head)
456226031Sstas	tls->current = ar->parent;
457226031Sstas    HEIMDAL_MUTEX_unlock(&tls->tls_mutex);
458226031Sstas}
459226031Sstas
460226031Sstasstatic int
461226031Sstasautorel_cmp(void *a, void *b)
462226031Sstas{
463226031Sstas    return (a == b);
464226031Sstas}
465226031Sstas
466226031Sstasstatic unsigned long
467226031Sstasautorel_hash(void *ptr)
468226031Sstas{
469226031Sstas    return (unsigned long)ptr;
470226031Sstas}
471226031Sstas
472226031Sstas
473226031Sstasstatic struct heim_type_data _heim_autorel_object = {
474226031Sstas    HEIM_TID_AUTORELEASE,
475226031Sstas    "autorelease-pool",
476226031Sstas    NULL,
477226031Sstas    autorel_dealloc,
478226031Sstas    NULL,
479226031Sstas    autorel_cmp,
480226031Sstas    autorel_hash
481226031Sstas};
482226031Sstas
483226031Sstas/**
484226031Sstas *
485226031Sstas */
486226031Sstas
487226031Sstasheim_auto_release_t
488226031Sstasheim_auto_release_create(void)
489226031Sstas{
490226031Sstas    struct ar_tls *tls = autorel_tls();
491226031Sstas    heim_auto_release_t ar;
492226031Sstas
493226031Sstas    if (tls == NULL)
494226031Sstas	heim_abort("Failed to create/get autorelease head");
495226031Sstas
496226031Sstas    ar = _heim_alloc_object(&_heim_autorel_object, sizeof(struct heim_auto_release));
497226031Sstas    if (ar) {
498226031Sstas	HEIMDAL_MUTEX_lock(&tls->tls_mutex);
499226031Sstas	if (tls->head == NULL)
500226031Sstas	    tls->head = ar;
501226031Sstas	ar->parent = tls->current;
502226031Sstas	tls->current = ar;
503226031Sstas	HEIMDAL_MUTEX_unlock(&tls->tls_mutex);
504226031Sstas    }
505226031Sstas
506226031Sstas    return ar;
507226031Sstas}
508226031Sstas
509226031Sstas/**
510226031Sstas * Mark the current object as a
511226031Sstas */
512226031Sstas
513226031Sstasvoid
514226031Sstasheim_auto_release(heim_object_t ptr)
515226031Sstas{
516226031Sstas    struct heim_base *p = PTR2BASE(ptr);
517226031Sstas    struct ar_tls *tls = autorel_tls();
518226031Sstas    heim_auto_release_t ar;
519226031Sstas
520226031Sstas    if (ptr == NULL || heim_base_is_tagged(ptr))
521226031Sstas	return;
522226031Sstas
523226031Sstas    /* drop from old pool */
524226031Sstas    if ((ar = p->autorelpool) != NULL) {
525226031Sstas	HEIMDAL_MUTEX_lock(&ar->pool_mutex);
526226031Sstas	HEIM_TAILQ_REMOVE(&ar->pool, p, autorel);
527226031Sstas	p->autorelpool = NULL;
528226031Sstas	HEIMDAL_MUTEX_unlock(&ar->pool_mutex);
529226031Sstas    }
530226031Sstas
531226031Sstas    if (tls == NULL || (ar = tls->current) == NULL)
532226031Sstas	heim_abort("no auto relase pool in place, would leak");
533226031Sstas
534226031Sstas    HEIMDAL_MUTEX_lock(&ar->pool_mutex);
535226031Sstas    HEIM_TAILQ_INSERT_HEAD(&ar->pool, p, autorel);
536226031Sstas    p->autorelpool = ar;
537226031Sstas    HEIMDAL_MUTEX_unlock(&ar->pool_mutex);
538226031Sstas}
539226031Sstas
540226031Sstas/**
541226031Sstas *
542226031Sstas */
543226031Sstas
544226031Sstasvoid
545226031Sstasheim_auto_release_drain(heim_auto_release_t autorel)
546226031Sstas{
547226031Sstas    heim_object_t obj;
548226031Sstas
549226031Sstas    /* release all elements on the tail queue */
550226031Sstas
551226031Sstas    HEIMDAL_MUTEX_lock(&autorel->pool_mutex);
552226031Sstas    while(!HEIM_TAILQ_EMPTY(&autorel->pool)) {
553226031Sstas	obj = HEIM_TAILQ_FIRST(&autorel->pool);
554226031Sstas	HEIMDAL_MUTEX_unlock(&autorel->pool_mutex);
555226031Sstas	heim_release(BASE2PTR(obj));
556226031Sstas	HEIMDAL_MUTEX_lock(&autorel->pool_mutex);
557226031Sstas    }
558226031Sstas    HEIMDAL_MUTEX_unlock(&autorel->pool_mutex);
559226031Sstas}
560