xref: /reactos/sdk/lib/ucrt/env/setenv.cpp (revision 04e0dc4a)
1 //
2 // setenv.cpp
3 //
4 //      Copyright (c) Microsoft Corporation.  All rights reserved.
5 //
6 // Internal functions for setting or removing variables from an environment. The
7 // logic for manipulating the environment data structures is split across this
8 // file and environment_initialization.cpp.
9 //
10 #include <corecrt_internal.h>
11 #include <corecrt_internal_traits.h>
12 #include <limits.h>
13 #include <stdlib.h>
14 
15 
16 
get_environment(char)17 static char**&    __cdecl get_environment(char)    throw() { return _environ_table.value(); }
get_environment(wchar_t)18 static wchar_t**& __cdecl get_environment(wchar_t) throw() { return _wenviron_table.value(); }
19 
get_other_environment(char)20 static wchar_t**& __cdecl get_other_environment(char)    throw() { return _wenviron_table.value(); }
get_other_environment(wchar_t)21 static char**&    __cdecl get_other_environment(wchar_t) throw() { return _environ_table.value(); }
22 
get_initial_environment(char)23 static char**&    __cdecl get_initial_environment(char)    throw() { return __dcrt_initial_narrow_environment; }
get_initial_environment(wchar_t)24 static wchar_t**& __cdecl get_initial_environment(wchar_t) throw() { return __dcrt_initial_wide_environment;   }
25 
26 
27 
28 // Makes a copy of the provided environment and returns the copy.  The caller is
29 // responsible for freeing the returned array (using the CRT free).  Returns
30 // nullptr on failure; terminates the process on allocation failure.
31 template <typename Character>
copy_environment(Character ** const old_environment)32 static Character** __cdecl copy_environment(Character** const old_environment) throw()
33 {
34     typedef __crt_char_traits<Character> traits;
35 
36     if (!old_environment)
37     {
38         return nullptr;
39     }
40 
41     // Count the number of environment variables:
42     size_t entry_count = 0;
43     for (Character** it = old_environment; *it; ++it)
44     {
45         ++entry_count;
46     }
47 
48     // We need one pointer for each string, plus one null pointer at the end:
49     __crt_unique_heap_ptr<Character*> new_environment(_calloc_crt_t(Character*, entry_count + 1));
50     if (!new_environment)
51     {
52         abort();
53     }
54 
55     Character** old_it = old_environment;
56     Character** new_it = new_environment.get();
57     for (; *old_it; ++old_it, ++new_it)
58     {
59         size_t const required_count = traits::tcslen(*old_it) + 1;
60         *new_it = _calloc_crt_t(Character, required_count).detach();
61         if (!*new_it)
62         {
63             abort();
64         }
65 
66         _ERRCHECK(traits::tcscpy_s(*new_it, required_count, *old_it));
67     }
68 
69     return new_environment.detach();
70 }
71 
72 
73 
74 // If the current environment is the initial environment, this function clones
75 // the current environment so that it is not the initial environment.  This
76 // should be called any time that we are about to modify the current environment
77 // but we do not know whether the current environment is the initial environment.
78 template <typename Character>
ensure_current_environment_is_not_initial_environment_nolock()79 static void __cdecl ensure_current_environment_is_not_initial_environment_nolock() throw()
80 {
81     if (get_environment(Character()) == get_initial_environment(Character()))
82     {
83         get_environment(Character()) = copy_environment(get_environment(Character()));
84     }
85 }
86 
87 
88 
89 // Finds an environment variable in the specified environment.  If a variable
90 // with the given name is found, its index in the environment is returned.  If
91 // no such environment is found, the total number of environment variables is
92 // returned, multiplied by -1.  Note that a return value of 0 may indicate
93 // either that the variable was found at index 0 or there are zero variables
94 // in the environment.  Be sure to check for this case.
95 template <typename Character>
find_in_environment_nolock(Character const * const name,size_t const length)96 static ptrdiff_t __cdecl find_in_environment_nolock(
97     Character const* const name,
98     size_t           const length
99     ) throw()
100 {
101     typedef __crt_char_traits<Character> traits;
102 
103     Character** const environment = get_environment(Character());
104 
105     Character** it = nullptr;
106     for (it = environment; *it; ++it)
107     {
108         // See if the first 'length' characters match:
109         if (traits::tcsnicoll(name, *it, length) != 0)
110         {
111             continue;
112         }
113 
114         // Ensure that the next character of the environment is an '=' or '\0':
115         if ((*it)[length] != '=' && (*it)[length] != '\0')
116         {
117             continue;
118         }
119 
120         // Otherwise, this entry matched; return its index in the environment:
121         return static_cast<ptrdiff_t>(it - environment);
122     }
123 
124     // No entry matched; return the total number of strings, multiplied by -1:
125     return -static_cast<ptrdiff_t>(it - environment);
126 }
127 
128 
129 /***
130 *int __dcrt_set_variable_in_narrow_environment(option) - add/replace/remove variable in environment
131 *
132 *Purpose:
133 *       option should be of the form "option=value".  If a string with the
134 *       given option part already exists, it is replaced with the given
135 *       string; otherwise the given string is added to the environment.
136 *       If the string is of the form "option=", then the string is
137 *       removed from the environment, if it exists.  If the string has
138 *       no equals sign, error is returned.
139 *
140 *Entry:
141 *       TCHAR **poption - pointer to option string to set in the environment list.
142 *           should be of the form "option=value".
143 *           This function takes ownership of this pointer in the success case.
144 *       int primary - Only the primary call to _crt[w]setenv needs to
145 *           create new copies or set the OS environment.
146 *           1 indicates that this is the primary call.
147 *
148 *Exit:
149 *       returns 0 if OK, -1 if fails.
150 *       If *poption is non-null on exit, we did not free it, and the caller should
151 *       If *poption is null on exit, we did free it, and the caller should not.
152 *
153 *Exceptions:
154 *
155 *Warnings:
156 *       This code will not work if variables are removed from the environment
157 *       by deleting them from environ[].  Use _putenv("option=") to remove a
158 *       variable.
159 *
160 *       The option argument will be taken ownership of by this code and may be freed!
161 *
162 *******************************************************************************/
163 template <typename Character>
common_set_variable_in_environment_nolock(Character * const option,int const is_top_level_call)164 static int __cdecl common_set_variable_in_environment_nolock(
165     Character* const option,
166     int        const is_top_level_call
167     ) throw()
168 {
169     typedef __crt_char_traits<Character> traits;
170 
171     // Check that the option string is valid first.  Find the '=' and verify
172     // that '=' is not the first character in the string:
173     _VALIDATE_RETURN_NOEXC(option != nullptr, EINVAL, -1);
174     __crt_unique_heap_ptr<Character> owned_option(option);
175 
176     Character* const equal_sign = traits::tcschr(option, '=');
177     _VALIDATE_RETURN_NOEXC(equal_sign != nullptr && equal_sign != option, EINVAL, -1);
178 
179     // Internal consistency check:  The environment string should never use
180     // buffers larger than _MAX_ENV.  See also the SetEnvironmentVariable SDK
181     // function.
182     _ASSERTE(equal_sign - option                       < _MAX_ENV);
183     _ASSERTE(traits::tcsnlen(equal_sign + 1, _MAX_ENV) < _MAX_ENV);
184 
185     // If the character following '=' is the terminator, we are removing the
186     // environment variable.  Otherwise, we are adding or updating the variable:
187     bool const is_removal = *(equal_sign + 1) == '\0';
188 
189     // At program startup, the initial environment (__dcrt_initial_narrow_environment), which is passed
190     // to main(), is backed by the same environment arrays as the global
191     // environment used by getenv, setenv, et al.  We cannot modify thie initial
192     // environment, so we make a copy of it the first time we need to make any
193     // modifications to the global environment:
194     ensure_current_environment_is_not_initial_environment_nolock<Character>();
195 
196     // If the required environment does not exist, see if the other environment
197     // exists; if it does, convert it to create the required environment.  These
198     // functions will reenter this function once for each environment variable;
199     // we use the top-level call flag to stop recursion.
200     if (!get_environment(Character()))
201     {
202         if (is_top_level_call && get_other_environment(Character()))
203         {
204             _VALIDATE_RETURN_NOEXC(traits::get_or_create_environment_nolock() != nullptr, EINVAL, -1);
205 
206             // The call to get_or_create_environment() may have initialized the
207             // current environment to the same environment that is the initial
208             // environment.  Re-check and make a new copy of the environment to
209             // modify if necessary.
210             ensure_current_environment_is_not_initial_environment_nolock<Character>();
211         }
212         else
213         {
214             // If the environment doesn't exist and the requested operation is a
215             // removal, there is nothing to do (there is nothing to remove):
216             if (is_removal)
217             {
218                 return 0;
219             }
220 
221             // Create a new environment for each environment that does not exist.
222             // Just start each off as an empty environment:
223             if (!_environ_table.value())
224             {
225                 _environ_table.value() = _calloc_crt_t(char*, 1).detach();
226             }
227 
228             if (!_environ_table.value())
229             {
230                 return -1;
231             }
232 
233             if (!_wenviron_table.value())
234             {
235                 _wenviron_table.value() = _calloc_crt_t(wchar_t*, 1).detach();
236             }
237 
238             if (!_wenviron_table.value())
239             {
240                 return -1;
241             }
242         }
243     }
244 
245     // At this point, either [1] only one environment exists, or [2] both of the
246     // environments exist and are in-sync.  The only way they can get out of sync
247     // is if there are conversion problems.  For example, if the user sets two
248     // Unicode environment variables, FOO1 and FOO2, and the conversion of these
249     // to multibyte yields FOO? and FOO?, then these environment blocks will
250     // differ.
251     Character** const environment = get_environment(Character());
252     if (!environment)
253     {
254         _ASSERTE(("CRT logic error in setenv", 0));
255         return -1;
256     }
257 
258     // Try to find the option in the environment...
259     ptrdiff_t const option_index = find_in_environment_nolock(option, equal_sign - option);
260 
261     // ... if the string is already in the environment, we free up the original
262     // string, then install the new string or shrink the environment:
263     if (option_index >= 0 && environment[0])
264     {
265         _free_crt(environment[option_index]);
266 
267         // If this is a removal, shrink the environment:
268         if (is_removal)
269         {
270             // Shift all of the entries down by one element:
271             size_t i = static_cast<size_t>(option_index);
272             for (; environment[i]; ++i)
273             {
274                 environment[i] = environment[i + 1];
275             }
276 
277             // Shrink the environment memory block.  At this point, i is the
278             // number of elements remaining in the environment.  This realloc
279             // should never fail, since we are shrinking the block, but it is
280             // best to be careful.  If it does fail, it doesn't matter.
281             Character** new_environment = _recalloc_crt_t(Character*, environment, i).detach();
282             if (new_environment)
283             {
284                 get_environment(Character()) = new_environment;
285             }
286         }
287         // If this is a replacement, replace the variable:
288         else
289         {
290             environment[option_index] = owned_option.detach();
291         }
292     }
293     // Otherwise, the string is not in the environment:
294     else
295     {
296         // If this is a removal, it is a no-op:  the variable does not exist.
297         if (is_removal)
298         {
299             return 0;
300         }
301         // Otherwise, we need to append the string to the environment table, and
302         // we must grow the table to do this:
303         else
304         {
305             size_t const environment_count = static_cast<size_t>(-option_index);
306             if (environment_count + 2 < environment_count)
307             {
308                 return -1;
309             }
310 
311             if (environment_count + 2 >= SIZE_MAX / sizeof(Character*))
312             {
313                 return -1;
314             }
315 
316             Character** const new_environment = _recalloc_crt_t(Character*, environment, environment_count + 2).detach();
317             if (!new_environment)
318             {
319                 return -1;
320             }
321 
322             new_environment[environment_count]     = owned_option.detach();
323             new_environment[environment_count + 1] = nullptr;
324 
325             get_environment(Character()) = new_environment;
326         }
327     }
328 
329     // Update the operating system environment.  Do not give an error if this
330     // fails since the failure will not affect the user code unless it is making
331     // direct calls to the operating system.  We only need to do this for one of
332     // the environments; the operating system synchronizes with the other
333     // environment automatically.
334     if (is_top_level_call)
335     {
336         size_t const count = traits::tcslen(option) + 2;
337         __crt_unique_heap_ptr<Character> const buffer(_calloc_crt_t(Character, count));
338         if (!buffer)
339         {
340             return 0;
341         }
342 
343         Character* const name = buffer.get();
344         _ERRCHECK(traits::tcscpy_s(name, count, option));
345 
346         Character* const value = name + (equal_sign - option) + 1;
347         *(value - 1) = '\0'; // Overwrite the '=' with a null terminator
348 
349         if (traits::set_environment_variable(name, is_removal ? nullptr : value) == 0)
350         {
351             errno = EILSEQ;
352             return -1;
353         }
354     }
355 
356     return 0;
357 }
358 
__dcrt_set_variable_in_narrow_environment_nolock(char * const option,int const is_top_level_call)359 extern "C" int __cdecl __dcrt_set_variable_in_narrow_environment_nolock(
360     char* const option,
361     int   const is_top_level_call
362     )
363 {
364     return common_set_variable_in_environment_nolock(option, is_top_level_call);
365 }
366 
__dcrt_set_variable_in_wide_environment_nolock(wchar_t * const option,int const is_top_level_call)367 extern "C" int __cdecl __dcrt_set_variable_in_wide_environment_nolock(
368     wchar_t* const option,
369     int      const is_top_level_call
370     )
371 {
372     return common_set_variable_in_environment_nolock(option, is_top_level_call);
373 }
374