| // |
| // 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(palEnvironment[i], name, &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); |
| } |