Reactos
at master 373 lines 14 kB view raw
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 17static char**& __cdecl get_environment(char) throw() { return _environ_table.value(); } 18static wchar_t**& __cdecl get_environment(wchar_t) throw() { return _wenviron_table.value(); } 19 20static wchar_t**& __cdecl get_other_environment(char) throw() { return _wenviron_table.value(); } 21static char**& __cdecl get_other_environment(wchar_t) throw() { return _environ_table.value(); } 22 23static char**& __cdecl get_initial_environment(char) throw() { return __dcrt_initial_narrow_environment; } 24static 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. 31template <typename Character> 32static 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. 78template <typename Character> 79static 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. 95template <typename Character> 96static 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*******************************************************************************/ 163template <typename Character> 164static 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 359extern "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 367extern "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}