/*
 * Copyright (c) 2007 Apple Inc.  All Rights Reserved.
 * 
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/***********************************************************************
* objc-os.m
* OS portability layer.
**********************************************************************/

#include "objc-os.h"
#include "objc-private.h"
#include "objc-loadmethod.h"

#if TARGET_OS_WIN32

#include "objc-runtime-old.h"
#include "objcrt.h"

malloc_zone_t *_objc_internal_zone(void) 
{ 
    return NULL; 
}

int monitor_init(monitor_t *c) 
{
    // fixme error checking
    HANDLE mutex = CreateMutex(NULL, TRUE, NULL);
    while (!c->mutex) {
        // fixme memory barrier here?
        if (0 == InterlockedCompareExchangePointer(&c->mutex, mutex, 0)) {
            // we win - finish construction
            c->waiters = CreateSemaphore(NULL, 0, 0x7fffffff, NULL);
            c->waitersDone = CreateEvent(NULL, FALSE, FALSE, NULL);
            InitializeCriticalSection(&c->waitCountLock);
            c->waitCount = 0;
            c->didBroadcast = 0;
            ReleaseMutex(c->mutex);    
            return 0;
        }
    }

    // someone else allocated the mutex and constructed the monitor
    ReleaseMutex(mutex);
    CloseHandle(mutex);
    return 0;
}

void mutex_init(mutex_t *m)
{
    while (!m->lock) {
        CRITICAL_SECTION *newlock = malloc(sizeof(CRITICAL_SECTION));
        InitializeCriticalSection(newlock);
        // fixme memory barrier here?
        if (0 == InterlockedCompareExchangePointer(&m->lock, newlock, 0)) {
            return;
        }
        // someone else installed their lock first
        DeleteCriticalSection(newlock);
        free(newlock);
    }
}


void recursive_mutex_init(recursive_mutex_t *m)
{
    // fixme error checking
    HANDLE newmutex = CreateMutex(NULL, FALSE, NULL);
    while (!m->mutex) {
        // fixme memory barrier here?
        if (0 == InterlockedCompareExchangePointer(&m->mutex, newmutex, 0)) {
            // we win
            return;
        }
    }
    
    // someone else installed their lock first
    CloseHandle(newmutex);
}


WINBOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        environ_init();
        tls_init();
        lock_init();
        sel_init(NO);
        exception_init();        
        break;

    case DLL_THREAD_ATTACH:
        break;

    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

OBJC_EXPORT void *_objc_init_image(HMODULE image, const objc_sections *sects)
{
    header_info *hi = _malloc_internal(sizeof(header_info));
    size_t count, i;

    hi->mhdr = (const headerType *)image;
    hi->info = sects->iiStart;
    hi->allClassesRealized = NO;
    hi->os.modules = sects->modStart ? (Module *)((void **)sects->modStart+1) : 0;
    hi->os.moduleCount = (Module *)sects->modEnd - hi->os.modules;
    hi->os.protocols = sects->protoStart ? (struct old_protocol **)((void **)sects->protoStart+1) : 0;
    hi->os.protocolCount = (struct old_protocol **)sects->protoEnd - hi->os.protocols;
    hi->os.imageinfo = NULL;
    hi->os.imageinfoBytes = 0;
    // hi->os.imageinfo = sects->iiStart ? (uint8_t *)((void **)sects->iiStart+1) : 0;;
//     hi->os.imageinfoBytes = (uint8_t *)sects->iiEnd - hi->os.imageinfo;
    hi->os.selrefs = sects->selrefsStart ? (SEL *)((void **)sects->selrefsStart+1) : 0;
    hi->os.selrefCount = (SEL *)sects->selrefsEnd - hi->os.selrefs;
    hi->os.clsrefs = sects->clsrefsStart ? (Class *)((void **)sects->clsrefsStart+1) : 0;
    hi->os.clsrefCount = (Class *)sects->clsrefsEnd - hi->os.clsrefs;

    count = 0;
    for (i = 0; i < hi->os.moduleCount; i++) {
        if (hi->os.modules[i]) count++;
    }
    hi->mod_count = 0;
    hi->mod_ptr = 0;
    if (count > 0) {
        hi->mod_ptr = malloc(count * sizeof(struct objc_module));
        for (i = 0; i < hi->os.moduleCount; i++) {
            if (hi->os.modules[i]) memcpy(&hi->mod_ptr[hi->mod_count++], hi->os.modules[i], sizeof(struct objc_module));
        }
    }
    
    hi->os.moduleName = malloc(MAX_PATH * sizeof(TCHAR));
    GetModuleFileName((HMODULE)(hi->mhdr), hi->os.moduleName, MAX_PATH * sizeof(TCHAR));

    _objc_appendHeader(hi);

    if (PrintImages) {
        _objc_inform("IMAGES: loading image for %s%s%s\n", 
            _nameForHeader(hi->mhdr), 
            headerIsBundle(hi) ? " (bundle)" : "", 
            _objcHeaderIsReplacement(hi) ? " (replacement)":"");
    }

    _read_images(&hi, 1);

    return hi;
}

OBJC_EXPORT void _objc_load_image(HMODULE image, header_info *hinfo)
{
    prepare_load_methods(hinfo);
    call_load_methods();
}

OBJC_EXPORT void _objc_unload_image(HMODULE image, header_info *hinfo)
{
    _objc_fatal("image unload not supported");
}


PRIVATE_EXTERN bool crashlog_header_name(header_info *hi)
{
    return true;
}


// TARGET_OS_WIN32
#elif TARGET_OS_MAC

#if !__OBJC2__
#include "objc-file-old.h"
#endif

PRIVATE_EXTERN void mutex_init(mutex_t *m)
{
    pthread_mutex_init(m, NULL);
}


PRIVATE_EXTERN void recursive_mutex_init(recursive_mutex_t *m)
{
    // fixme error checking
    pthread_mutex_t *newmutex;

    // Build recursive mutex attributes, if needed
    static pthread_mutexattr_t *attr;
    if (!attr) {
        pthread_mutexattr_t *newattr = 
            _malloc_internal(sizeof(pthread_mutexattr_t));
        pthread_mutexattr_init(newattr);
        pthread_mutexattr_settype(newattr, PTHREAD_MUTEX_RECURSIVE);
        while (!attr) {
            if (OSAtomicCompareAndSwapPtrBarrier(0, newattr, (void**)&attr)) {
                // we win
                goto attr_done;
            }
        }
        // someone else built the attr first
        _free_internal(newattr);
    }
 attr_done:

    // Build the mutex itself
    newmutex = _malloc_internal(sizeof(pthread_mutex_t));
    pthread_mutex_init(newmutex, attr);
    while (!m->mutex) {
        if (OSAtomicCompareAndSwapPtrBarrier(0, newmutex, (void**)&m->mutex)) {
            // we win
            return;
        }
    }
    
    // someone else installed their mutex first
    pthread_mutex_destroy(newmutex);
}


/***********************************************************************
* bad_magic.
* Return YES if the header has invalid Mach-o magic.
**********************************************************************/
PRIVATE_EXTERN BOOL bad_magic(const headerType *mhdr)
{
    return (mhdr->magic != MH_MAGIC  &&  mhdr->magic != MH_MAGIC_64  &&  
            mhdr->magic != MH_CIGAM  &&  mhdr->magic != MH_CIGAM_64);
}


static header_info * _objc_addHeader(const headerType *mhdr)
{
    size_t info_size = 0;
    unsigned long seg_size;
    const uint8_t *objc_segment;
    const objc_image_info *image_info;
    header_info *result;

    if (bad_magic(mhdr)) return NULL;

    // Weed out duplicates
    for (result = FirstHeader; result; result = result->next) {
        if (mhdr == result->mhdr) return NULL;
    }

    // Locate the __OBJC segment
    image_info = _getObjcImageInfo(mhdr, &info_size);
    objc_segment = getsegmentdata(mhdr, SEG_OBJC, &seg_size);
    if (!objc_segment  &&  !image_info) return NULL;

    // Allocate a header_info entry.
    result = _calloc_internal(sizeof(header_info), 1);

    // Set up the new header_info entry.
    result->mhdr = mhdr;
#if !__OBJC2__
    // mhdr must already be set
    result->mod_count = 0;
    result->mod_ptr = _getObjcModules(result, &result->mod_count);
#endif
    result->info = image_info;
    dladdr(result->mhdr, &result->os.dl_info);
    result->allClassesRealized = NO;

    // dylibs are not allowed to unload
    // ...except those with image_info and nothing else (5359412)
    if (result->mhdr->filetype == MH_DYLIB  &&  _hasObjcContents(result)) {
        dlopen(result->os.dl_info.dli_fname, RTLD_NOLOAD);
    }

    // Make sure every copy of objc_image_info in this image is the same.
    // This means same version and same bitwise contents.
    if (result->info) {
        const objc_image_info *start = result->info;
        const objc_image_info *end = 
            (objc_image_info *)(info_size + (uint8_t *)start);
        const objc_image_info *info = start;
        while (info < end) {
            // version is byte size, except for version 0
            size_t struct_size = info->version;
            if (struct_size == 0) struct_size = 2 * sizeof(uint32_t);
            if (info->version != start->version  ||  
                0 != memcmp(info, start, struct_size))
            {
                _objc_inform("'%s' has inconsistently-compiled Objective-C "
                            "code. Please recompile all code in it.", 
                            _nameForHeader(mhdr));
            }
            info = (objc_image_info *)(struct_size + (uint8_t *)info);
        }
    }

    _objc_appendHeader(result);
    
    return result;
}


#if !SUPPORT_GC

PRIVATE_EXTERN const char *_gcForHInfo(const header_info *hinfo)
{
    return "";
}
PRIVATE_EXTERN const char *_gcForHInfo2(const header_info *hinfo)
{
    return "";
}

#else

/***********************************************************************
* _gcForHInfo.
**********************************************************************/
PRIVATE_EXTERN const char *_gcForHInfo(const header_info *hinfo)
{
    if (_objcHeaderRequiresGC(hinfo)) {
        if (_objcHeaderSupportsCompaction(hinfo))
            return "requires GC, supports compaction";
        else
            return "requires GC";
    } else if (_objcHeaderSupportsGC(hinfo)) {
        if (_objcHeaderSupportsCompaction(hinfo))
            return "supports GC, supports compaction";
        else
            return "supports GC";
    } else {
        return "does not support GC";
    }
}
PRIVATE_EXTERN const char *_gcForHInfo2(const header_info *hinfo)
{
    if (_objcHeaderRequiresGC(hinfo)) {
        if (_objcHeaderSupportsCompaction(hinfo))
            return "(requires GC) (supports compaction)";
        else
            return "(requires GC)";
    } else if (_objcHeaderSupportsGC(hinfo)) {
        if (_objcHeaderSupportsCompaction(hinfo))
            return "(supports GC) (supports compaction)";
        else
            return "(supports GC)";
    }
    return "";
}


/***********************************************************************
* check_gc
* Check whether the executable supports or requires GC, and make sure 
* all already-loaded libraries support the executable's GC mode.
* Returns TRUE if the executable wants GC on.
**********************************************************************/
static void check_wants_gc(BOOL *appWantsGC, BOOL *appSupportsCompaction)
{
    const header_info *hi;

    // Environment variables can override the following.
    if (DisableGC) {
        _objc_inform("GC: forcing GC OFF because OBJC_DISABLE_GC is set");
        *appWantsGC = NO;
        *appSupportsCompaction = NO;
    }
    else {
        // Find the executable and check its GC bits. 
        // If the executable cannot be found, default to NO.
        // (The executable will not be found if the executable contains 
        // no Objective-C code.)
        *appWantsGC = NO;
        *appSupportsCompaction = NO;
        for (hi = FirstHeader; hi != NULL; hi = hi->next) {
            if (hi->mhdr->filetype == MH_EXECUTE) {
                *appWantsGC = _objcHeaderSupportsGC(hi) ? YES : NO;
                *appSupportsCompaction = (*appWantsGC && _objcHeaderSupportsCompaction(hi)) ? YES : NO;
                if (PrintGC) {
                    _objc_inform("GC: executable '%s' %s",
                                 _nameForHeader(hi->mhdr), _gcForHInfo(hi));
                }
            }
        }
    }
}


/***********************************************************************
* verify_gc_readiness
* if we want gc, verify that every header describes files compiled
* and presumably ready for gc.
************************************************************************/
static void verify_gc_readiness(BOOL wantsGC, BOOL *wantsCompaction,
                                header_info **hList, uint32_t hCount)
{
    BOOL busted = NO;
    uint32_t i;

    // Find the libraries and check their GC bits against the app's request
    for (i = 0; i < hCount; i++) {
        header_info *hi = hList[i];
        if (hi->mhdr->filetype == MH_EXECUTE) {
            continue;
        }
        else if (hi->mhdr == &_mh_dylib_header) {
            // libobjc itself works with anything even though it is not 
            // compiled with -fobjc-gc (fixme should it be?)
        } 
        else if (wantsGC  &&  ! _objcHeaderSupportsGC(hi)) {
            // App wants GC but library does not support it - bad
            _objc_inform_now_and_on_crash
                ("'%s' was not compiled with -fobjc-gc or -fobjc-gc-only, "
                 "but the application requires GC",
                 _nameForHeader(hi->mhdr));
            busted = YES;
        } 
        else if (!wantsGC  &&  _objcHeaderRequiresGC(hi)) {
            // App doesn't want GC but library requires it - bad
            _objc_inform_now_and_on_crash
                ("'%s' was compiled with -fobjc-gc-only, "
                 "but the application does not support GC",
                 _nameForHeader(hi->mhdr));
            busted = YES;            
        }
        
        if (*wantsCompaction && !_objcHeaderSupportsCompaction(hi)) {
            // App supports compaction, but library doesn't.
            _objc_inform_now_and_on_crash
                ("'%s' was not linked with -Xlinker -objc_gc_compaction, "
                 "but the application wants compaction.",
                 _nameForHeader(hi->mhdr));
            // Simply warn for now until radars are filed. Eventually,
            // objc_disableCompaction() will block until any current compaction completes.
            objc_disableCompaction();
            *wantsCompaction = NO;
        }

        if (PrintGC) {
            _objc_inform("GC: library '%s' %s", 
                         _nameForHeader(hi->mhdr), _gcForHInfo(hi));
        }
    }
    
    if (busted) {
        // GC state is not consistent. 
        // Kill the process unless one of the forcing flags is set.
        if (!DisableGC) {
            _objc_fatal("*** GC capability of application and some libraries did not match");
        }
    }
}


/***********************************************************************
* gc_enforcer
* Make sure that images about to be loaded by dyld are GC-acceptable.
* Images linked to the executable are always permitted; they are 
* enforced inside map_images() itself.
**********************************************************************/
static BOOL InitialDyldRegistration = NO;
static const char *gc_enforcer(enum dyld_image_states state, 
                               uint32_t infoCount, 
                               const struct dyld_image_info info[])
{
    uint32_t i;

    // Linked images get a free pass
    if (InitialDyldRegistration) return NULL;

    if (PrintImages) {
        _objc_inform("IMAGES: checking %d images for compatibility...", 
                     infoCount);
    }

    for (i = 0; i < infoCount; i++) {
        crashlog_header_name_string(info[i].imageFilePath);

        const headerType *mhdr = (const headerType *)info[i].imageLoadAddress;
        if (bad_magic(mhdr)) continue;

        objc_image_info *image_info;
        size_t size;

        if (mhdr == &_mh_dylib_header) {
            // libobjc itself - OK
            continue;
        }

#if !__OBJC2__
        unsigned long seg_size;
        // 32-bit: __OBJC seg but no image_info means no GC support
        if (!getsegmentdata(mhdr, "__OBJC", &seg_size)) {
            // not objc - assume OK
            continue;
        }
        image_info = _getObjcImageInfo(mhdr, &size);
        if (!image_info) {
            // No image_info - assume GC unsupported
            if (!UseGC) {
                // GC is OFF - ok
                continue;
            } else {
                // GC is ON - bad
                if (PrintImages  ||  PrintGC) {
                    _objc_inform("IMAGES: rejecting %d images because %s doesn't support GC (no image_info)", infoCount, info[i].imageFilePath);
                }
                goto reject;
            }
        }
#else
        // 64-bit: no image_info means no objc at all
        image_info = _getObjcImageInfo(mhdr, &size);
        if (!image_info) {
            // not objc - assume OK
            continue;
        }
#endif

        if (UseGC  &&  !_objcInfoSupportsGC(image_info)) {
            // GC is ON, but image does not support GC
            if (PrintImages  ||  PrintGC) {
                _objc_inform("IMAGES: rejecting %d images because %s doesn't support GC", infoCount, info[i].imageFilePath);
            }
            goto reject;
        }
        if (!UseGC  &&  _objcInfoRequiresGC(image_info)) {
            // GC is OFF, but image requires GC
            if (PrintImages  ||  PrintGC) {
                _objc_inform("IMAGES: rejecting %d images because %s requires GC", infoCount, info[i].imageFilePath);
            }
            goto reject;
        }
    }

    crashlog_header_name_string(NULL);
    return NULL;

 reject:
    crashlog_header_name_string(NULL);
    return "GC capability mismatch";
}

// SUPPORT_GC
#endif


/***********************************************************************
* map_images_nolock
* Process the given images which are being mapped in by dyld.
* All class registration and fixups are performed (or deferred pending
* discovery of missing superclasses etc), and +load methods are called.
*
* info[] is in bottom-up order i.e. libobjc will be earlier in the 
* array than any library that links to libobjc.
*
* Locking: loadMethodLock(old) or runtimeLock(new) acquired by map_images.
**********************************************************************/
PRIVATE_EXTERN const char *
map_images_nolock(enum dyld_image_states state, uint32_t infoCount,
                  const struct dyld_image_info infoList[])
{
    static BOOL firstTime = YES;
    static BOOL wantsGC = NO;
    static BOOL wantsCompaction = NO;
    uint32_t i;
    header_info *hi;
    header_info *hList[infoCount];
    uint32_t hCount;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
#if SUPPORT_GC
        InitialDyldRegistration = YES;
        dyld_register_image_state_change_handler(dyld_image_state_mapped, 0 /* batch */, &gc_enforcer);
        InitialDyldRegistration = NO;
#endif
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", infoCount);
    }


    // Find all images with Objective-C metadata.
    hCount = 0;
    i = infoCount;
    while (i--) {
        const headerType *mhdr = (headerType *)infoList[i].imageLoadAddress;

        hi = _objc_addHeader(mhdr);
        if (!hi) {
            // no objc data in this entry
            continue;
        }

        hList[hCount++] = hi;
        

        if (PrintImages) {
            _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                         _nameForHeader(mhdr), 
                         mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", 
                         _objcHeaderIsReplacement(hi) ? " (replacement)" : "",
                         _objcHeaderOptimizedByDyld(hi)?" (preoptimized)" : "",
                         _gcForHInfo2(hi));
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later. In that case, check_wants_gc() 
    // will do the right thing.)
#if SUPPORT_GC
    if (firstTime) {
        check_wants_gc(&wantsGC, &wantsCompaction);

        verify_gc_readiness(wantsGC, &wantsCompaction, hList, hCount);
        
        gc_init(wantsGC, wantsCompaction);  // needs executable for GC decision
        rtp_init();                         // needs GC decision first
    } else {
        verify_gc_readiness(wantsGC, &wantsCompaction, hList, hCount);
    }

    if (wantsGC) {
        // tell the collector about the data segment ranges.
        for (i = 0; i < hCount; ++i) {
            uint8_t *seg;
            unsigned long seg_size;
            hi = hList[i];

            seg = getsegmentdata(hi->mhdr, "__DATA", &seg_size);
            if (seg) gc_register_datasegment((uintptr_t)seg, seg_size);

            seg = getsegmentdata(hi->mhdr, "__OBJC", &seg_size);
            if (seg) gc_register_datasegment((uintptr_t)seg, seg_size);
            // __OBJC contains no GC data, but pointers to it are 
            // used as associated reference values (rdar://6953570)
        }
    }

    // Need to fixup barriers in all libraries that call into libobjc, whether GC is on or not.
    for (i = 0; i < infoCount; ++i) {
        gc_fixup_barrier_stubs(&infoList[i]);
    }
#endif

    if (firstTime) {
        extern SEL FwdSel;  // in objc-msg-*.s
        sel_init(wantsGC);
        FwdSel = sel_registerName("forward::");

        arr_init();
    }

    _read_images(hList, hCount);

    firstTime = NO;

    return NULL;
}


/***********************************************************************
* load_images_nolock
* Prepares +load in the given images which are being mapped in by dyld.
* Returns YES if there are now +load methods to be called by call_load_methods.
*
* Locking: loadMethodLock(both) and runtimeLock(new) acquired by load_images
**********************************************************************/
PRIVATE_EXTERN BOOL 
load_images_nolock(enum dyld_image_states state,uint32_t infoCount,
                   const struct dyld_image_info infoList[])
{
    BOOL found = NO;
    uint32_t i;

    i = infoCount;
    while (i--) {
        header_info *hi;
        for (hi = FirstHeader; hi != NULL; hi = hi->next) {
            const headerType *mhdr = (headerType*)infoList[i].imageLoadAddress;
            if (hi->mhdr == mhdr) {
                prepare_load_methods(hi);
                found = YES;
            }
        }
    }

    return found;
}


/***********************************************************************
* unmap_image_nolock
* Process the given image which is about to be unmapped by dyld.
* mh is mach_header instead of headerType because that's what 
*   dyld_priv.h says even for 64-bit.
* 
* Locking: loadMethodLock(both) and runtimeLock(new) acquired by unmap_image.
**********************************************************************/
PRIVATE_EXTERN void 
unmap_image_nolock(const struct mach_header *mh)
{
    if (PrintImages) {
        _objc_inform("IMAGES: processing 1 newly-unmapped image...\n");
    }

    header_info *hi;
    
    // Find the runtime's header_info struct for the image
    for (hi = FirstHeader; hi != NULL; hi = hi->next) {
        if (hi->mhdr == (const headerType *)mh) {
            break;
        }
    }

    if (!hi) return;

    if (PrintImages) { 
        _objc_inform("IMAGES: unloading image for %s%s%s%s\n", 
                     _nameForHeader(hi->mhdr), 
                     hi->mhdr->filetype == MH_BUNDLE ? " (bundle)" : "", 
                     _objcHeaderIsReplacement(hi) ? " (replacement)" : "", 
                     _gcForHInfo2(hi));
    }

#if SUPPORT_GC
    if (UseGC) {
        uint8_t *seg;
        unsigned long seg_size;

        seg = getsegmentdata(hi->mhdr, "__DATA", &seg_size);
        if (seg) gc_unregister_datasegment((uintptr_t)seg, seg_size);

        seg = getsegmentdata(hi->mhdr, "__OBJC", &seg_size);
        if (seg) gc_unregister_datasegment((uintptr_t)seg, seg_size);
    }
#endif

    _unload_image(hi);

    // Remove header_info from header list
    _objc_removeHeader(hi);
    _free_internal(hi);
}


/***********************************************************************
* _objc_init
* Static initializer. Registers our image notifier with dyld.
**********************************************************************/
static __attribute__((constructor))
void _objc_init(void)
{
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    lock_init();
    exception_init();
        
    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}


/***********************************************************************
* _headerForAddress.
* addr can be a class or a category
**********************************************************************/
static const header_info *_headerForAddress(void *addr)
{
#if __OBJC2__
    const char *segname = "__DATA";
#else
    const char *segname = "__OBJC";
#endif
    header_info *hi;

    // Check all headers in the vector
    for (hi = FirstHeader; hi != NULL; hi = hi->next)
    {
        uint8_t *seg;
        unsigned long seg_size;

        seg = getsegmentdata(hi->mhdr, segname, &seg_size);
        if (!seg) continue;

        // Is the class in this header?
        if ((uint8_t *)addr >= seg  &&  (uint8_t *)addr < seg + seg_size)
            return hi;
    }

    // Not found
    return 0;
}


/***********************************************************************
* _headerForClass
* Return the image header containing this class, or NULL.
* Returns NULL on runtime-constructed classes, and the NSCF classes.
**********************************************************************/
PRIVATE_EXTERN const header_info *_headerForClass(Class cls)
{
    return _headerForAddress(cls);
}


/**********************************************************************
* secure_open
* Securely open a file from a world-writable directory (like /tmp)
* If the file does not exist, it will be atomically created with mode 0600
* If the file exists, it must be, and remain after opening: 
*   1. a regular file (in particular, not a symlink)
*   2. owned by euid
*   3. permissions 0600
*   4. link count == 1
* Returns a file descriptor or -1. Errno may or may not be set on error.
**********************************************************************/
PRIVATE_EXTERN int secure_open(const char *filename, int flags, uid_t euid)
{
    struct stat fs, ls;
    int fd = -1;
    BOOL truncate = NO;
    BOOL create = NO;

    if (flags & O_TRUNC) {
        // Don't truncate the file until after it is open and verified.
        truncate = YES;
        flags &= ~O_TRUNC;
    }
    if (flags & O_CREAT) {
        // Don't create except when we're ready for it
        create = YES;
        flags &= ~O_CREAT;
        flags &= ~O_EXCL;
    }

    if (lstat(filename, &ls) < 0) {
        if (errno == ENOENT  &&  create) {
            // No such file - create it
            fd = open(filename, flags | O_CREAT | O_EXCL, 0600);
            if (fd >= 0) {
                // File was created successfully.
                // New file does not need to be truncated.
                return fd;
            } else {
                // File creation failed.
                return -1;
            }
        } else {
            // lstat failed, or user doesn't want to create the file
            return -1;
        }
    } else {
        // lstat succeeded - verify attributes and open
        if (S_ISREG(ls.st_mode)  &&  // regular file?
            ls.st_nlink == 1  &&     // link count == 1?
            ls.st_uid == euid  &&    // owned by euid?
            (ls.st_mode & ALLPERMS) == (S_IRUSR | S_IWUSR))  // mode 0600?
        {
            // Attributes look ok - open it and check attributes again
            fd = open(filename, flags, 0000);
            if (fd >= 0) {
                // File is open - double-check attributes
                if (0 == fstat(fd, &fs)  &&  
                    fs.st_nlink == ls.st_nlink  &&  // link count == 1?
                    fs.st_uid == ls.st_uid  &&      // owned by euid?
                    fs.st_mode == ls.st_mode  &&    // regular file, 0600?
                    fs.st_ino == ls.st_ino  &&      // same inode as before?
                    fs.st_dev == ls.st_dev)         // same device as before?
                {
                    // File is open and OK
                    if (truncate) ftruncate(fd, 0);
                    return fd;
                } else {
                    // Opened file looks funny - close it
                    close(fd);
                    return -1;
                }
            } else {
                // File didn't open
                return -1;
            }
        } else {
            // Unopened file looks funny - don't open it
            return -1;
        }
    }
}


/***********************************************************************
* _objc_internal_zone.
* Malloc zone for internal runtime data.
* By default this is the default malloc zone, but a dedicated zone is 
* used if environment variable OBJC_USE_INTERNAL_ZONE is set.
**********************************************************************/
PRIVATE_EXTERN malloc_zone_t *_objc_internal_zone(void)
{
    static malloc_zone_t *z = (malloc_zone_t *)-1;
    if (z == (malloc_zone_t *)-1) {
        if (UseInternalZone) {
            z = malloc_create_zone(vm_page_size, 0);
            malloc_set_zone_name(z, "ObjC");
        } else {
            z = malloc_default_zone();
        }
    }
    return z;
}


PRIVATE_EXTERN const char *
_getObjcHeaderName(const headerType *header)
{
    Dl_info info;

    if (dladdr(header, &info)) {
        return info.dli_fname;
    }
    else {
        return (*_NSGetArgv())[0];
    }
}


PRIVATE_EXTERN bool crashlog_header_name(header_info *hi)
{
    return crashlog_header_name_string(hi ? hi->os.dl_info.dli_fname : NULL);
}

PRIVATE_EXTERN bool crashlog_header_name_string(const char *name)
{
    CRSetCrashLogMessage2(name);
    return true;
}


#if TARGET_OS_IPHONE

PRIVATE_EXTERN const char *__crashreporter_info__ = NULL;

PRIVATE_EXTERN const char *CRSetCrashLogMessage(const char *msg)
{
    __crashreporter_info__ = msg;
    return msg;
}
PRIVATE_EXTERN const char *CRGetCrashLogMessage(void)
{
    return __crashreporter_info__;
}

PRIVATE_EXTERN const char *CRSetCrashLogMessage2(const char *msg)
{
    // sorry
    return msg;
}

#endif

// TARGET_OS_MAC
#else


#error unknown OS


#endif
