diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetEnv.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetEnv.cs new file mode 100644 index 0000000000000..d83d6700b5162 --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetEnv.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal unsafe partial class Sys + { + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetEnv")] + internal static extern unsafe IntPtr GetEnv(string name); + } +} \ No newline at end of file diff --git a/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs new file mode 100644 index 0000000000000..abe8ff0e1916e --- /dev/null +++ b/src/libraries/Common/src/Interop/Unix/System.Native/Interop.GetEnviron.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal unsafe partial class Sys + { + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_GetEnviron")] + internal static extern unsafe IntPtr GetEnviron(); + + [DllImport(Interop.Libraries.SystemNative, EntryPoint = "SystemNative_FreeEnviron")] + internal static extern unsafe void FreeEnviron(IntPtr environ); + } +} \ No newline at end of file diff --git a/src/libraries/Native/Unix/System.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Native/CMakeLists.txt index a28952750b3ef..188ca5fc6bde8 100644 --- a/src/libraries/Native/Unix/System.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Native/CMakeLists.txt @@ -40,6 +40,12 @@ else() list (APPEND NATIVE_SOURCES pal_autoreleasepool.c) endif() +if (CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS) + list (APPEND NATIVE_SOURCES pal_environment.m) +else() + list (APPEND NATIVE_SOURCES pal_environment.c) +endif() + if (CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVOS) set(NATIVE_SOURCES ${NATIVE_SOURCES} pal_log.m diff --git a/src/libraries/Native/Unix/System.Native/entrypoints.c b/src/libraries/Native/Unix/System.Native/entrypoints.c index c8b678ec08340..c5f15e1e9d1d6 100644 --- a/src/libraries/Native/Unix/System.Native/entrypoints.c +++ b/src/libraries/Native/Unix/System.Native/entrypoints.c @@ -7,6 +7,7 @@ #include "pal_autoreleasepool.h" #include "pal_console.h" #include "pal_datetime.h" +#include "pal_environment.h" #include "pal_errno.h" #include "pal_interfaceaddresses.h" #include "pal_io.h" @@ -257,6 +258,9 @@ static const Entry s_sysNative[] = DllImportEntry(SystemNative_SetPosixSignalHandler) DllImportEntry(SystemNative_GetPlatformSignalNumber) DllImportEntry(SystemNative_GetGroups) + DllImportEntry(SystemNative_GetEnv) + DllImportEntry(SystemNative_GetEnviron) + DllImportEntry(SystemNative_FreeEnviron) }; EXTERN_C const void* SystemResolveDllImport(const char* name); diff --git a/src/libraries/Native/Unix/System.Native/pal_environment.c b/src/libraries/Native/Unix/System.Native/pal_environment.c new file mode 100644 index 0000000000000..fd9e68f023028 --- /dev/null +++ b/src/libraries/Native/Unix/System.Native/pal_environment.c @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_config.h" +#include "pal_environment.h" + +#include +#include +#if HAVE_NSGETENVIRON +#include +#endif + +char* SystemNative_GetEnv(const char* variable) +{ + return getenv(variable); +} + +char** SystemNative_GetEnviron() +{ +#if HAVE_NSGETENVIRON + return *(_NSGetEnviron()); +#else + extern char **environ; + return environ; +#endif +} + +void SystemNative_FreeEnviron(char** environ) +{ + // no op + (void)environ; +} diff --git a/src/libraries/Native/Unix/System.Native/pal_environment.h b/src/libraries/Native/Unix/System.Native/pal_environment.h new file mode 100644 index 0000000000000..dee7a10f3aecd --- /dev/null +++ b/src/libraries/Native/Unix/System.Native/pal_environment.h @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_compiler.h" +#include "pal_types.h" + +PALEXPORT char* SystemNative_GetEnv(const char* variable); + +PALEXPORT char** SystemNative_GetEnviron(void); + +PALEXPORT void SystemNative_FreeEnviron(char** environ); diff --git a/src/libraries/Native/Unix/System.Native/pal_environment.m b/src/libraries/Native/Unix/System.Native/pal_environment.m new file mode 100644 index 0000000000000..ea4edc6ccd319 --- /dev/null +++ b/src/libraries/Native/Unix/System.Native/pal_environment.m @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "pal_config.h" +#include "pal_environment.h" + +#include +#include +#include +#include + +char* SystemNative_GetEnv(const char* variable) +{ + return getenv(variable); +} + +static char *empty_key_value_pair = "="; + +static void get_environ_helper(const void *key, const void *value, void *context) +{ + char ***temp_environ_ptr = (char***)context; + const char *utf8_key = [(NSString *)key UTF8String]; + const char *utf8_value = [(NSString *)value UTF8String]; + int utf8_key_length = strlen(utf8_key); + int utf8_value_length = strlen(utf8_value); + char *key_value_pair; + + key_value_pair = malloc(utf8_key_length + utf8_value_length + 2); + if (key_value_pair != NULL) + { + strcpy(key_value_pair, utf8_key); + key_value_pair[utf8_key_length] = '='; + strcpy(key_value_pair + utf8_key_length + 1, utf8_value); + } + else + { + // In case of failed allocation add pointer to preallocated entry. This is + // ignored on the managed side and skipped over in SystemNative_FreeEnviron. + key_value_pair = empty_key_value_pair; + } + + **temp_environ_ptr = key_value_pair; + (*temp_environ_ptr)++; +} + +char** SystemNative_GetEnviron() +{ + char **temp_environ; + char **temp_environ_ptr; + + CFDictionaryRef environment = (CFDictionaryRef)[[NSProcessInfo processInfo] environment]; + int count = CFDictionaryGetCount(environment); + temp_environ = (char **)malloc((count + 1) * sizeof(char *)); + if (temp_environ != NULL) + { + temp_environ_ptr = temp_environ; + CFDictionaryApplyFunction(environment, get_environ_helper, &temp_environ_ptr); + *temp_environ_ptr = NULL; + } + + return temp_environ; +} + +void SystemNative_FreeEnviron(char** environ) +{ + if (environ != NULL) + { + for (char** environ_ptr = environ; *environ_ptr != NULL; environ_ptr++) + { + if (*environ_ptr != empty_key_value_pair) + { + free(*environ_ptr); + } + } + + free(environ); + } +} diff --git a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj index 1878cb4ed7c05..1b57aa747896e 100644 --- a/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -265,6 +265,12 @@ + + Common\Interop\Unix\System.Native\Interop.GetEnv.cs + + + Common\Interop\Unix\System.Native\Interop.GetEnviron.cs + diff --git a/src/mono/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs index 1b75ae0abbc2b..5858d78f4c972 100644 --- a/src/mono/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Environment.Unix.Mono.cs @@ -5,8 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; -using Mono; namespace System { @@ -20,7 +20,7 @@ public partial class Environment if (s_environment == null) { - return InternalGetEnvironmentVariable(variable); + return Marshal.PtrToStringAnsi(Interop.Sys.GetEnv(variable)); } variable = TrimStringOnFirstZero(variable); @@ -31,23 +31,15 @@ public partial class Environment } } - private static string InternalGetEnvironmentVariable(string name) - { - using (SafeStringMarshal handle = RuntimeMarshal.MarshalString(name)) - { - return internalGetEnvironmentVariable_native(handle.Value); - } - } - private static unsafe void SetEnvironmentVariableCore(string variable, string? value) { Debug.Assert(variable != null); EnsureEnvironmentCached(); + variable = TrimStringOnFirstZero(variable); + value = value == null ? null : TrimStringOnFirstZero(value); lock (s_environment!) { - variable = TrimStringOnFirstZero(variable); - value = value == null ? null : TrimStringOnFirstZero(value); if (string.IsNullOrEmpty(value)) { s_environment.Remove(variable); @@ -97,22 +89,75 @@ private static Dictionary GetSystemEnvironmentVariables() { var results = new Dictionary(); - foreach (string name in GetEnvironmentVariableNames()) + IntPtr block = Interop.Sys.GetEnviron(); + if (block != IntPtr.Zero) { - if (name != null) + try + { + IntPtr blockIterator = block; + + // Per man page, environment variables come back as an array of pointers to strings + // Parse each pointer of strings individually + while (ParseEntry(blockIterator, out string? key, out string? value)) + { + if (key != null && value != null) + { + try + { + // Add may throw if the environment block was corrupted leading to duplicate entries. + // We allow such throws and eat them (rather than proactively checking for duplication) + // to provide a non-fatal notification about the corruption. + results.Add(key, value); + } + catch (ArgumentException) { } + } + + // Increment to next environment variable entry + blockIterator += IntPtr.Size; + } + } + finally { - results.Add(name, InternalGetEnvironmentVariable(name)); + Interop.Sys.FreeEnviron(block); } } return results; - } - - - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern string internalGetEnvironmentVariable_native(IntPtr variable); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - private static extern string[] GetEnvironmentVariableNames(); + // Use a local, unsafe function since we cannot use `yield return` inside of an `unsafe` block + static unsafe bool ParseEntry(IntPtr current, out string? key, out string? value) + { + // Setup + key = null; + value = null; + + // Point to current entry + byte* entry = *(byte**)current; + + // Per man page, "The last pointer in this array has the value NULL" + // Therefore, if entry is null then we're at the end and can bail + if (entry == null) + return false; + + // Parse each byte of the entry until we hit either the separator '=' or '\0'. + // This finds the split point for creating key/value strings below. + // On some old OS, the environment block can be corrupted. + // Some will not have '=', so we need to check for '\0'. + byte* splitpoint = entry; + while (*splitpoint != '=' && *splitpoint != '\0') + splitpoint++; + + // Skip over entries starting with '=' and entries with no value (just a null-terminating char '\0') + if (splitpoint == entry || *splitpoint == '\0') + return true; + + // The key is the bytes from start (0) until our splitpoint + key = new string((sbyte*)entry, 0, checked((int)(splitpoint - entry))); + // The value is the rest of the bytes starting after the splitpoint + value = new string((sbyte*)(splitpoint + 1)); + + return true; + } + } } } diff --git a/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs b/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs index 1a95bdc408f1c..372805e70086e 100644 --- a/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs +++ b/src/mono/System.Private.CoreLib/src/System/Environment.iOS.cs @@ -84,7 +84,7 @@ private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOptio return Interop.Sys.SearchPath(NSSearchPathDirectory.NSCachesDirectory); case SpecialFolder.UserProfile: - return InternalGetEnvironmentVariable("HOME"); + return GetEnvironmentVariable("HOME"); case SpecialFolder.CommonApplicationData: return "/usr/share"; diff --git a/src/mono/mono/metadata/icall-def-netcore.h b/src/mono/mono/metadata/icall-def-netcore.h index 8e026693a109e..02dfa0ed3cbe0 100644 --- a/src/mono/mono/metadata/icall-def-netcore.h +++ b/src/mono/mono/metadata/icall-def-netcore.h @@ -98,12 +98,10 @@ ICALL_TYPE(ENV, "System.Environment", ENV_1) NOHANDLES(ICALL(ENV_1, "Exit", ves_icall_System_Environment_Exit)) HANDLES(ENV_1a, "FailFast", ves_icall_System_Environment_FailFast, void, 3, (MonoString, MonoException, MonoString)) HANDLES(ENV_2, "GetCommandLineArgs", ves_icall_System_Environment_GetCommandLineArgs, MonoArray, 0, ()) -HANDLES(ENV_3, "GetEnvironmentVariableNames", ves_icall_System_Environment_GetEnvironmentVariableNames, MonoArray, 0, ()) NOHANDLES(ICALL(ENV_4, "GetProcessorCount", ves_icall_System_Environment_get_ProcessorCount)) NOHANDLES(ICALL(ENV_9, "get_ExitCode", mono_environment_exitcode_get)) NOHANDLES(ICALL(ENV_15, "get_TickCount", ves_icall_System_Environment_get_TickCount)) NOHANDLES(ICALL(ENV_15a, "get_TickCount64", ves_icall_System_Environment_get_TickCount64)) -HANDLES(ENV_17, "internalGetEnvironmentVariable_native", ves_icall_System_Environment_GetEnvironmentVariable_native, MonoString, 1, (const_char_ptr)) NOHANDLES(ICALL(ENV_20, "set_ExitCode", mono_environment_exitcode_set)) ICALL_TYPE(GC, "System.GC", GC_13) diff --git a/src/mono/mono/metadata/icall-internals.h b/src/mono/mono/metadata/icall-internals.h index 082a57b64cf80..45a51a2224b17 100644 --- a/src/mono/mono/metadata/icall-internals.h +++ b/src/mono/mono/metadata/icall-internals.h @@ -35,12 +35,6 @@ mono_icall_get_new_line (MonoError *error); MonoBoolean mono_icall_is_64bit_os (void); -MonoArrayHandle -mono_icall_get_environment_variable_names (MonoError *error); - -void -mono_icall_set_environment_variable (MonoString *name, MonoString *value); - MonoStringHandle mono_icall_get_windows_folder_path (int folder, MonoError *error); diff --git a/src/mono/mono/metadata/icall-windows.c b/src/mono/mono/metadata/icall-windows.c index e288e2d244717..0b5adb2488679 100644 --- a/src/mono/mono/metadata/icall-windows.c +++ b/src/mono/mono/metadata/icall-windows.c @@ -88,64 +88,6 @@ mono_icall_is_64bit_os (void) #endif } -MonoArrayHandle -mono_icall_get_environment_variable_names (MonoError *error) -{ - MonoArrayHandle names; - MonoStringHandle str; - WCHAR* env_strings; - WCHAR* env_string; - WCHAR* equal_str; - int n = 0; - - env_strings = GetEnvironmentStrings(); - - if (env_strings) { - env_string = env_strings; - while (*env_string != '\0') { - /* weird case that MS seems to skip */ - if (*env_string != '=') - n++; - while (*env_string != '\0') - env_string++; - env_string++; - } - } - - names = mono_array_new_handle (mono_defaults.string_class, n, error); - return_val_if_nok (error, NULL_HANDLE_ARRAY); - - if (env_strings) { - n = 0; - str = MONO_HANDLE_NEW (MonoString, NULL); - env_string = env_strings; - while (*env_string != '\0') { - /* weird case that MS seems to skip */ - if (*env_string != '=') { - equal_str = wcschr(env_string, '='); - g_assert(equal_str); - MonoString *s = mono_string_new_utf16_checked (env_string, (gint32)(equal_str - env_string), error); - goto_if_nok (error, cleanup); - MONO_HANDLE_ASSIGN_RAW (str, s); - - mono_array_handle_setref (names, n, str); - n++; - } - while (*env_string != '\0') - env_string++; - env_string++; - } - - } - -cleanup: - if (env_strings) - FreeEnvironmentStrings (env_strings); - if (!is_ok (error)) - return NULL_HANDLE_ARRAY; - return names; -} - #if HAVE_API_SUPPORT_WIN32_SH_GET_FOLDER_PATH #include MonoStringHandle diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 7dd21b9b538c6..e471252ea6195 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -6519,52 +6519,6 @@ ves_icall_System_Environment_GetIs64BitOperatingSystem (void) return mono_icall_is_64bit_os (); } -MonoStringHandle -ves_icall_System_Environment_GetEnvironmentVariable_native (const gchar *utf8_name, MonoError *error) -{ - gchar *value; - - if (utf8_name == NULL) - return NULL_HANDLE_STRING; - - value = g_getenv (utf8_name); - - if (value == 0) - return NULL_HANDLE_STRING; - - MonoStringHandle res = mono_string_new_handle (value, error); - g_free (value); - return res; -} - -/* - * There is no standard way to get at environ. - */ -#ifndef _MSC_VER -#ifndef __MINGW32_VERSION -#if defined(__APPLE__) -#if defined (TARGET_OSX) -/* Apple defines this in crt_externs.h but doesn't provide that header for - * arm-apple-darwin9. We'll manually define the symbol on Apple as it does - * in fact exist on all implementations (so far) - */ -G_BEGIN_DECLS -gchar ***_NSGetEnviron(void); -G_END_DECLS -#define environ (*_NSGetEnviron()) -#else -static char *mono_environ[1] = { NULL }; -#define environ mono_environ -#endif /* defined (TARGET_OSX) */ -#else -G_BEGIN_DECLS -extern -char **environ; -G_END_DECLS -#endif -#endif -#endif - MonoArrayHandle ves_icall_System_Environment_GetCommandLineArgs (MonoError *error) { @@ -6572,51 +6526,6 @@ ves_icall_System_Environment_GetCommandLineArgs (MonoError *error) return result; } -#ifndef HOST_WIN32 -static MonoArrayHandle -mono_icall_get_environment_variable_names (MonoError *error) -{ - MonoArrayHandle names; - MonoStringHandle str; - gchar **e, **parts; - int n; - - n = 0; - for (e = environ; *e != 0; ++ e) - ++ n; - - names = mono_array_new_handle (mono_defaults.string_class, n, error); - return_val_if_nok (error, NULL_HANDLE_ARRAY); - - str = MONO_HANDLE_NEW (MonoString, NULL); - n = 0; - for (e = environ; *e != 0; ++ e) { - parts = g_strsplit (*e, "=", 2); - if (*parts != 0) { - MonoString *s = mono_string_new_checked (*parts, error); - MONO_HANDLE_ASSIGN_RAW (str, s); - if (!is_ok (error)) { - g_strfreev (parts); - return NULL_HANDLE_ARRAY; - } - mono_array_handle_setref (names, n, str); - } - - g_strfreev (parts); - - ++ n; - } - - return names; -} -#endif /* !HOST_WIN32 */ - -MonoArrayHandle -ves_icall_System_Environment_GetEnvironmentVariableNames (MonoError *error) -{ - return mono_icall_get_environment_variable_names (error); -} - void ves_icall_System_Environment_Exit (int result) {