summaryrefslogtreecommitdiff
path: root/src/System.Private.CoreLib/shared/System/Environment.Variables.Windows.cs
blob: 92be84b1fb046d8b657a9daaaead011ad6e66260 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 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.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;

namespace System
{
    public static partial class Environment
    {
        private static string GetEnvironmentVariableCore(string variable)
        {
            Span<char> buffer = stackalloc char[128]; // a somewhat reasonable default size
            int requiredSize = Interop.Kernel32.GetEnvironmentVariable(variable, buffer);

            if (requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND)
            {
                return null;
            }

            if (requiredSize <= buffer.Length)
            {
                return new string(buffer.Slice(0, requiredSize));
            }

            char[] chars = ArrayPool<char>.Shared.Rent(requiredSize);
            try
            {
                buffer = chars;
                requiredSize = Interop.Kernel32.GetEnvironmentVariable(variable, buffer);
                if ((requiredSize == 0 && Marshal.GetLastWin32Error() == Interop.Errors.ERROR_ENVVAR_NOT_FOUND) ||
                    requiredSize > buffer.Length)
                {
                    return null;
                }

                return new string(buffer.Slice(0, requiredSize));
            }
            finally
            {
                ArrayPool<char>.Shared.Return(chars);
            }
        }

        private static void SetEnvironmentVariableCore(string variable, string value)
        {
            if (!Interop.Kernel32.SetEnvironmentVariable(variable, value))
            {
                int errorCode = Marshal.GetLastWin32Error();
                switch (errorCode)
                {
                    case Interop.Errors.ERROR_ENVVAR_NOT_FOUND:
                        // Allow user to try to clear a environment variable
                        return;

                    case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
                        // The error message from Win32 is "The filename or extension is too long",
                        // which is not accurate.
                        throw new ArgumentException(SR.Argument_LongEnvVarValue);

                    case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY:
                    case Interop.Errors.ERROR_NO_SYSTEM_RESOURCES:
                        throw new OutOfMemoryException(Interop.Kernel32.GetMessage(errorCode));

                    default:
                        throw new ArgumentException(Interop.Kernel32.GetMessage(errorCode));
                }
            }
        }

        public static unsafe IDictionary GetEnvironmentVariables()
        {
            char* pStrings = Interop.Kernel32.GetEnvironmentStrings();
            if (pStrings == null)
            {
                throw new OutOfMemoryException();
            }

            try
            {
                // Format for GetEnvironmentStrings is:
                // [=HiddenVar=value\0]* [Variable=value\0]* \0
                // See the description of Environment Blocks in MSDN's
                // CreateProcess page (null-terminated array of null-terminated strings).

                // Search for terminating \0\0 (two unicode \0's).
                char* p = pStrings;
                while (!(*p == '\0' && *(p + 1) == '\0'))
                {
                    p++;
                }
                Span<char> block = new Span<char>(pStrings, (int)(p - pStrings + 1));

                // Format for GetEnvironmentStrings is:
                // (=HiddenVar=value\0 | Variable=value\0)* \0
                // See the description of Environment Blocks in MSDN's
                // CreateProcess page (null-terminated array of null-terminated strings).
                // Note the =HiddenVar's aren't always at the beginning.

                // Copy strings out, parsing into pairs and inserting into the table.
                // The first few environment variable entries start with an '='.
                // The current working directory of every drive (except for those drives
                // you haven't cd'ed into in your DOS window) are stored in the 
                // environment block (as =C:=pwd) and the program's exit code is 
                // as well (=ExitCode=00000000).

                var results = new Hashtable();
                for (int i = 0; i < block.Length; i++)
                {
                    int startKey = i;

                    // Skip to key. On some old OS, the environment block can be corrupted.
                    // Some will not have '=', so we need to check for '\0'. 
                    while (block[i] != '=' && block[i] != '\0')
                    {
                        i++;
                    }

                    if (block[i] == '\0')
                    {
                        continue;
                    }

                    // Skip over environment variables starting with '='
                    if (i - startKey == 0)
                    {
                        while (block[i] != 0)
                        {
                            i++;
                        }

                        continue;
                    }

                    string key = new string(block.Slice(startKey, i - startKey));
                    i++;  // skip over '='

                    int startValue = i;
                    while (block[i] != 0)
                    {
                        i++; // Read to end of this entry 
                    }

                    string value = new string(block.Slice(startValue, i - startValue)); // skip over 0 handled by for loop's i++
                    try
                    {
                        results.Add(key, value);
                    }
                    catch (ArgumentException)
                    {
                        // Throw and catch intentionally to provide non-fatal notification about corrupted environment block
                    }
                }
                return results;
            }
            finally
            {
                bool success = Interop.Kernel32.FreeEnvironmentStrings(pStrings);
                Debug.Assert(success);
            }
        }
    }
}