Skip to content

Environment.SetEnvironmentVariable() works differently to setenv() p/Invoke #30525

Closed

Description

@rjmholt

I've been working on adding login shell support to PowerShell in PowerShell/PowerShell#10050.

One of the optimisations I've been trying to add is a short circuit at startup so we don't check for login twice.

The idea we settled on was to set an environment variable, which for the purposes of this issue looks like this:

static void DoPowerShellLogin()
{
        if (Environment.GetEnvironmentVariable("ALREADY_CHECKED_LOGIN") != null)
        {
            return;
        }

        Environment.SetEnvironmentVariable("ALREADY_CHECKED_LOGIN", "1");
        exec("/path/to/pwsh", new string[] { null });
}

[DllImport("libc")]
private static extern int exec(string path, string[] args);

In the debugger, this successfully created the environment variable in the first pass, but on the other side of the exec call it wasn't defined.

I played with this a bit and eventually tried a direct invocation of the setenv syscall:

static void DoPowerShellLogin()
{
        if (Environment.GetEnvironmentVariable("ALREADY_CHECKED_LOGIN") != null)
        {
            return;
        }

        setenv("ALREADY_CHECKED_LOGIN", "1", true);
        exec("/path/to/pwsh", new string[] { null });
}

[DllImport("libc")]
private static extern int exec(string path, string[] args);

[DllImport("libc")]
private static extern int setenv(string name, string value, bool overwrite);

And this now worked.

Most interestingly, before calling exec but after calling setenv, Environment.GetEnvironmentVariable("ALREADY_CHECKED_LOGIN") returned null.

I'm assuming this behaviour is by design, but I'm not really sure why it is the way it is; I certainly didn't expect setenv to work when Environment.SetEnvironmentVariable() didn't.

Activity

  1. rjmholt commented on Aug 8, 2019

    @rjmholt
    Author

    Environment information:

     > [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription
    .NET Core 3.0.0-preview6-27804-01
    
     > [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
    Darwin 18.7.0 Darwin Kernel Version 18.7.0: Thu Jun 20 18:42:21 PDT 2019; root:xnu-4903.270.47~4/RELEASE_X86_64
    
  2. MichalStrehovsky commented on Aug 8, 2019

    @MichalStrehovsky
    Member

    This is a known problem on Unix with no good solution: see e.g. dotnet/coreclr#635.

    If you use Environment.SetEnvironmentVariable, use Process.Start to start the process. The environment variables you set on the managed side will be passed to the new process.

    If you want to use libc exec, use setenv. Basically, you need to stick to one thing (libc calls or managed APIs).

  3. rjmholt commented on Aug 8, 2019

    @rjmholt
    Author

    Ah didn't think to search coreclr; this is definitely a duplicate of https://github.com/dotnet/coreclr/issues/635.

    use Process.Start to start the process

    In our case, the complicating factor is that there is no new process. Only an exec with no fork.

    Thanks for linking that issue!

  4. transferred this issue fromdotnet/corefxon Feb 1, 2020
  5. added this to the 5.0 milestone on Feb 1, 2020
  6. ghost locked as resolved and limited conversation to collaborators on Dec 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions