-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Implement Environment.GetEnvironmentVariables for Apple platforms using official API on iOS/tvOS/MacCatalyst #58161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
7d697e7
33e5b5d
5eb8dc2
3346036
0e520ab
d9f527b
d55d4ad
30b72b4
8759796
64744d6
316b2f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <stdlib.h> | ||
| #include <string.h> | ||
| #if HAVE_NSGETENVIRON | ||
| #include <crt_externs.h> | ||
| #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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(int32_t *releaseMemory); | ||
|
|
||
| PALEXPORT void SystemNative_FreeEnviron(char** environ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 <Foundation/Foundation.h> | ||
| #include <CoreFoundation/CoreFoundation.h> | ||
| #include <objc/runtime.h> | ||
| #include <objc/message.h> | ||
|
|
||
| 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); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<string, string> GetSystemEnvironmentVariables() | |
| { | ||
| var results = new Dictionary<string, string>(); | ||
|
|
||
| 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. | ||
danmoseley marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // 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; | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.