//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/***
*mlock.c - Multi-thread locking routines
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*
*Revision History:
*       05-07-90  JCR   Module created.
*       06-04-90  GJF   Changed error message interface.
*       08-08-90  GJF   Removed 32 from API names.
*       08-08-90  SBM   _lockmap no longer 8 times required size
*       10-08-90  GJF   New-style function declarators. Removed questionable
*                       return statements from void functions (weren't needed
*                       and the compiler was complaining).
*       10-09-90  GJF   Thread ids are unsigned longs.
*       06-06-91  GJF   Adapted for Win32 [_WIN32_].
*       09-29-91  GJF   Fixed infinite recursion problem with DEBUG version
*                       of _lock [_WIN32_].
*       03-06-92  GJF   Removed _[un]lock_fh() and _[un]lock_stream for Win32
*                       targets.
*       05-28-92  GJF   Added _mtdeletelocks() for Win32 for DLLs with contain
*                       the C runtime (e.g., crtdll.dll).
*       10-06-92  SRW   Make _locktable an array of PCRITICAL_SECTION pointers
*                       instead of structures.  Allocate each critical section
*                       as it is needed.
*       02-25-93  GJF   Substantially revised. Restored static critical section
*                       structures for some locks. Replaced bit-array scheme
*                       of keeping track of locks. Removed Cruiser support and
*                       replaced obsolete DEBUG code.
*       03-03-93  GJF   Made CRITICAL_SECTION structure for _HEAP_LOCK static.
*       03-08-93  SKS   Fix ptr use error in DEBUG version of _mtdeletelocks
*       03-08-93  SKS   Fix deletion of the special critical sections,
*                       especially the heap lock.
*       04-06-93  SKS   Replace _CRTAPI* with __cdecl
*       05-05-93  GJF   Turned DEBUG code off.
*       06-03-93  SRW   Disable FPO optimizations for this file so it can call
*                       CriticalSection routines on a checked build even though
*                       the C Runtimes are compiled free.
*       10-19-93  GJF   Merged in NT SDK version. Enclosed #pragma-s in
*                       #ifdef _M_IX86 / #endif. Replaced MTHREAD with _MT.
*       04-12-94  GJF   Made into empty functions for the Win32s version of
*                       msvcrt*.dll.
*       01-10-95  CFW   Debug CRT allocs.
*       01-30-95  GJF   Made CRITICAL_SECTION structure for _SIGNAL_LOCK
*                       static.
*       03-06-95  GJF   Added _[un]lock_file[2] to lock stdio files (__piob[]
*                       entries).
*       03-23-95  BWT   Store the critsec in the locktable *after* it's
*                       initialized.
*       10-03-95  GJF   Added comments to the effect that the _LC_*_LOCK
*                       locks are obsolete.
*       11-15-95  JWM   Correct syntax error in 2nd '#pragma optimize()'.
*       06-19-97  GJF   Moved _[un]lock_file[2]() to stdio\_file.c to improve 
*                       granularity.
*       05-13-99  PML   Remove Win32s
*       10-14-99  PML   Replace InitializeCriticalSection with wrapper function
*                       __crtInitCritSecAndSpinCount
*       12-10-99  GB    Added a new Lock _UNDNAME_LOCK for critical section in
*                       unDName().
*       03-06-00  PML   Call __crtExitProcess instead of ExitProcess.
*       02-20-01  PML   vs7#172586 Avoid _RT_LOCK by preallocating all locks
*                       that will be required, and returning failure back on
*                       inability to allocate a lock.
*       03-07-01  PML   vs7#221122 Release preallocated locks after heap ones,
*                       so _HEAP_LOCK is around while we're still freeing mem.
*       03-22-01  PML   Add _DEBUG_LOCK for _CrtSetReportHook2 (vs7#124998)
*       06-12-01  BWT   ntbug: 414059 - cleanup from mtinit failure
*       04-29-02  GB    Added try-finally arounds lock-unlock.
*       12-22-03  MSL   VSWhidbey#174074 - Added new lock for serializing 
*                       asserts across threads
*       03-13-04  MSL   Avoid returing from __try for prefast
*       01-28-05  SJ    Making DEBUG_LOCK preallocated.
*
*******************************************************************************/

#include <cruntime.h>
#include <oscalls.h>
#include <awint.h>
#include <internal.h>
#include <mtdll.h>
#include <rterr.h>
#include <stddef.h>
#include <malloc.h>
#include <limits.h>
#include <stdio.h>
#include <dbgint.h>
#include <errno.h>
#include <winheap.h>


/*
 * Local routines
 */
void __cdecl _lockerr_exit(char *);


/*
 * Global Data
 */

/*
 * Statically allocated critical section structures for all preallocated locks.
 * These are most of the named locks before _STREAM_LOCKS, along with the locks
 * for stdin/stdout/stderr.  These must be preallocated so we do not hit fatal
 * memory conditions on failing to initialize a critical section, except at
 * runtime startup, since these locks may be taken where we have no good way
 * to return a non-fatal error.
 */
#define NUM_STD_FILE_LOCKS     3

/*
 *  _DEBUG_LOCK is preallocated in _DEBUG & not in Retail
 */

#ifdef _DEBUG
#define NUM_NON_PREALLOC_LOCKS 4
#else
#define NUM_NON_PREALLOC_LOCKS 5
#endif

#define NUM_PREALLOC_LOCKS \
        ( _STREAM_LOCKS + NUM_STD_FILE_LOCKS - NUM_NON_PREALLOC_LOCKS )
static CRITICAL_SECTION lclcritsects[NUM_PREALLOC_LOCKS];

/*
 * Lock Table
 * This table contains a pointer to the critical section management structure
 * for each lock.
 *
 * Locks marked lkPrealloc have their critical sections statically allocated
 * and initialized at startup in _mtinitlocks.  Locks marked lkNormal must
 * be allocated when first used, via a call to _mtinitlocknum.
 */
static struct {
        PCRITICAL_SECTION lock;
        enum { lkNormal = 0, lkPrealloc, lkDeleted } kind;
} _locktable[_TOTAL_LOCKS] = {
        { NULL, lkPrealloc }, /* 0  == _SIGNAL_LOCK      */
        { NULL, lkPrealloc }, /* 1  == _IOB_SCAN_LOCK    */
        { NULL, lkNormal   }, /* 2  == _TMPNAM_LOCK      - not preallocated */
        { NULL, lkPrealloc }, /* 3  == _CONIO_LOCK       */
        { NULL, lkPrealloc }, /* 4  == _HEAP_LOCK        */
        { NULL, lkNormal   }, /* 5  == _UNDNAME_LOCK     - not preallocated */
        { NULL, lkPrealloc }, /* 6  == _TIME_LOCK        */
        { NULL, lkPrealloc }, /* 7  == _ENV_LOCK         */
        { NULL, lkPrealloc }, /* 8  == _EXIT_LOCK1       */
        { NULL, lkNormal   }, /* 9  == _POPEN_LOCK       - not preallocated */
        { NULL, lkPrealloc }, /* 10 == _LOCKTAB_LOCK     */
        { NULL, lkNormal   }, /* 11 == _OSFHND_LOCK      - not preallocated */
        { NULL, lkPrealloc }, /* 12 == _SETLOCALE_LOCK   */
        { NULL, lkPrealloc }, /* 13 == _MB_CP_LOCK       */
        { NULL, lkPrealloc }, /* 14 == _TYPEINFO_LOCK    */
#ifdef _DEBUG
        { NULL, lkPrealloc }, /* 15 == _DEBUG_LOCK       */
#else
        { NULL, lkNormal },   /* 15 == _DEBUG_LOCK       */
#endif

        { NULL, lkPrealloc }, /* 16 == _STREAM_LOCKS+0 - stdin  */
        { NULL, lkPrealloc }, /* 17 == _STREAM_LOCKS+1 - stdout */
        { NULL, lkPrealloc }, /* 18 == _STREAM_LOCKS+2 - stderr */
/*      { NULL, lkNormal   }, /* ... */
};

#ifndef NT_BUILD
#ifdef  _M_IX86
#pragma optimize("y",off)
#endif
#endif

/***
*_mtinitlocks() - Initialize multi-thread lock scheme
*
*Purpose:
*       Perform whatever initialization is required for the multi-thread
*       locking (synchronization) scheme. This routine should be called
*       exactly once, during startup, and this must be before any requests
*       are made to assert locks.
*
*       NOTES: In Win32, the multi-thread locks are created individually,
*       each upon its first use. That is when any particular lock is asserted
*       for the first time, the underlying critical section is then allocated,
*       initialized and (finally) entered. This allocation and initialization
*       is protected under _LOCKTAB_LOCK. It is _mtinitlocks' job to set up
*       _LOCKTAB_LOCK.
*
*       All other named (non-FILE) locks are also preallocated in _mtinitlocks.
*       That is because a failure to allocate a lock on its first use in _lock
*       triggers a fatal error, which cannot be permitted since that can bring
*       down a long-lived app without warning.
*
*Entry:
*       <none>
*
*Exit:
*       returns FALSE on failure
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _mtinitlocks (
        void
        )
{
        int locknum;
        int idxPrealloc = 0;

        /*
         * Scan _locktable[] and allocate all entries marked lkPrealloc.
         */
        for ( locknum = 0 ; locknum < _TOTAL_LOCKS ; locknum++ ) {
#ifdef  DEBUG
            if ( _locktable[locknum].lock != NULL )
                _lockerr_exit("fatal error in _mtinitlocks #1\n");
#endif  /* DEBUG */
            if ( _locktable[locknum].kind == lkPrealloc ) {
                _locktable[locknum].lock = &lclcritsects[idxPrealloc++];
#ifdef _WIN32_WCE // CE doesn't support SPIN count
                InitializeCriticalSection( _locktable[locknum].lock );
#else                
                InitializeCriticalSectionAndSpinCount( _locktable[locknum].lock,_CRT_SPINCOUNT );
#endif // _WIN32_WCE
            }
        }

#ifdef  DEBUG
        if ( idxPrealloc != NUM_PREALLOC_LOCKS )
            _lockerr_exit("fatal error in _mtinitlocks #2\n");
#endif  /* DEBUG */

        return TRUE;
}


/***
*_mtdeletelocks() - Delete all initialized locks
*
*Purpose:
*       Walks _locktable[] and _lockmap, and deletes every 'lock' (i.e.,
*       critical section) which has been initialized.
*
*       This function is intended for use in DLLs containing the C runtime
*       (i.e., crtdll.dll and user DLLs built using libcmt.lib and the
*       special startup objects). It is to be called from within the DLL's
*       entrypoint function when that function is called with
*       DLL_PROCESS_DETACH.
*
*Entry:
*       <none>
*
*Exit:
*
*Exceptions:
*       behavior undefined/unknown if a lock is being held when this routine
*       is called.
*
*******************************************************************************/

void __cdecl _mtdeletelocks(
        void
        )
{
        int locknum;

        /*
         * Delete and free all normal locks that have been created.
         */
        for ( locknum = 0 ; locknum < _TOTAL_LOCKS ; locknum++ ) {
            if ( _locktable[locknum].lock != NULL &&
                 _locktable[locknum].kind != lkPrealloc )
            {
                PCRITICAL_SECTION pcs = _locktable[locknum].lock;

                DeleteCriticalSection(pcs);

                /*
                 * Free the memory for the CritSect after deleting it.
                 */
#ifdef  DEBUG
                /* check that it's an undeleted normal lock */
                if ( _locktable[locknum].kind != lkNormal )
                    _lockerr_exit("fatal error in _mtdeletelocks #1\n");

                /* mark as deleted */
                _locktable[locknum].kind = lkDeleted;
#endif  /* DEBUG */

                _free_crt(pcs);
                _locktable[locknum].lock = NULL;
            }
        }

        /*
         * Delete all preallocated locks after all normal ones are
         * freed (so preallocated _HEAP_LOCK outlives all heap usages).
         */
        for ( locknum = 0 ; locknum < _TOTAL_LOCKS ; locknum++ ) {
            if ( _locktable[locknum].lock != NULL &&
                 _locktable[locknum].kind == lkPrealloc )
            {
                PCRITICAL_SECTION pcs = _locktable[locknum].lock;

                DeleteCriticalSection(pcs);
            }
        }
}

/***
* _mtinitlocknum - Allocate a non-preallocated multi-thread lock
*
*Purpose:
*       Allocate a new, non-preallocated multi-thread lock.  This should be
*       used whenever a new lock is known to be needed, so that failure to
*       allocate can return an error, instead of allowing _lock() to issue
*       a fatal _RT_LOCK instead.
*
*       It is not an error to call this on a normal lock which has already
*       been allocated.  It is used to ensure that certain named locks which
*       are not preallocated are available.
*
*       It is also called by _lock, in case any other paths exist which call
*       _lock without calling _mtinitlocknum first.  This is not expected,
*       and can allow fatal _RT_LOCK errors to be issued.
*
*       Since a failure sets errno to ENOMEM, this should only be called
*       after the per-thread data has been set up (after _mtinit).
*
*Entry:
*       locknum = number of the lock to aquire
*
*Exit:
*       Returns FALSE on failure, and sets errno to ENOMEM.
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _mtinitlocknum (
        int locknum
        )
{
        PCRITICAL_SECTION pcs;
        int retval=TRUE;

        /*
         * Check if CRT is initialized. The check if _crtheap is initialized
         * will do the job. More over we had to add this test in initlocks because
         * in debug version we don't endup calling lock before calling malloc_base,
         * where we check for crtheap.
         */
        if (_crtheap == 0) {
            _FF_MSGBANNER();    /* write run-time error banner */
            _NMSG_WRITE(_RT_CRT_NOTINIT);  /* write message */
            __crtExitProcess(255);  /* normally _exit(255) */
        }

#ifdef  DEBUG
        if ( _locktable[locknum].kind != lkNormal )
            _lockerr_exit("fatal error in _mtinitlocknum #1\n");
#endif  /* DEBUG */

        if ( _locktable[locknum].lock != NULL )
            return TRUE;

        if ( (pcs = _malloc_crt(sizeof(CRITICAL_SECTION))) == NULL ) {
            errno = ENOMEM;
            return FALSE;
        }

        _mlock(_LOCKTAB_LOCK);
        __TRY

            if ( _locktable[locknum].lock == NULL ) {
#ifdef _WIN32_WCE // CE doesn't support SPIN count
                InitializeCriticalSection( pcs );
#else
                InitializeCriticalSectionAndSpinCount(pcs, _CRT_SPINCOUNT);
#endif // _WIN32_WCE
                _locktable[locknum].lock = pcs;
            }
            else {
                _free_crt(pcs);
            }

        __FINALLY
            _munlock(_LOCKTAB_LOCK);
        __END_TRY_FINALLY

        return retval;
}


/***
* _lock - Acquire a multi-thread lock
*
*Purpose:
*       Acquire a multi-thread lock.  If the lock has not already been
*       allocated, do so, but that is an internal CRT error, since all locks
*       should be allocated before first being acquired, either in
*       _mtinitlocks or individually in _mtinitlocknum.
*
*       Note that it is legal for a thread to aquire _EXIT_LOCK1
*       multiple times.
*
*Entry:
*       locknum = number of the lock to aquire
*
*Exit:
*
*Exceptions:
*       A failure to allocate a new lock results in a fatal _RT_LOCK error.
*
*******************************************************************************/

void __cdecl _lock (
        int locknum
        )
{
#ifdef  DEBUG
        if ( _locktable[locknum].kind != lkNormal &&
             _locktable[locknum].kind != lkPrealloc )
            _lockerr_exit("fatal error in _lock #1\n");
#endif  /* DEBUG */

        /*
         * Create/open the lock, if necessary
         */
        if ( _locktable[locknum].lock == NULL ) {
#ifdef  DEBUG
            if ( _locktable[locknum].kind != lkNormal ) {
                _lockerr_exit("fatal error in _lock #2\n");
            }

            /*
             * All locks should be allocated before first being acquired.
             * Failure to do so is an internal CRT error, which we silently
             * allow in a production CRT, but that can lead to fatal _RT_LOCK
             * errors which result in an ExitProcess call.
             */
            //TODO: VS11 porting: Need to determine how to initialize _OSFHND_LOCK for
            // String I/O functions.
            //_lockerr_exit("fatal error in _lock #3\n");
#endif  /* DEBUG */

            if ( !_mtinitlocknum(locknum) )
                _amsg_exit( _RT_LOCK );
        }

        /*
         * Enter the critical section.
         */

        EnterCriticalSection( _locktable[locknum].lock );
}


/***
* _unlock - Release multi-thread lock
*
*Purpose:
*       Note that it is legal for a thread to aquire _EXIT_LOCK1
*       multiple times.
*
*Entry:
*       locknum = number of the lock to release
*
*Exit:
*
*Exceptions:
*
*******************************************************************************/

void __cdecl _unlock (
        int locknum
        )
{
        /*
         * leave the critical section.
         */
        LeaveCriticalSection( _locktable[locknum].lock );
}


#ifndef NT_BUILD
#ifdef  _M_IX86
#pragma optimize("y",on)
#endif
#endif

/***
*_lockerr_exit() - Write error message and die
*
*Purpose:
*       Attempt to write out the unexpected lock error message, then terminate
*       the program by a direct API call.  This function is used in place of
*       amsg_exit(_RT_LOCK) when it is judged unsafe to allow further lock
*       or unlock calls.
*
*Entry:
*
*Exit:
*
*Exceptions:
*
*******************************************************************************/

void __cdecl _lockerr_exit (
        char *msg
        )
{
#ifndef _WIN32_WCE // CE doesn't support FatalAppExit api, and CE doesn't support packaged application
        if (!__crtIsPackagedApp())
        {
            FatalAppExit(0, msg);       /* Die with message box */
        }
#endif // _WIN32_WCE	
        __crtExitProcess(255);      /* Just die */
}

