Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal static partial class Interop
internal static partial class Sys
{
internal static unsafe int ForkAndExecProcess(
string filename, string[] argv, IDictionary<string, string?> env, string? cwd,
string filename, string[] argv, IDictionary<string, string?>? env, string? cwd,
bool setUser, uint userId, uint groupId, uint[]? groups,
out int lpChildPid, SafeFileHandle? stdinFd, SafeFileHandle? stdoutFd, SafeFileHandle? stderrFd,
ProcessStartInfo startInfo, SafeHandle[]? inheritedHandles = null)
Expand Down Expand Up @@ -67,7 +67,10 @@ internal static unsafe int ForkAndExecProcess(
}

AllocArgvArray(argv, ref argvPtr);
AllocEnvpArray(env, ref envpPtr);
if (env is not null)
{
AllocEnvpArray(env, ref envpPtr);
}
fixed (uint* pGroups = groups)
fixed (int* pInheritedFds = inheritedFds)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
using System.Runtime.Versioning;
Expand Down Expand Up @@ -218,7 +219,7 @@ internal static SafeProcessHandle StartCore(ProcessStartInfo startInfo, SafeFile
string filename;
string[] argv;

IDictionary<string, string?> env = startInfo.Environment;
IDictionary<string, string?>? env = GetEnvironmentVariables(startInfo);
string? cwd = !string.IsNullOrWhiteSpace(startInfo.WorkingDirectory) ? startInfo.WorkingDirectory : null;

Comment thread
adamsitnik marked this conversation as resolved.
bool setCredentials = !string.IsNullOrEmpty(startInfo.UserName);
Expand Down Expand Up @@ -356,7 +357,7 @@ internal static unsafe SafeProcessHandle StartWithCallback(ProcessStartInfo star
private static SafeProcessHandle StartWithShellExecute(ProcessStartInfo startInfo, SafeFileHandle? stdinHandle, SafeFileHandle? stdoutHandle,
SafeFileHandle? stderrHandle, out ProcessWaitState.Holder? waitStateHolder)
{
IDictionary<string, string?> env = startInfo.Environment;
IDictionary<string, string?>? env = GetEnvironmentVariables(startInfo);
string? cwd = !string.IsNullOrWhiteSpace(startInfo.WorkingDirectory) ? startInfo.WorkingDirectory : null;

bool setCredentials = !string.IsNullOrEmpty(startInfo.UserName);
Expand Down Expand Up @@ -425,9 +426,15 @@ private static SafeProcessHandle StartWithShellExecute(ProcessStartInfo startInf
private static bool UsesTerminal(SafeFileHandle? stdinHandle, SafeFileHandle? stdoutHandle, SafeFileHandle? stderrHandle)
=> ProcessUtils.IsTerminal(stdinHandle) || ProcessUtils.IsTerminal(stdoutHandle) || ProcessUtils.IsTerminal(stderrHandle);

private static IDictionary<string, string?>? GetEnvironmentVariables(ProcessStartInfo startInfo)
=> GetHasEnvironmentVariablesBeenModified(null) ? startInfo.Environment : startInfo._environmentVariables;

[UnsafeAccessor(UnsafeAccessorKind.StaticMethod, Name = "get_HasEnvironmentVariablesBeenModified")]
private static extern bool GetHasEnvironmentVariablesBeenModified([UnsafeAccessorType("System.Environment, System.Private.CoreLib")] object? _);

private static SafeProcessHandle ForkAndExecProcess(
ProcessStartInfo startInfo, string? resolvedFilename, string[] argv,
IDictionary<string, string?> env, string? cwd, bool setCredentials, uint userId,
IDictionary<string, string?>? env, string? cwd, bool setCredentials, uint userId,
uint groupId, uint[]? groups,
SafeFileHandle? stdinHandle, SafeFileHandle? stdoutHandle, SafeFileHandle? stderrHandle,
bool usesTerminal, SafeHandle[]? inheritedHandles, out ProcessWaitState.Holder? waitStateHolder, bool throwOnNoExec = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,46 @@ public void TestEnvironmentOfChildProcess()
}
}

[ConditionalTheory(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
[InlineData("not_null", true)]
[InlineData("not_null", false)]
[InlineData("null", false)]
[InlineData("null", true)]
Comment thread
adamsitnik marked this conversation as resolved.
public void SeesEnvironmentVariableSetBeforeStart(string value, bool modifyParent)
Comment on lines +283 to +288
{
const string Name = "TestEnvironmentOfChildProcess_ParentSetBeforeStart";

// We run a dedicated process to ensure that each test has its own copy of the static shared state.
using Process isolation = CreateProcess((expectedValue, modifyParentStr) =>
Comment thread
adamsitnik marked this conversation as resolved.
{
using Process process = CreateProcess(static (value) =>
{
Assert.Equal(value == "null" ? null : value, Environment.GetEnvironmentVariable(Name));

return RemoteExecutor.SuccessExitCode;
}, expectedValue);

if (bool.Parse(modifyParentStr))
{
Environment.SetEnvironmentVariable(Name, expectedValue == "null" ? null : expectedValue);
}
else
{
process.StartInfo.Environment[Name] = expectedValue == "null" ? null : expectedValue;
}

process.Start();
Assert.True(process.WaitForExit(WaitInMS));
Assert.Equal(RemoteExecutor.SuccessExitCode, process.ExitCode);

return RemoteExecutor.SuccessExitCode;
}, value, modifyParent.ToString());

isolation.Start();
Assert.True(isolation.WaitForExit(WaitInMS));
Assert.Equal(RemoteExecutor.SuccessExitCode, isolation.ExitCode);
}

[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public void EnvironmentNullValue()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<!-- Used by VS4Mac via reflection to symbolize stack traces -->
<method name="GetMethodFromNativeIP" />
</type>
<type fullname="System.Environment">
<!-- Used by System.Diagnostics.Process via UnsafeAccessor -->
<method name="get_HasEnvironmentVariablesBeenModified" />
</type>
<type fullname="System.Reflection.Emit.AssemblyBuilder">
<!-- Used by System.Linq.Expressions via reflection -->
<method name="ForceAllowDynamicCode" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ namespace System
{
public static partial class Environment
{
private static volatile bool s_hasEnvironmentVariablesBeenModified;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to other reviewers: this logic is part of Environment.Unix.cs rather than Environment.Variables.Unix.cs because, the latter is used only for Mono (or at least not for CLR):

<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Variables.Unix.cs" Condition="'$(FeatureCoreCLR)' != 'true'" />

As it seems that for CLR we compile Environment.Variables.Windows.cs on UNIX!!!

<ItemGroup Condition="'$(TargetsWindows)' == 'true' or '$(FeatureCoreCLR)'=='true'">
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetEnvironmentVariable.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.GetEnvironmentVariable.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.GetEnvironmentStrings.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.GetEnvironmentStrings.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FreeEnvironmentStrings.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.FreeEnvironmentStrings.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SetEnvironmentVariable.cs</Link>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Variables.Windows.cs" />
</ItemGroup>


internal static bool HasEnvironmentVariablesBeenModified => s_hasEnvironmentVariablesBeenModified;

static partial void SetHasEnvironmentVariablesBeenModified() => s_hasEnvironmentVariablesBeenModified = true;

public static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints();

public static string MachineName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,12 @@ public static IDictionary GetEnvironmentVariables(EnvironmentVariableTarget targ
return GetEnvironmentVariablesFromRegistry(fromMachine);
}

static partial void SetHasEnvironmentVariablesBeenModified();

public static void SetEnvironmentVariable(string variable, string? value)
{
ValidateVariable(variable);
SetHasEnvironmentVariablesBeenModified();
SetEnvironmentVariableCore(variable, value);
}

Expand Down
7 changes: 4 additions & 3 deletions src/native/libs/System.Native/pal_process.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

#include "pal_config.h"
#include "pal_environment.h"
#include "pal_process.h"
#include "pal_signal.h"
#include "pal_io.h"
Expand Down Expand Up @@ -565,7 +566,7 @@ static int32_t ForkAndExecProcessInternal(
int32_t* inheritedFds, int32_t inheritedFdCount, int32_t startDetached, int32_t applyPDeathSig, int32_t startSuspended)
{
#if HAVE_FORK || defined(TARGET_OSX) || defined(TARGET_MACCATALYST)
assert(NULL != filename && NULL != argv && NULL != envp && NULL != childPid &&
assert(NULL != filename && NULL != argv && NULL != childPid &&
(groupsLength == 0 || groups != NULL) && "null argument.");

#if !HAVE_PR_SET_PDEATHSIG
Expand Down Expand Up @@ -712,7 +713,7 @@ static int32_t ForkAndExecProcessInternal(
}

// Spawn the process
result = posix_spawn(&spawnedPid, filename, &file_actions, &attr, argv, envp);
result = posix_spawn(&spawnedPid, filename, &file_actions, &attr, argv, envp != NULL ? envp : SystemNative_GetEnviron());

posix_spawn_file_actions_destroy(&file_actions);
posix_spawnattr_destroy(&attr);
Expand Down Expand Up @@ -934,7 +935,7 @@ static int32_t ForkAndExecProcessInternal(
#endif

// Finally, execute the new process. execve will not return if it's successful.
execve(filename, argv, envp);
execve(filename, argv, envp != NULL ? envp : SystemNative_GetEnviron());
ExitChild(waitForChildToExecPipe[WRITE_END_OF_PIPE], errno); // execve failed
Comment thread
adamsitnik marked this conversation as resolved.
}

Expand Down
2 changes: 1 addition & 1 deletion src/native/libs/System.Native/pal_process.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
PALEXPORT int32_t SystemNative_ForkAndExecProcess(
const char* filename, // filename argument to execve
char* const argv[], // argv argument to execve
char* const envp[], // envp argument to execve
char* const envp[], // envp argument to execve; NULL to inherit the current environment
const char* cwd, // path passed to chdir in child process
Comment thread
adamsitnik marked this conversation as resolved.
int32_t setCredentials, // whether to set the userId and groupId for the child process
uint32_t userId, // the user id under which the child process should run
Expand Down
Loading