//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

/*++



Module Name:

    cruntime/misc.cpp

Abstract:

    Implementation of C runtime functions that don't fit anywhere else.



--*/

#include "pal/thread.hpp"
#include "pal/threadsusp.hpp"
#include "pal/malloc.hpp"
#include "pal/palinternal.h"
#include "pal/dbgmsg.h"
#include "pal/misc.h"

#include <errno.h>
/* <stdarg.h> needs to be included after "palinternal.h" to avoid name
   collision for va_start and va_end */
#include <stdarg.h>
#include <time.h>
#include <limits.h>
#if HAVE_CRT_EXTERNS_H
#include <crt_externs.h>
#endif  // HAVE_CRT_EXTERNS_H
#if defined(_AMD64_) || defined(_x86_)
#include <xmmintrin.h>
#endif // defined(_AMD64_) || defined(_x86_)

SET_DEFAULT_DEBUG_CHANNEL(CRT);

char **palEnvironment = NULL;

CRITICAL_SECTION gcsEnvironment;

using namespace CorUnix;

/*++
Function:
  _gcvt_s

See MSDN doc.
--*/
char *
__cdecl
_gcvt_s( char * buffer, int iSize, double value, int digits )
{
    PERF_ENTRY(_gcvt);
    ENTRY( "_gcvt( value:%f digits=%d, buffer=%p )\n", value, digits, buffer );

    if ( !buffer )
    {
        ERROR( "buffer was an invalid pointer.\n" );
    }

    switch ( digits )
    {
    case 7 :
        /* Fall through */
    case 8 :
        /* Fall through */
    case 15 :
        /* Fall through */
    case 17 :

        sprintf_s( buffer, iSize, "%.*g", digits, value );
        break;

    default :
        ASSERT( "Only the digits 7, 8, 15, and 17 are valid.\n" );
        *buffer = '\0';
    }

    LOGEXIT( "_gcvt returns %p (%s)\n", buffer , buffer );
    PERF_EXIT(_gcvt);
    return buffer;
}


/*++
Function :

    __iscsym

See MSDN for more details.
--*/
int
__cdecl
__iscsym( int c )
{
    PERF_ENTRY(__iscsym);
    ENTRY( "__iscsym( c=%d )\n", c );

    if ( isalnum( c ) || c == '_'  )
    {
        LOGEXIT( "__iscsym returning 1\n" );
        PERF_EXIT(__iscsym);
        return 1;
    }

    LOGEXIT( "__iscsym returning 0\n" );
    PERF_EXIT(__iscsym);
    return 0;
}


/*++

Function :

    PAL_errno

    Returns the address of the errno.

--*/
int * __cdecl PAL_errno( int caller )
{
    int *retval;
    PERF_ENTRY(errno);
    ENTRY( "PAL_errno( void )\n" );
    retval = (INT*)(&errno);
    LOGEXIT("PAL_errno returns %p\n",retval);
    PERF_EXIT(errno);
    return retval;
}

/*++

Function : _putenv.

See MSDN for more details.

Note:   The BSD implementation can cause
        memory leaks. See man pages for more details.
--*/
int
__cdecl
_putenv( const char * envstring )
{
    int ret = -1;

    PERF_ENTRY(_putenv);
    ENTRY( "_putenv( %p (%s) )\n", envstring ? envstring : "NULL", envstring ? envstring : "NULL") ;

    if (!envstring)
    {
        ERROR( "_putenv() called with NULL envstring!\n");
        goto EXIT;
    }

    ret = MiscPutenv(envstring, TRUE) ? 0 : -1;

EXIT:
    LOGEXIT( "_putenv returning %d\n", ret);
    PERF_EXIT(_putenv);
    return ret;
}

/*++

Function : PAL_getenv

See MSDN for more details.
--*/
char * __cdecl PAL_getenv(const char *varname)
{
    char *retval;

    PERF_ENTRY(getenv);
    ENTRY("getenv (%p (%s))\n", varname ? varname : "NULL", varname ? varname : "NULL");

    if (strcmp(varname, "") == 0)
    {
        ERROR("getenv called with a empty variable name\n");
        LOGEXIT("getenv returning NULL\n");
        PERF_EXIT(getenv);
        return(NULL);
    }
    retval = MiscGetenv(varname);

    LOGEXIT("getenv returning %p\n", retval);
    PERF_EXIT(getenv);
    return(retval);
}

/*++
Function:

   rand

   The difference between the FreeBSD and Windows implementations is the max
   of the return value. in FreeBSD, RAND_MAX is 0x7fffffff and in Windows
   it's 0x7fff.

See MSDN for more details.
--*/
int
__cdecl
PAL_rand(void)
{
    int ret;
    PERF_ENTRY(rand);
    ENTRY("rand(void)\n");

    ret = (rand() % (PAL_RAND_MAX + 1));

    LOGEXIT("rand() returning %d\n", ret);
    PERF_EXIT(rand);
    return ret;
}

PALIMPORT
void * __cdecl
PAL_bsearch(const void *key, const void *base, size_t nmemb, size_t size,
            int (__cdecl *compar)(const void *, const void *))
{
    void *retval;

    PERF_ENTRY(bsearch);
    ENTRY("bsearch(key=%p, base=%p, nmemb=%lu, size=%lu, compar=%p\n",
          key, base, (unsigned long) nmemb, (unsigned long) size, compar);

/* reset ENTRY nesting level back to zero, bsearch will invoke app-defined
   callbacks and we want their entry traces... */
#if _ENABLE_DEBUG_MESSAGES_
{
    int old_level;
    old_level = DBG_change_entrylevel(0);
#endif /* _ENABLE_DEBUG_MESSAGES_ */

    retval = bsearch(key,base,nmemb,size,compar);

/* ...and set nesting level back to what it was */
#if _ENABLE_DEBUG_MESSAGES_
    DBG_change_entrylevel(old_level);
}
#endif /* _ENABLE_DEBUG_MESSAGES_ */

    LOGEXIT("bsearch returns %p\n",retval);
    PERF_EXIT(bsearch);
    return retval;
}

/*++
Function:
  MiscGetEnvArray

Get a reference to the process's environ array into palEnvironment

NOTE: This function MUST be called while holding the gcsEnvironment
      critical section (except if the caller is the initialization
      routine)
--*/
void
MiscGetEnvArray(void)
{
#if HAVE__NSGETENVIRON
    palEnvironment = *(_NSGetEnviron());
#else   // HAVE__NSGETENVIRON
    extern char **environ;
    palEnvironment = environ;
#endif  // HAVE__NSGETENVIRON
}

/*++
Function:
  MiscSetEnvArray

Make sure the process's environ array is in sync with palEnvironment variable

NOTE: This function MUST be called while holding the gcsEnvironment
      critical section (except if the caller is the initialization
      routine)
--*/
void
MiscSetEnvArray(void)
{
#if HAVE__NSGETENVIRON
    *(_NSGetEnviron()) = palEnvironment;
#else   // HAVE__NSGETENVIRON
    extern char **environ;
    environ = palEnvironment;
#endif  // HAVE__NSGETENVIRON
}

/*++
Function:
  MiscInitialize

Initialization function called from PAL_Initialize.
Allocates the TLS Index. On systems that use extern variables for
time zone information, this also initializes those variables.

Note: This is called before debug channels are initialized, so it
      cannot use debug tracing calls.
--*/
BOOL
MiscInitialize(void)
{
    InternalInitializeCriticalSection(&gcsEnvironment);
    MiscGetEnvArray();

    return TRUE;
}

/*++
Function:
  MiscCleanup

Termination function called from PAL_Terminate to delete the
TLS Keys created in MiscInitialize
--*/
void MiscCleanup(void)
{
     TRACE("Cleaning Misc...\n");
     InternalDeleteCriticalSection(&gcsEnvironment);
}

int compare(const char *left, const char *right, int *last_index)
{
    int i;
    int result = 0;
    for(i = 0; left[i] != 0 && result == 0 && i < INT_MAX; i++)
    {
        result = (left[i] == right[i]) ? 0 : (left[i] < right[i] ? -1 : 1);
    }
    *last_index = i;
    return result;
}

/*++
Function:
  MiscGetenv

Gets an environment variable's value from environ. The returned buffer
must not be modified or freed.
--*/
char *MiscGetenv(const char *name)
{
    int i, length;
    char *equals;
    char *pRet = NULL;
    CPalThread * pthrCurrent = InternalGetCurrentThread();

    InternalEnterCriticalSection(pthrCurrent, &gcsEnvironment);

    if (palEnvironment)
    {
        for(i = 0; palEnvironment[i] != NULL; i++)
        {
            if (compare(name, palEnvironment[i], &length) == 0)
            {
                equals = palEnvironment[i] + length;
                if (*equals == '\0')
                {
                    pRet = (char *) "";
                    goto done;
                }
                else if (*equals == '=')
                {
                    pRet = equals + 1;
                    goto done;
                }
            }
        }
    }
done:
    InternalLeaveCriticalSection(pthrCurrent, &gcsEnvironment);
    if (pRet == NULL)
    {
        return getenv(name);
    }
    return pRet;
}


/*++
Function:
  MiscPutenv

Sets an environment variable's value by directly modifying palEnvironment.
Returns TRUE if the variable was set, or FALSE if PAL_malloc or realloc
failed or if the given string is malformed.
--*/
BOOL MiscPutenv(const char *string, BOOL deleteIfEmpty)
{
    const char *equals, *existingEquals;
    char *copy = NULL;
    int length;
    int i, j;
    bool fOwningCS = false;
    BOOL result = FALSE;
    CPalThread * pthrCurrent = InternalGetCurrentThread();

    equals = strchr(string, '=');
    if (equals == string || equals == NULL)
    {
        // "=foo" and "foo" have no meaning
        goto done;
    }
    if (equals[1] == '\0' && deleteIfEmpty)
    {
        // "foo=" removes foo from the environment in _putenv() on Windows.
        // The same string can result from a call to SetEnvironmentVariable()
        // with the empty string as the value, but in that case we want to
        // set the variable's value to "". deleteIfEmpty will be FALSE in
        // that case.
        length = strlen(string);
        copy = (char *) InternalMalloc(length);
        if (copy == NULL)
        {
            goto done;
        }
        memcpy(copy, string, length - 1);
        copy[length - 1] = '\0';    // Change '=' to '\0'
        MiscUnsetenv(copy);
        result = TRUE;
    }
    else
    {
        // See if we are replacing an item or adding one.

        // Make our copy up front, since we'll use it either way.
        copy = InternalStrdup(string);
        if (copy == NULL)
        {
            goto done;
        }

        length = equals - string;

        InternalEnterCriticalSection(pthrCurrent, &gcsEnvironment);
        fOwningCS = true;

        for(i = 0; palEnvironment[i] != NULL; i++)
        {
            existingEquals = strchr(palEnvironment[i], '=');
            if (existingEquals == NULL)
            {
                // The PAL screens out malformed strings, but
                // environ comes from the system, so it might
                // have strings without '='. We treat the entire
                // string as a name in that case.
                existingEquals = palEnvironment[i] + strlen(palEnvironment[i]);
            }
            if (existingEquals - palEnvironment[i] == length)
            {
                if (memcmp(string, palEnvironment[i], length) == 0)
                {
                    // Replace this one. Don't free the original,
                    // though, because there may be outstanding
                    // references to it that were acquired via
                    // getenv. This is an unavoidable memory leak.
                    palEnvironment[i] = copy;

                    // Set 'copy' to NULL so it won't be freed
                    copy = NULL;

                    result = TRUE;
                    break;
                }
            }
        }
        if (palEnvironment[i] == NULL)
        {
            static BOOL sAllocatedEnviron = FALSE;
            // Add a new environment variable.
            // We'd like to realloc palEnvironment, but we can't do that the
            // first time through.
            char **newEnviron = NULL;

            if (sAllocatedEnviron) {
                if (NULL == (newEnviron =
                        (char **)InternalRealloc(palEnvironment, (i + 2) * sizeof(char *))))
                {
                    goto done;
                }
            }
            else
            {
                // Allocate palEnvironment ourselves so we can realloc it later.
                newEnviron = (char **)InternalMalloc((i + 2) * sizeof(char *));
                if (newEnviron == NULL)
                {
                    goto done;
                }
                for(j = 0; palEnvironment[j] != NULL; j++)
                {
                    newEnviron[j] = palEnvironment[j];
                }
                sAllocatedEnviron = TRUE;
            }
            palEnvironment = newEnviron;
            MiscSetEnvArray();
            palEnvironment[i] = copy;
            palEnvironment[i + 1] = NULL;

            // Set 'copy' to NULL so it won't be freed
            copy = NULL;

            result = TRUE;
        }
    }
done:

    if (fOwningCS)
    {
        InternalLeaveCriticalSection(pthrCurrent, &gcsEnvironment);
    }
    if (NULL != copy)
    {
        InternalFree(copy);
    }
    return result;
}

/*++
Function:
  MiscUnsetenv

Removes a variable from the environment. Does nothing if the variable
does not exist in the environment.
--*/
void MiscUnsetenv(const char *name)
{
    const char *equals;
    int length;
    int i, j;
    CPalThread * pthrCurrent = InternalGetCurrentThread();

    length = strlen(name);

    InternalEnterCriticalSection(pthrCurrent, &gcsEnvironment);
    for(i = 0; palEnvironment[i] != NULL; i++)
    {
        equals = strchr(palEnvironment[i], '=');
        if (equals == NULL)
        {
            equals = palEnvironment[i] + strlen(palEnvironment[i]);
        }
        if (equals - palEnvironment[i] == length)
        {
            if (memcmp(name, palEnvironment[i], length) == 0)
            {
                // Remove this one. Don't free it, though, since
                // there might be oustanding references to it that
                // were acquired via getenv. This is an
                // unavoidable memory leak.
                for(j = i + 1; palEnvironment[j] != NULL; j++) { }
                // i is now the one we want to remove. j is the
                // last index in palEnvironment, which is NULL.

                // Shift palEnvironment down by the difference between i and j.
                memmove(palEnvironment + i, palEnvironment + i + 1, (j - i) * sizeof(char *));
            }
        }
    }
    InternalLeaveCriticalSection(pthrCurrent, &gcsEnvironment);
}
