Skip to content

Commit

Permalink
Copy CoreFX environment variable code (dotnet/coreclr#8405)
Browse files Browse the repository at this point in the history
Tweak the core code to match up with what we had done in CoreFX and expose
so that we can have a single source of environment truth. This is
particularly important for Unix as we use a local copy of the state.

Commit migrated from dotnet/coreclr@e06afa7
  • Loading branch information
JeremyKuhne committed Dec 5, 2016
1 parent acee92f commit c552b65
Show file tree
Hide file tree
Showing 6 changed files with 498 additions and 371 deletions.
6 changes: 6 additions & 0 deletions src/coreclr/src/mscorlib/model.xml
Expand Up @@ -2015,6 +2015,12 @@
<Member Name="Exit(System.Int32)" />
<Member Name="FailFast(System.String,System.Exception)" />
<Member Name="GetCommandLineArgs" />
<Member Name="GetEnvironmentVariable(System.String)" />
<Member Name="GetEnvironmentVariable(System.String,System.EnvironmentVariableTarget)" />
<Member Name="GetEnvironmentVariables" />
<Member Name="GetEnvironmentVariables(System.EnvironmentVariableTarget)" />
<Member Name="SetEnvironmentVariable(System.String,System.String)" />
<Member Name="SetEnvironmentVariable(System.String,System.String,System.EnvironmentVariableTarget)" />
</Type>
<Type Name="System.EventArgs">
<Member MemberType="Field" Name="Empty" />
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/src/mscorlib/mscorlib.shared.sources.props
Expand Up @@ -768,12 +768,14 @@
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<SafehandleSources Include="$(CoreFxSourcesRoot)\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs" />
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.Unix.cs" />
<SystemSources Include="$(BclSourcesRoot)\System\Environment.Unix.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' != 'true'">
<SafehandleSources Include="$(CoreFxSourcesRoot)\Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs" />
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.Win32.cs" />
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStreamCompletionSource.Win32.cs" />
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\Win32Marshal.cs" />
<SystemSources Include="$(BclSourcesRoot)\System\Environment.Win32.cs" />
</ItemGroup>
<ItemGroup>
<IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.cs" />
Expand Down
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections;

namespace Internal.Runtime.Augments
{
Expand All @@ -17,5 +18,11 @@ public static class EnvironmentAugments
public static bool HasShutdownStarted => Environment.HasShutdownStarted;
public static string StackTrace => Environment.StackTrace;
public static int TickCount => Environment.TickCount;
public static string GetEnvironmentVariable(string variable) => Environment.GetEnvironmentVariable(variable);
public static string GetEnvironmentVariable(string variable, EnvironmentVariableTarget target) => Environment.GetEnvironmentVariable(variable, target);
public static IDictionary GetEnvironmentVariables() => Environment.GetEnvironmentVariables();
public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget target) => Environment.GetEnvironmentVariables(target);
public static void SetEnvironmentVariable(string variable, string value) => Environment.SetEnvironmentVariable(variable, value);
public static void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target) => Environment.SetEnvironmentVariable(variable, value, target);
}
}
101 changes: 101 additions & 0 deletions src/coreclr/src/mscorlib/src/System/Environment.Unix.cs
@@ -0,0 +1,101 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Collections.Generic;

namespace System
{
public static partial class Environment
{
private static readonly unsafe Lazy<Dictionary<string, string>> s_environ = new Lazy<Dictionary<string, string>>(() =>
{
// We cache on Unix as using the block isn't thread safe
return GetRawEnvironmentVariables();
});

private static string GetEnvironmentVariableCore(string variable)
{
// Ensure variable doesn't include a null char
int nullEnd = variable.IndexOf('\0');
if (nullEnd != -1)
{
variable = variable.Substring(0, nullEnd);
}

// Get the value of the variable
lock (s_environ)
{
string value;
return s_environ.Value.TryGetValue(variable, out value) ? value : null;
}
}

private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target)
{
return target == EnvironmentVariableTarget.Process ?
GetEnvironmentVariableCore(variable) :
null;
}

private static IDictionary GetEnvironmentVariablesCore()
{
lock (s_environ)
{
return new Dictionary<string, string>(s_environ.Value);
}
}

private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target)
{
return target == EnvironmentVariableTarget.Process ?
GetEnvironmentVariablesCore() :
new Dictionary<string, string>();
}

private static void SetEnvironmentVariableCore(string variable, string value)
{
int nullEnd;

// Ensure variable doesn't include a null char
nullEnd = variable.IndexOf('\0');
if (nullEnd != -1)
{
variable = variable.Substring(0, nullEnd);
}

// Ensure value doesn't include a null char
if (value != null)
{
nullEnd = value.IndexOf('\0');
if (nullEnd != -1)
{
value = value.Substring(0, nullEnd);
}
}

lock (s_environ)
{
// Remove the entry if the value is null, otherwise add/overwrite it
if (value == null)
{
s_environ.Value.Remove(variable);
}
else
{
s_environ.Value[variable] = value;
}
}
}

private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target)
{
if (target == EnvironmentVariableTarget.Process)
{
SetEnvironmentVariableCore(variable, value);
}
// other targets ignored
}
}
}
226 changes: 226 additions & 0 deletions src/coreclr/src/mscorlib/src/System/Environment.Win32.cs
@@ -0,0 +1,226 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Win32;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace System
{
public static partial class Environment
{
private static string GetEnvironmentVariableCore(string variable)
{
if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode())
{
// Environment variable accessors are not approved modern API.
// Behave as if the variable was not found in this case.
return null;
}

StringBuilder sb = StringBuilderCache.Acquire(128); // A somewhat reasonable default size
int requiredSize = Win32Native.GetEnvironmentVariable(variable, sb, sb.Capacity);

if (requiredSize == 0 && Marshal.GetLastWin32Error() == Win32Native.ERROR_ENVVAR_NOT_FOUND)
{
StringBuilderCache.Release(sb);
return null;
}

while (requiredSize > sb.Capacity)
{
sb.Capacity = requiredSize;
sb.Length = 0;
requiredSize = Win32Native.GetEnvironmentVariable(variable, sb, sb.Capacity);
}

return StringBuilderCache.GetStringAndRelease(sb);
}

private static string GetEnvironmentVariableCore(string variable, EnvironmentVariableTarget target)
{
if (target == EnvironmentVariableTarget.Process)
return GetEnvironmentVariableCore(variable);

#if FEATURE_WIN32_REGISTRY
RegistryKey baseKey;
string keyName;

if (target == EnvironmentVariableTarget.Machine)
{
baseKey = Registry.LocalMachine;
keyName = @"System\CurrentControlSet\Control\Session Manager\Environment";
}
else if (target == EnvironmentVariableTarget.User)
{
Debug.Assert(target == EnvironmentVariableTarget.User);
baseKey = Registry.CurrentUser;
keyName = "Environment";
}
else
{
throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target));
}

using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: false))
{
return environmentKey?.GetValue(variable) as string;
}
#else
throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target));
#endif
}

private static IDictionary GetEnvironmentVariablesCore()
{
if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode())
{
// Environment variable accessors are not approved modern API.
// Behave as if no environment variables are defined in this case.
return new Hashtable(0);
}

return GetRawEnvironmentVariables();
}

private static IDictionary GetEnvironmentVariablesCore(EnvironmentVariableTarget target)
{
if (target == EnvironmentVariableTarget.Process)
return GetEnvironmentVariablesCore();

#if FEATURE_WIN32_REGISTRY
RegistryKey baseKey;
string keyName;
if (target == EnvironmentVariableTarget.Machine)
{
baseKey = Registry.LocalMachine;
keyName = @"System\CurrentControlSet\Control\Session Manager\Environment";
}
else if (target == EnvironmentVariableTarget.User)
{
Debug.Assert(target == EnvironmentVariableTarget.User);
baseKey = Registry.CurrentUser;
keyName = @"Environment";
}
else
{
throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target));
}

using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: false))
{
var table = new Dictionary<string, string>();
if (environmentKey != null)
{
foreach (string name in environmentKey.GetValueNames())
{
table.Add(name, environmentKey.GetValue(name, "").ToString());
}
}
return table;
}
#endif // FEATURE_WIN32_REGISTRY

throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target));
}

private static void SetEnvironmentVariableCore(string variable, string value)
{
// explicitly null out value if is the empty string.
if (string.IsNullOrEmpty(value) || value[0] == '\0')
value = null;

if (AppDomain.IsAppXModel() && !AppDomain.IsAppXDesignMode())
{
// Environment variable accessors are not approved modern API.
// so we throw PlatformNotSupportedException.
throw new PlatformNotSupportedException();
}

if (!Win32Native.SetEnvironmentVariable(variable, value))
{
int errorCode = Marshal.GetLastWin32Error();

switch (errorCode)
{
case Win32Native.ERROR_ENVVAR_NOT_FOUND:
// Allow user to try to clear a environment variable
return;
case Win32Native.ERROR_FILENAME_EXCED_RANGE:
// The error message from Win32 is "The filename or extension is too long",
// which is not accurate.
throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"));
default:
throw new ArgumentException(Win32Native.GetMessage(errorCode));
}
}
}

private static void SetEnvironmentVariableCore(string variable, string value, EnvironmentVariableTarget target)
{
if (target == EnvironmentVariableTarget.Process)
{
SetEnvironmentVariableCore(variable, value);
return;
}

// explicitly null out value if is the empty string.
if (string.IsNullOrEmpty(value) || value[0] == '\0')
value = null;

#if FEATURE_WIN32_REGISTRY
RegistryKey baseKey;
string keyName;

if (target == EnvironmentVariableTarget.Machine)
{
baseKey = Registry.LocalMachine;
keyName = @"System\CurrentControlSet\Control\Session Manager\Environment";
}
else if (target == EnvironmentVariableTarget.User)
{
Debug.Assert(target == EnvironmentVariableTarget.User);

// User-wide environment variables stored in the registry are limited to 255 chars for the environment variable name.
const int MaxUserEnvVariableLength = 255;
if (variable.Length >= MaxUserEnvVariableLength)
{
throw new ArgumentException(GetResourceString("Argument_LongEnvVarValue"), nameof(variable));
}

baseKey = Registry.CurrentUser;
keyName = "Environment";
}
else
{
throw new ArgumentException(GetResourceString("Arg_EnumIllegalVal", (int)target));
}

using (RegistryKey environmentKey = baseKey.OpenSubKey(keyName, writable: true))
{
if (environmentKey != null)
{
if (value == null)
{
environmentKey.DeleteValue(variable, throwOnMissingValue: false);
}
else
{
environmentKey.SetValue(variable, value);
}
}
}

// send a WM_SETTINGCHANGE message to all windows
IntPtr r = Win32Native.SendMessageTimeout(new IntPtr(Win32Native.HWND_BROADCAST), Win32Native.WM_SETTINGCHANGE, IntPtr.Zero, "Environment", 0, 1000, IntPtr.Zero);
if (r == IntPtr.Zero) Debug.Assert(false, "SetEnvironmentVariable failed: " + Marshal.GetLastWin32Error());

#else // FEATURE_WIN32_REGISTRY
throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)target));
#endif
}
}
}

0 comments on commit c552b65

Please sign in to comment.