mirror of
https://github.com/reactos/reactos.git
synced 2025-08-07 05:52:57 +00:00
[UCRT] Import Microsoft.Windows.SDK.CRTSource version 10.0.22621.3
Imported from https://www.nuget.org/packages/Microsoft.Windows.SDK.CRTSource/10.0.22621.3 License: MIT
This commit is contained in:
parent
f1b60c66f0
commit
04e0dc4a7a
568 changed files with 115483 additions and 0 deletions
336
sdk/lib/ucrt/env/environment_initialization.cpp
vendored
Normal file
336
sdk/lib/ucrt/env/environment_initialization.cpp
vendored
Normal file
|
@ -0,0 +1,336 @@
|
|||
//
|
||||
// environment_initialization.cpp
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Defines functions for initializing and uninitializing the global environments
|
||||
// and for constructing and destroying environments. The logic for manipulating
|
||||
// the environment data structures is split across this file and the setenv.cpp
|
||||
// file.
|
||||
//
|
||||
#include <corecrt_internal_traits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
// The global environment data. The initial environments store the pointer to
|
||||
// the environment that is passed to main or wmain. This is used only to
|
||||
// ensure that we do not modify that environment block after we pass it to
|
||||
// user code. The _environ_table and _wenviron_table hold the current CRT environment.
|
||||
// Their names cannot change; they are publicly documented.
|
||||
extern "C"
|
||||
{
|
||||
char** __dcrt_initial_narrow_environment = nullptr;
|
||||
wchar_t** __dcrt_initial_wide_environment = nullptr;
|
||||
|
||||
__crt_state_management::dual_state_global<char**> _environ_table;
|
||||
__crt_state_management::dual_state_global<wchar_t**> _wenviron_table;
|
||||
|
||||
char*** __cdecl __p__environ() { return &_environ_table.value(); }
|
||||
wchar_t*** __cdecl __p__wenviron() { return &_wenviron_table.value(); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
_Ret_opt_z_
|
||||
static char**& get_environment_nolock(char) throw() { return _environ_table.value(); }
|
||||
|
||||
_Ret_opt_z_
|
||||
static wchar_t**& get_environment_nolock(wchar_t) throw() { return _wenviron_table.value(); }
|
||||
|
||||
_Ret_opt_z_
|
||||
static char**& __cdecl get_initial_environment(char) throw() { return __dcrt_initial_narrow_environment; }
|
||||
|
||||
_Ret_opt_z_
|
||||
static wchar_t**& __cdecl get_initial_environment(wchar_t) throw() { return __dcrt_initial_wide_environment; }
|
||||
|
||||
static __crt_state_management::dual_state_global<char**>& get_dual_state_environment_nolock(char) throw() { return _environ_table; }
|
||||
static __crt_state_management::dual_state_global<wchar_t**>& get_dual_state_environment_nolock(wchar_t) throw() { return _wenviron_table; }
|
||||
|
||||
|
||||
|
||||
// Counts the number of environment variables in the provided 'environment_block',
|
||||
// excluding those that start with '=' (these are drive letter settings).
|
||||
template <typename Character>
|
||||
static size_t const count_variables_in_environment_block(Character* const environment_block) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
// Count the number of variables in the environment block, ignoring drive
|
||||
// letter settings, which begin with '=':
|
||||
size_t count = 0;
|
||||
|
||||
Character* it = environment_block;
|
||||
while (*it != '\0')
|
||||
{
|
||||
if (*it != '=')
|
||||
++count;
|
||||
|
||||
// This advances the iterator to the next string:
|
||||
it += traits::tcslen(it) + 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Environment Create and Free (These do not modify any global data)
|
||||
//
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// Frees the environment pointed-to by 'environment'. This function ensures that
|
||||
// each of the environment strings is freed and that the array itself is freed.
|
||||
// This function requires that 'environment' is either nullptr or that it points
|
||||
// to a valid environment (i.e., one composed of a sequence of zero or more non-
|
||||
// null pointers terminated by a null pointer).
|
||||
template <typename Character>
|
||||
static void free_environment(Character** const environment) throw()
|
||||
{
|
||||
if (!environment)
|
||||
return;
|
||||
|
||||
for (Character** it = environment; *it; ++it)
|
||||
_free_crt(*it);
|
||||
|
||||
_free_crt(environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Creates a new environment, populating it with the strings from the provided
|
||||
// 'environment_block', which must be a double-null-terminated sequence of
|
||||
// environment variable strings of the form "name=value". Variables beginning
|
||||
// with '=' are ignored (these are drive letter settings). Returns the newly
|
||||
// created environment on succes; returns nullptr on failure.
|
||||
template <typename Character>
|
||||
static Character** const create_environment(Character* const environment_block) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
size_t const variable_count = count_variables_in_environment_block(environment_block);
|
||||
|
||||
__crt_unique_heap_ptr<Character*> environment(_calloc_crt_t(Character*, variable_count + 1));
|
||||
if (!environment)
|
||||
return nullptr;
|
||||
|
||||
Character* source_it = environment_block;
|
||||
Character** result_it = environment.get();
|
||||
|
||||
while (*source_it != '\0')
|
||||
{
|
||||
size_t const required_count = traits::tcslen(source_it) + 1;
|
||||
|
||||
// Don't copy drive letter settings, which start with '=':
|
||||
if (*source_it != '=')
|
||||
{
|
||||
__crt_unique_heap_ptr<Character> variable(_calloc_crt_t(Character, required_count));
|
||||
if (!variable)
|
||||
{
|
||||
free_environment(environment.detach());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_ERRCHECK(traits::tcscpy_s(variable.get(), required_count, source_it));
|
||||
*result_it++ = variable.detach();
|
||||
}
|
||||
|
||||
// This advances the iterator to the next string:
|
||||
source_it += required_count;
|
||||
}
|
||||
|
||||
// The sequence of pointers is already null-terminated; return it:
|
||||
return environment.detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Environment Initialize and Uninitialize
|
||||
//
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// In the initialize function below, we need to ensure that we've initialized
|
||||
// the mbc table before we start performing character transformations.
|
||||
static void pre_initialize(char) throw() { __acrt_initialize_multibyte(); }
|
||||
static void pre_initialize(wchar_t) throw() { /* no-op */ }
|
||||
|
||||
|
||||
|
||||
// Gets the current environment from the operating system and initializes the
|
||||
// CRT environment from that environment. Returns 0 on success; -1 on failure.
|
||||
// If this function returns successfully, the global environment pointer for
|
||||
// the requested environment will be non-null and valid.
|
||||
template <typename Character>
|
||||
static int __cdecl common_initialize_environment_nolock() throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
// We only initialize the environment once. Once the environment has been
|
||||
// initialized, all updates and modifications go through the other functions
|
||||
// that manipulate the environment.
|
||||
if (get_environment_nolock(Character()))
|
||||
return 0;
|
||||
|
||||
pre_initialize(Character());
|
||||
|
||||
__crt_unique_heap_ptr<Character> const os_environment(traits::get_environment_from_os());
|
||||
if (!os_environment)
|
||||
return -1;
|
||||
|
||||
__crt_unique_heap_ptr<Character*> crt_environment(create_environment(os_environment.get()));
|
||||
if (!crt_environment)
|
||||
return -1;
|
||||
|
||||
get_initial_environment(Character()) = crt_environment.get();
|
||||
get_dual_state_environment_nolock(Character()).initialize(crt_environment.detach());
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int __cdecl _initialize_narrow_environment()
|
||||
{
|
||||
return common_initialize_environment_nolock<char>();
|
||||
}
|
||||
|
||||
extern "C" int __cdecl _initialize_wide_environment()
|
||||
{
|
||||
return common_initialize_environment_nolock<wchar_t>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Frees the global wide and narrow environments and returns.
|
||||
template <typename Character>
|
||||
static void __cdecl uninitialize_environment_internal(Character**& environment) throw()
|
||||
{
|
||||
if (environment == get_initial_environment(Character()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
free_environment(environment);
|
||||
}
|
||||
|
||||
extern "C" void __cdecl __dcrt_uninitialize_environments_nolock()
|
||||
{
|
||||
_environ_table .uninitialize(uninitialize_environment_internal<char>);
|
||||
_wenviron_table.uninitialize(uninitialize_environment_internal<wchar_t>);
|
||||
|
||||
free_environment(__dcrt_initial_narrow_environment);
|
||||
free_environment(__dcrt_initial_wide_environment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// Environment Get-Or-Create
|
||||
//
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// These functions help with synchronization between the narrow and wide
|
||||
// environments. If the requested environment has not yet been initialized but
|
||||
// the other environment has been initialized, the other environment is cloned
|
||||
// to create the requested environment. Note that if the other environment has
|
||||
// not yet been initialized, these functions do not do anything.
|
||||
|
||||
// Gets the other environment and copies each of the environment variables from
|
||||
// it into the requested environment. Returns 0 on success; -1 on failure.
|
||||
template <typename Character>
|
||||
static int __cdecl initialize_environment_by_cloning_nolock() throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
typedef typename traits::other_char_type other_char_type;
|
||||
|
||||
other_char_type** const other_environment = get_environment_nolock(other_char_type());
|
||||
if (!other_environment)
|
||||
return -1;
|
||||
|
||||
for (other_char_type** it = other_environment; *it; ++it)
|
||||
{
|
||||
size_t const required_count = __crt_compute_required_transform_buffer_count(CP_ACP, *it);
|
||||
if (required_count == 0)
|
||||
return -1;
|
||||
|
||||
__crt_unique_heap_ptr<Character> buffer(_calloc_crt_t(Character, required_count));
|
||||
if (!buffer)
|
||||
return -1;
|
||||
|
||||
size_t const actual_count = __crt_transform_string(CP_ACP, *it, buffer.get(), required_count);
|
||||
if (actual_count == 0)
|
||||
return -1;
|
||||
|
||||
// Ignore a failed attempt to set a variable; continue with the rest...
|
||||
traits::set_variable_in_environment_nolock(buffer.detach(), 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If the requested environment exists, this function returns it unmodified. If
|
||||
// the requested environment does not exist but the other environment does, the
|
||||
// other environment is cloned to create the requested environment, and the new
|
||||
// requested environment is returned. Otherwise, nullptr is returned.
|
||||
template <typename Character>
|
||||
_Deref_ret_opt_z_
|
||||
static Character** __cdecl common_get_or_create_environment_nolock() throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
typedef typename traits::other_char_type other_char_type;
|
||||
|
||||
// Check to see if the required environment already exists:
|
||||
Character** const existing_environment = get_environment_nolock(Character());
|
||||
if (existing_environment)
|
||||
return existing_environment;
|
||||
|
||||
// Check to see if the other environment exists. We will only initialize
|
||||
// the environment here if the other environment was already initialized.
|
||||
other_char_type** const other_environment = get_environment_nolock(other_char_type());
|
||||
if (!other_environment)
|
||||
return nullptr;
|
||||
|
||||
if (common_initialize_environment_nolock<Character>() != 0)
|
||||
{
|
||||
if (initialize_environment_by_cloning_nolock<Character>() != 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return get_environment_nolock(Character());
|
||||
}
|
||||
|
||||
extern "C" char** __cdecl __dcrt_get_or_create_narrow_environment_nolock()
|
||||
{
|
||||
return common_get_or_create_environment_nolock<char>();
|
||||
}
|
||||
|
||||
extern "C" wchar_t** __cdecl __dcrt_get_or_create_wide_environment_nolock()
|
||||
{
|
||||
return common_get_or_create_environment_nolock<wchar_t>();
|
||||
}
|
||||
|
||||
template <typename Character>
|
||||
static Character** __cdecl common_get_initial_environment() throw()
|
||||
{
|
||||
Character**& initial_environment = get_initial_environment(Character());
|
||||
if (!initial_environment)
|
||||
{
|
||||
initial_environment = common_get_or_create_environment_nolock<Character>();
|
||||
}
|
||||
|
||||
return initial_environment;
|
||||
}
|
||||
|
||||
extern "C" char** __cdecl _get_initial_narrow_environment()
|
||||
{
|
||||
return common_get_initial_environment<char>();
|
||||
}
|
||||
|
||||
extern "C" wchar_t** __cdecl _get_initial_wide_environment()
|
||||
{
|
||||
return common_get_initial_environment<wchar_t>();
|
||||
}
|
121
sdk/lib/ucrt/env/get_environment_from_os.cpp
vendored
Normal file
121
sdk/lib/ucrt/env/get_environment_from_os.cpp
vendored
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// get_environment_from_os.cpp
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Defines a pair of functions to get the environment strings from the operating
|
||||
// system. These functions copy the environment strings into a CRT-allocated
|
||||
// buffer and return a pointer to that buffer. The caller is responsible for
|
||||
// freeing the returned buffer (via _crt_free).
|
||||
//
|
||||
#include <corecrt_internal.h>
|
||||
#include <corecrt_internal_traits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
struct environment_strings_traits
|
||||
{
|
||||
typedef wchar_t* type;
|
||||
|
||||
static bool close(_In_ type p) throw()
|
||||
{
|
||||
FreeEnvironmentStringsW(p);
|
||||
return true;
|
||||
}
|
||||
|
||||
static type get_invalid_value() throw()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
typedef __crt_unique_handle_t<environment_strings_traits> environment_strings_handle;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static wchar_t const* __cdecl find_end_of_double_null_terminated_sequence(wchar_t const* const first) throw()
|
||||
{
|
||||
wchar_t const* last = first;
|
||||
for (; *last != '\0'; last += wcslen(last) + 1)
|
||||
{
|
||||
}
|
||||
|
||||
return last + 1; // Return the pointer one-past-the-end of the double null terminator
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" wchar_t* __cdecl __dcrt_get_wide_environment_from_os() throw()
|
||||
{
|
||||
environment_strings_handle const environment(GetEnvironmentStringsW());
|
||||
if (!environment)
|
||||
return nullptr;
|
||||
|
||||
// Find the
|
||||
wchar_t const* const first = environment.get();
|
||||
wchar_t const* const last = find_end_of_double_null_terminated_sequence(first);
|
||||
|
||||
size_t const required_count = last - first;
|
||||
|
||||
__crt_unique_heap_ptr<wchar_t> buffer(_malloc_crt_t(wchar_t, required_count));
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
// Note that the multiplication here cannot overflow:
|
||||
memcpy(buffer.get(), environment.get(), required_count * sizeof(wchar_t));
|
||||
return buffer.detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" char* __cdecl __dcrt_get_narrow_environment_from_os() throw()
|
||||
{
|
||||
// Note that we call GetEnvironmentStringsW and convert to multibyte. The
|
||||
// GetEnvironmentStringsA function returns the environment in the OEM code
|
||||
// page, but we need the strings in ANSI.
|
||||
environment_strings_handle const environment(GetEnvironmentStringsW());
|
||||
if (!environment.is_valid())
|
||||
return nullptr;
|
||||
|
||||
wchar_t const* const first = environment.get();
|
||||
wchar_t const* const last = find_end_of_double_null_terminated_sequence(first);
|
||||
|
||||
size_t const required_wide_count = last - first;
|
||||
#pragma warning(suppress:__WARNING_W2A_BEST_FIT) // 38021 Prefast recommends WC_NO_BEST_FIT_CHARS.
|
||||
size_t const required_narrow_count = static_cast<size_t>(__acrt_WideCharToMultiByte(
|
||||
CP_ACP,
|
||||
0,
|
||||
environment.get(),
|
||||
static_cast<int>(required_wide_count),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr));
|
||||
|
||||
if (required_narrow_count == 0)
|
||||
return nullptr;
|
||||
|
||||
__crt_unique_heap_ptr<char> buffer(_malloc_crt_t(char, required_narrow_count));
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
#pragma warning(suppress:__WARNING_W2A_BEST_FIT) // 38021 Prefast recommends WC_NO_BEST_FIT_CHARS.
|
||||
int const conversion_result = __acrt_WideCharToMultiByte(
|
||||
CP_ACP,
|
||||
0,
|
||||
environment.get(),
|
||||
static_cast<int>(required_wide_count),
|
||||
buffer.get(),
|
||||
static_cast<int>(required_narrow_count),
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
if (conversion_result == 0)
|
||||
return nullptr;
|
||||
|
||||
return buffer.detach();
|
||||
}
|
329
sdk/lib/ucrt/env/getenv.cpp
vendored
Normal file
329
sdk/lib/ucrt/env/getenv.cpp
vendored
Normal file
|
@ -0,0 +1,329 @@
|
|||
//
|
||||
// getenv.cpp
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Defines the getenv family of functions, which search the environment for a
|
||||
// variable and return its value.
|
||||
//
|
||||
#include <corecrt_internal_traits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// getenv() and _wgetenv()
|
||||
//
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// These functions search the environment for a variable with the given name.
|
||||
// If such a variable is found, a pointer to its value is returned. Otherwise,
|
||||
// nullptr is returned. Note that if the environment is access and manipulated
|
||||
// from multiple threads, this function cannot be safely used: the returned
|
||||
// pointer may not be valid when the function returns.
|
||||
template <typename Character>
|
||||
static Character* __cdecl common_getenv_nolock(Character const* const name) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
Character** const environment = traits::get_or_create_environment_nolock();
|
||||
if (environment == nullptr || name == nullptr)
|
||||
return nullptr;
|
||||
|
||||
size_t const name_length = traits::tcslen(name);
|
||||
|
||||
for (Character** current = environment; *current; ++current)
|
||||
{
|
||||
if (traits::tcslen(*current) <= name_length)
|
||||
continue;
|
||||
|
||||
if (*(*current + name_length) != '=')
|
||||
continue;
|
||||
|
||||
if (traits::tcsnicoll(*current, name, name_length) != 0)
|
||||
continue;
|
||||
|
||||
// Internal consistency check: The environment string should never use
|
||||
// a bigger buffer than _MAX_ENV. See also the SetEnvironmentVariable
|
||||
// SDK function.
|
||||
_ASSERTE(traits::tcsnlen(*current + name_length + 1, _MAX_ENV) < _MAX_ENV);
|
||||
|
||||
return *current + name_length + 1;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename Character>
|
||||
static Character* __cdecl common_getenv(Character const* const name) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
_VALIDATE_RETURN(name != nullptr, EINVAL, nullptr);
|
||||
_VALIDATE_RETURN(traits::tcsnlen(name, _MAX_ENV) < _MAX_ENV, EINVAL, nullptr);
|
||||
|
||||
Character* result = 0;
|
||||
|
||||
__acrt_lock(__acrt_environment_lock);
|
||||
__try
|
||||
{
|
||||
result = common_getenv_nolock(name);
|
||||
}
|
||||
__finally
|
||||
{
|
||||
__acrt_unlock(__acrt_environment_lock);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
extern "C" char* __cdecl getenv(char const* const name)
|
||||
{
|
||||
return common_getenv(name);
|
||||
}
|
||||
|
||||
extern "C" wchar_t* __cdecl _wgetenv(wchar_t const* const name)
|
||||
{
|
||||
return common_getenv(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// getenv_s() and _wgetenv_s()
|
||||
//
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// These functions search the environment for a variable with the given name.
|
||||
// If such a variable is found, its value is copied into the provided buffer.
|
||||
// The number of characters in the value (including the null terminator) is
|
||||
// stored in '*required_count'. Returns 0 on success; returns ERANGE if the
|
||||
// provided buffer is too small; otherwise returns an error code on failure.
|
||||
template <typename Character>
|
||||
_Success_(return == 0)
|
||||
static errno_t __cdecl common_getenv_s_nolock(
|
||||
size_t* const required_count,
|
||||
_Out_writes_z_(buffer_count) Character* const buffer,
|
||||
size_t const buffer_count,
|
||||
Character const* const name
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
_VALIDATE_RETURN_ERRCODE(required_count != nullptr, EINVAL);
|
||||
*required_count = 0;
|
||||
|
||||
_VALIDATE_RETURN_ERRCODE(
|
||||
(buffer != nullptr && buffer_count > 0) ||
|
||||
(buffer == nullptr && buffer_count == 0), EINVAL);
|
||||
|
||||
if (buffer)
|
||||
buffer[0] = '\0';
|
||||
|
||||
Character const* const value = common_getenv_nolock(name);
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
*required_count = traits::tcslen(value) + 1;
|
||||
if (buffer_count == 0)
|
||||
return 0;
|
||||
|
||||
// The buffer is too small; we return an error code and the caller can have
|
||||
// the opportunity to try again with a larger buffer:
|
||||
if (*required_count > buffer_count)
|
||||
return ERANGE;
|
||||
|
||||
_ERRCHECK(traits::tcscpy_s(buffer, buffer_count, value));
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Character>
|
||||
_Success_(return == 0)
|
||||
static errno_t __cdecl common_getenv_s(
|
||||
size_t* const required_count,
|
||||
_Out_writes_z_(buffer_count) Character* const buffer,
|
||||
size_t const buffer_count,
|
||||
Character const* const name
|
||||
) throw()
|
||||
{
|
||||
errno_t status = 0;
|
||||
|
||||
__acrt_lock(__acrt_environment_lock);
|
||||
__try
|
||||
{
|
||||
status = common_getenv_s_nolock(required_count, buffer, buffer_count, name);
|
||||
}
|
||||
__finally
|
||||
{
|
||||
__acrt_unlock(__acrt_environment_lock);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl getenv_s(
|
||||
size_t* const required_count,
|
||||
char* const buffer,
|
||||
size_t const buffer_count,
|
||||
char const* const name
|
||||
)
|
||||
{
|
||||
return common_getenv_s(required_count, buffer, buffer_count, name);
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl _wgetenv_s(
|
||||
size_t* const required_count,
|
||||
wchar_t* const buffer,
|
||||
size_t const buffer_count,
|
||||
wchar_t const* const name
|
||||
)
|
||||
{
|
||||
return common_getenv_s(required_count, buffer, buffer_count, name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// _dupenv_s() and _wdupenv_s()
|
||||
//
|
||||
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// These functions search the environment for a variable with the given name.
|
||||
// If a variable is found, a buffer is allocated using the public malloc to hold
|
||||
// the value of the variable. The value is copied into the buffer and the buffer
|
||||
// is returned to the caller via 'buffer_pointer' and 'buffer_count'. The caller
|
||||
// is responsible for freeing the buffer.
|
||||
//
|
||||
// Returns zero on success; returns an error code on failure. Note that if a
|
||||
// variable with the specified name is not found, that is still considered a
|
||||
// success; in this case, the 'value' is an empty string ('*buffer_count' will
|
||||
// be zero and '*buffer_pointer' will be nullptr).
|
||||
template <typename Character>
|
||||
static errno_t __cdecl common_dupenv_s_nolock(
|
||||
_Outptr_result_buffer_maybenull_(*buffer_count) _Outptr_result_maybenull_z_ Character** const buffer_pointer,
|
||||
_Out_opt_ size_t* const buffer_count,
|
||||
Character const* const name,
|
||||
int const block_use,
|
||||
char const* const file_name,
|
||||
int const line_number
|
||||
) throw()
|
||||
{
|
||||
// These are referenced only in the Debug CRT build
|
||||
UNREFERENCED_PARAMETER(block_use);
|
||||
UNREFERENCED_PARAMETER(file_name);
|
||||
UNREFERENCED_PARAMETER(line_number);
|
||||
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
_VALIDATE_RETURN_ERRCODE(buffer_pointer != nullptr, EINVAL);
|
||||
*buffer_pointer = nullptr;
|
||||
|
||||
if (buffer_count != nullptr)
|
||||
*buffer_count = 0;
|
||||
|
||||
_VALIDATE_RETURN_ERRCODE(name != nullptr, EINVAL);
|
||||
|
||||
Character const* const value = common_getenv_nolock(name);
|
||||
if (value == nullptr)
|
||||
return 0;
|
||||
|
||||
size_t const value_count = traits::tcslen(value) + 1;
|
||||
|
||||
*buffer_pointer = static_cast<Character*>(_calloc_dbg(
|
||||
value_count,
|
||||
sizeof(Character),
|
||||
block_use,
|
||||
file_name,
|
||||
line_number));
|
||||
_VALIDATE_RETURN_ERRCODE_NOEXC(*buffer_pointer != nullptr, ENOMEM);
|
||||
|
||||
_ERRCHECK(traits::tcscpy_s(*buffer_pointer, value_count, value));
|
||||
if (buffer_count != nullptr)
|
||||
*buffer_count = value_count;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename Character>
|
||||
_Success_(return != 0)
|
||||
static errno_t __cdecl common_dupenv_s(
|
||||
_Outptr_result_buffer_maybenull_(*buffer_count) _Outptr_result_maybenull_z_ Character** const buffer_pointer,
|
||||
_Out_opt_ size_t* const buffer_count,
|
||||
Character const* const name,
|
||||
int const block_use,
|
||||
char const* const file_name,
|
||||
int const line_number
|
||||
) throw()
|
||||
{
|
||||
errno_t status = 0;
|
||||
|
||||
__acrt_lock(__acrt_environment_lock);
|
||||
__try
|
||||
{
|
||||
status = common_dupenv_s_nolock(
|
||||
buffer_pointer,
|
||||
buffer_count,
|
||||
name,
|
||||
block_use,
|
||||
file_name,
|
||||
line_number);
|
||||
}
|
||||
__finally
|
||||
{
|
||||
__acrt_unlock(__acrt_environment_lock);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl _dupenv_s(
|
||||
char** const buffer_pointer,
|
||||
size_t* const buffer_count,
|
||||
char const* const name
|
||||
)
|
||||
{
|
||||
return common_dupenv_s(buffer_pointer, buffer_count, name, _NORMAL_BLOCK, nullptr, 0);
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl _wdupenv_s(
|
||||
wchar_t** const buffer_pointer,
|
||||
size_t* const buffer_count,
|
||||
wchar_t const* const name
|
||||
)
|
||||
{
|
||||
return common_dupenv_s(buffer_pointer, buffer_count, name, _NORMAL_BLOCK, nullptr, 0);
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#undef _dupenv_s_dbg
|
||||
#undef _wdupenv_s_dbg
|
||||
|
||||
extern "C" errno_t __cdecl _dupenv_s_dbg(
|
||||
char** const buffer_pointer,
|
||||
size_t* const buffer_count,
|
||||
char const* const name,
|
||||
int const block_use,
|
||||
char const* const file_name,
|
||||
int const line_number
|
||||
)
|
||||
{
|
||||
return common_dupenv_s(buffer_pointer, buffer_count, name, block_use, file_name, line_number);
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl _wdupenv_s_dbg(
|
||||
wchar_t** const buffer_pointer,
|
||||
size_t* const buffer_count,
|
||||
wchar_t const* const name,
|
||||
int const block_use,
|
||||
char const* const file_name,
|
||||
int const line_number
|
||||
)
|
||||
{
|
||||
return common_dupenv_s(buffer_pointer, buffer_count, name, block_use, file_name, line_number);
|
||||
}
|
||||
|
||||
#endif // _DEBUG
|
159
sdk/lib/ucrt/env/getpath.cpp
vendored
Normal file
159
sdk/lib/ucrt/env/getpath.cpp
vendored
Normal file
|
@ -0,0 +1,159 @@
|
|||
/***
|
||||
*getpath.c - extract a pathname from an environment variable
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
*Purpose:
|
||||
* Extract pathnames from a string of semicolon delimited pathnames
|
||||
* (generally the value of an environment variable such as PATH).
|
||||
*
|
||||
*******************************************************************************/
|
||||
|
||||
#include <corecrt_internal.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/***
|
||||
*_getpath() - extract a pathname from a semicolon-delimited list of pathnames
|
||||
*
|
||||
*Purpose:
|
||||
* To extract the next pathname from a semicolon-delimited list of
|
||||
* pathnames (usually the value on an environment variable) and copy
|
||||
* it to a caller-specified buffer. No check is done to see if the path
|
||||
* is valid. The maximum number of characters copied to the buffer is
|
||||
* maxlen - 1 (and then a '\0' is appended).
|
||||
*
|
||||
*ifdef _HPFS_
|
||||
* If we hit a quoted string, then allow any characters inside.
|
||||
* For example, to put a semi-colon in a path, the user could have
|
||||
* an environment variable that looks like:
|
||||
*
|
||||
* PATH=C:\BIN;"D:\CRT\TOOLS;B1";C:\BINP
|
||||
*endif
|
||||
*
|
||||
* NOTE: Semi-colons in sequence are skipped over; pointers to 0-length
|
||||
* pathnames are NOT returned (this includes leading semi-colons).
|
||||
*
|
||||
* NOTE: If this routine is made user-callable, the near attribute
|
||||
* must be replaced by _LOAD_DS and the prototype moved from INTERNAL.H
|
||||
* to STDLIB.H. The source files MISC\SEARCHEN.C and EXEC\SPAWNVPE.C
|
||||
* will need to be recompiled, but should not require any changes.
|
||||
*
|
||||
*Entry:
|
||||
* src - Pointer to a string of 0 or more path specificiations,
|
||||
* delimited by semicolons (';'), and terminated by a null
|
||||
* character
|
||||
* dst - Pointer to the buffer where the next path specification is to
|
||||
* be copied
|
||||
* maxlen - Maximum number of characters to be copied, counting the
|
||||
* terminating null character. Note that a value of 0 is treated
|
||||
* as UINT_MAX + 1.
|
||||
*
|
||||
*Exit:
|
||||
* If a pathname is successfully extracted and copied, a pointer to the
|
||||
* first character of next pathname is returned (intervening semicolons
|
||||
* are skipped over). If the pathname is too long, as much as possible
|
||||
* is copied to the user-specified buffer and nullptr is returned.
|
||||
*
|
||||
* Note that the no check is made of the validity of the copied pathname.
|
||||
*
|
||||
*Exceptions:
|
||||
*
|
||||
*******************************************************************************/
|
||||
template <typename Character>
|
||||
_Success_(return != 0)
|
||||
static Character* __cdecl common_getpath(
|
||||
_In_z_ Character const* const delimited_paths,
|
||||
_Out_writes_z_(result_count) Character* const result,
|
||||
size_t const result_count
|
||||
) throw()
|
||||
{
|
||||
_VALIDATE_RETURN_NOEXC(result != nullptr, EINVAL, nullptr);
|
||||
|
||||
if (result_count > 0)
|
||||
{
|
||||
result[0] = '\0';
|
||||
}
|
||||
|
||||
_VALIDATE_RETURN_NOEXC(result_count > 1, EINVAL, nullptr);
|
||||
|
||||
Character const* source_it = delimited_paths;
|
||||
|
||||
// Skip past any leading semicolons:
|
||||
while (*source_it == ';')
|
||||
{
|
||||
++source_it;
|
||||
}
|
||||
|
||||
Character const* const source_first = source_it;
|
||||
|
||||
Character* result_it = result;
|
||||
Character* const result_last = result + result_count - 1; // Leave room for \0
|
||||
|
||||
#pragma warning(suppress:__WARNING_POTENTIAL_BUFFER_OVERFLOW_NULLTERMINATED) // 26018 Prefast is confused.
|
||||
while (*source_it != '\0' && *source_it != ';')
|
||||
{
|
||||
if (*source_it == '"')
|
||||
{
|
||||
// We found a quote; copy all characters until we reach the matching
|
||||
// end quote or the end of the string:
|
||||
++source_it; // Advance past opening quote
|
||||
|
||||
while (*source_it != '\0' && *source_it != '"')
|
||||
{
|
||||
#pragma warning(suppress:__WARNING_POTENTIAL_BUFFER_OVERFLOW_HIGH_PRIORITY) // 26015 Prefast is confused.
|
||||
*result_it++ = *source_it++;
|
||||
if (result_it == result_last)
|
||||
{
|
||||
*result_it = '\0';
|
||||
errno = ERANGE;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (*source_it != '\0')
|
||||
{
|
||||
++source_it; // Advance past closing quote
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
*result_it++ = *source_it++;
|
||||
if (result_it == result_last)
|
||||
{
|
||||
*result_it = '\0';
|
||||
errno = ERANGE;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we copied something and stopped because we reached a semicolon, skip
|
||||
// any semicolons before returning:
|
||||
while (*source_it == ';')
|
||||
{
|
||||
++source_it;
|
||||
}
|
||||
|
||||
*result_it = '\0';
|
||||
return source_it == source_first
|
||||
? nullptr
|
||||
: const_cast<Character*>(source_it);
|
||||
}
|
||||
|
||||
extern "C" char* __cdecl __acrt_getpath(
|
||||
char const* const delimited_paths,
|
||||
char* const result,
|
||||
size_t const result_count
|
||||
)
|
||||
{
|
||||
return common_getpath(delimited_paths, result, result_count);
|
||||
}
|
||||
|
||||
extern "C" wchar_t* __cdecl __acrt_wgetpath(
|
||||
wchar_t const* const delimited_paths,
|
||||
wchar_t* const result,
|
||||
size_t const result_count
|
||||
)
|
||||
{
|
||||
return common_getpath(delimited_paths, result, result_count);
|
||||
}
|
243
sdk/lib/ucrt/env/putenv.cpp
vendored
Normal file
243
sdk/lib/ucrt/env/putenv.cpp
vendored
Normal file
|
@ -0,0 +1,243 @@
|
|||
//
|
||||
// putenv.cpp
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Defines the putenv() family of functions, which add, replace, or remove an
|
||||
// environment variable from the current process environment.
|
||||
//
|
||||
#include <corecrt_internal.h>
|
||||
#include <corecrt_internal_traits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
|
||||
// These functions test to see if the other environment exists
|
||||
static bool other_environment_exists(wchar_t) throw() { return _environ_table.value() != nullptr; }
|
||||
static bool other_environment_exists(char) throw() { return _wenviron_table.value() != nullptr; }
|
||||
|
||||
|
||||
|
||||
// This function computes the required size of the buffer that will store a
|
||||
// "name=value" string in the other environment.
|
||||
template <typename Character>
|
||||
static size_t compute_required_transform_buffer_count(
|
||||
Character const* const name,
|
||||
Character const* const value
|
||||
) throw()
|
||||
{
|
||||
// Compute the amount of space required for the transformation:
|
||||
size_t const name_count_required = __crt_compute_required_transform_buffer_count(CP_ACP, name);
|
||||
_VALIDATE_RETURN_NOEXC(name_count_required != 0, EILSEQ, false);
|
||||
|
||||
if (!value)
|
||||
return name_count_required;
|
||||
|
||||
size_t const value_count_required = __crt_compute_required_transform_buffer_count(CP_ACP, value);
|
||||
_VALIDATE_RETURN_NOEXC(value_count_required != 0, EILSEQ, false);
|
||||
|
||||
// Note that each count includes space a the null terminator. Since we'll
|
||||
// only be storing one terminator in the buffer, the space for the other
|
||||
// terminator will be used to store the '=' between the name and the value.
|
||||
return name_count_required + value_count_required;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Constructs an environment string of the form "name=value" from a {name, value}
|
||||
// pair. Returns a pointer to the resulting string. The string is dynamically
|
||||
// allocated and must be freed by the caller (via the CRT free).
|
||||
template <typename Character>
|
||||
static Character* create_environment_string(
|
||||
Character const* const name,
|
||||
Character const* const value
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
if (value)
|
||||
{
|
||||
size_t const name_length = traits::tcsnlen(name, _MAX_ENV);
|
||||
size_t const value_length = traits::tcsnlen(value, _MAX_ENV);
|
||||
|
||||
_VALIDATE_RETURN(name_length < _MAX_ENV, EINVAL, nullptr);
|
||||
_VALIDATE_RETURN(value_length < _MAX_ENV, EINVAL, nullptr);
|
||||
|
||||
// We add two to the length: one for the '=' and one for the terminator
|
||||
size_t const buffer_count = name_length + 1 + value_length + 1;
|
||||
|
||||
__crt_unique_heap_ptr<Character> buffer(_calloc_crt_t(Character, buffer_count));
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
traits::tcscpy_s(buffer.get(), buffer_count, name);
|
||||
buffer.get()[name_length] = '=';
|
||||
traits::tcscpy_s(buffer.get() + name_length + 1, value_length + 1, value);
|
||||
|
||||
return buffer.detach();
|
||||
}
|
||||
else
|
||||
{
|
||||
Character const* const equal_sign_it = traits::tcschr(name, '=');
|
||||
if (equal_sign_it)
|
||||
{
|
||||
// Validate the length of both the name and the value:
|
||||
_VALIDATE_RETURN(equal_sign_it - name < _MAX_ENV, EINVAL, nullptr);
|
||||
_VALIDATE_RETURN(traits::tcsnlen(equal_sign_it + 1, _MAX_ENV) < _MAX_ENV, EINVAL, nullptr);
|
||||
}
|
||||
|
||||
size_t const buffer_count = traits::tcslen(name) + 1;
|
||||
|
||||
__crt_unique_heap_ptr<Character> buffer(_calloc_crt_t(Character, buffer_count));
|
||||
if (!buffer)
|
||||
return nullptr;
|
||||
|
||||
traits::tcscpy_s(buffer.get(), buffer_count, name);
|
||||
|
||||
return buffer.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Converts the {name, value} pair to the other kind of string (wchar_t => char,
|
||||
// char => wchar_t) and updates the other environment.
|
||||
template <typename Character>
|
||||
static bool __cdecl set_variable_in_other_environment(
|
||||
Character const* const name,
|
||||
Character const* const value
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
typedef typename traits::other_char_type other_char_type;
|
||||
typedef __crt_char_traits<other_char_type> other_traits;
|
||||
|
||||
size_t const buffer_count = compute_required_transform_buffer_count(name, value);
|
||||
|
||||
__crt_unique_heap_ptr<other_char_type> buffer(_calloc_crt_t(other_char_type, buffer_count));
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
size_t const name_written_count = __crt_transform_string(CP_ACP, name, buffer.get(), buffer_count);
|
||||
_VALIDATE_RETURN_NOEXC(name_written_count != 0, EILSEQ, false);
|
||||
|
||||
if (value)
|
||||
{
|
||||
// Overwrite the null terminator with an '=':
|
||||
buffer.get()[name_written_count - 1] = '=';
|
||||
|
||||
size_t const value_written_count = __crt_transform_string(
|
||||
CP_ACP,
|
||||
value,
|
||||
buffer.get() + name_written_count,
|
||||
buffer_count - name_written_count);
|
||||
_VALIDATE_RETURN_NOEXC(value_written_count != 0, EILSEQ, false);
|
||||
}
|
||||
|
||||
return other_traits::set_variable_in_environment_nolock(buffer.detach(), 0) == 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Adds, replaces, or removes a variable in the current environment. For the
|
||||
// functions that take a {name, value} pair, the name is the name of the variable
|
||||
// and the value is the value it is to be given. For the functions that just
|
||||
// take and option, the option is of the form "name=value".
|
||||
//
|
||||
// If the value is an empty string and the name names an existing environment
|
||||
// variable, that variable is removed from the environment. If the value is
|
||||
// a nonempty string and the name names an existing environment variable, the
|
||||
// variable is updated to have the new value. If the value is a nonempty string
|
||||
// and the name does not name an existing environment variable, a new variable
|
||||
// is added to the environment.
|
||||
//
|
||||
// If the required environment does not yet exist, it is created from the other
|
||||
// environment. If both environments exist, the modifications are made to both
|
||||
// of them so that they are kept in sync.
|
||||
//
|
||||
// Returns 0 on success; -1 on failure.
|
||||
template <typename Character>
|
||||
static int __cdecl common_putenv_nolock(
|
||||
Character const* const name,
|
||||
Character const* const value
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
// Ensure that the environment is initialized:
|
||||
if (!_environ_table.value() && !_wenviron_table.value())
|
||||
return -1;
|
||||
|
||||
// At startup, we obtain the "native" flavor of environment strings from the
|
||||
// operating system. So, a "main" program has _environ set and a "wmain"
|
||||
// program has _wenviron set. Only when the user gets or puts the "other"
|
||||
// flavor do we convert it.
|
||||
_VALIDATE_RETURN(name != nullptr, EINVAL, -1);
|
||||
|
||||
__crt_unique_heap_ptr<Character> new_option(create_environment_string(name, value));
|
||||
if (!new_option)
|
||||
return -1;
|
||||
|
||||
if (traits::set_variable_in_environment_nolock(new_option.detach(), 1) != 0)
|
||||
return -1;
|
||||
|
||||
// See if the "other" environment type exists; if it doesn't, we're done.
|
||||
// Otherwise, put the new option into the other environment as well.
|
||||
if (!other_environment_exists(Character()))
|
||||
return 0;
|
||||
|
||||
if (!set_variable_in_other_environment(name, value))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename Character>
|
||||
static int __cdecl common_putenv(
|
||||
Character const* const name,
|
||||
Character const* const value
|
||||
) throw()
|
||||
{
|
||||
int status = 0;
|
||||
|
||||
__acrt_lock(__acrt_environment_lock);
|
||||
__try
|
||||
{
|
||||
status = common_putenv_nolock(name, value);
|
||||
}
|
||||
__finally
|
||||
{
|
||||
__acrt_unlock(__acrt_environment_lock);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" int __cdecl _putenv(char const* const option)
|
||||
{
|
||||
return common_putenv(option, static_cast<char const*>(nullptr));
|
||||
}
|
||||
|
||||
extern "C" int __cdecl _wputenv(wchar_t const* const option)
|
||||
{
|
||||
return common_putenv(option, static_cast<wchar_t const*>(nullptr));
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" errno_t __cdecl _putenv_s(char const* const name, char const* const value)
|
||||
{
|
||||
_VALIDATE_RETURN_ERRCODE(value != nullptr, EINVAL);
|
||||
return common_putenv(name, value) == 0 ? 0 : errno;
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl _wputenv_s(wchar_t const* const name, wchar_t const* const value)
|
||||
{
|
||||
_VALIDATE_RETURN_ERRCODE(value != nullptr, EINVAL);
|
||||
return common_putenv(name, value) == 0 ? 0 : errno;
|
||||
}
|
215
sdk/lib/ucrt/env/searchenv.cpp
vendored
Normal file
215
sdk/lib/ucrt/env/searchenv.cpp
vendored
Normal file
|
@ -0,0 +1,215 @@
|
|||
//
|
||||
// searchenv.cpp
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Defines the searchenv() family of functions, which search for a file in the
|
||||
// paths contained in an environment variable (like %PATH%).
|
||||
//
|
||||
#include <direct.h>
|
||||
#include <corecrt_internal_traits.h>
|
||||
#include <io.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#pragma warning(disable:__WARNING_POSTCONDITION_NULLTERMINATION_VIOLATION) // 26036 Prefast doesn't understand perfect forwarding so annotations are lost.
|
||||
#pragma warning(disable:__WARNING_MISSING_ZERO_TERMINATION2) // 6054 Prefast doesn't understand perfect forwarding so annotations are lost.
|
||||
|
||||
// These functions search for a file in the paths in an environment variable.
|
||||
// The environment variable named 'environment_variable' is retrieved from the
|
||||
// environment. It is expected to contain a semicolon-delimited sequence of
|
||||
// directories, similar to %PATH%. The file name is concatenated onto the end
|
||||
// of each directory string in sequence and its existence is tested for. When
|
||||
// a file is found, the search stops and the buffer is filled with the full path
|
||||
// to the file. If the file is not found for whatever reason, a zero-length
|
||||
// string is placed in the buffer and an error code is returned.
|
||||
template <typename Character>
|
||||
static errno_t __cdecl common_searchenv_s(
|
||||
_In_z_ Character const* const file_name,
|
||||
_In_z_ Character const* const environment_variable,
|
||||
_Out_writes_z_(result_count) Character* const result_buffer,
|
||||
_In_ size_t const result_count
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
_VALIDATE_RETURN_ERRCODE(result_buffer != nullptr, EINVAL);
|
||||
_VALIDATE_RETURN_ERRCODE(result_count > 0, EINVAL);
|
||||
if (!file_name)
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
_VALIDATE_RETURN_ERRCODE(file_name != nullptr, EINVAL);
|
||||
}
|
||||
|
||||
// Special case: If the file name is an empty string, we'll just return an
|
||||
// empty result_buffer and set errno:
|
||||
if (file_name[0] == '\0')
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ENOENT;
|
||||
}
|
||||
|
||||
errno_t saved_errno = errno;
|
||||
int const access_result = traits::taccess_s(file_name, 0);
|
||||
errno = saved_errno;
|
||||
|
||||
// If the file name exists, convert it to a fully qualified result_buffer name:
|
||||
if (access_result == 0)
|
||||
{
|
||||
if (traits::tfullpath(result_buffer, file_name, result_count) == nullptr)
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno; // fullpath will set errno
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Character* path_string = nullptr;
|
||||
if (_ERRCHECK_EINVAL(traits::tdupenv_s_crt(&path_string, nullptr, environment_variable)) != 0 || path_string == nullptr)
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ENOENT; // The environment variable doesn't exist
|
||||
}
|
||||
|
||||
__crt_unique_heap_ptr<Character> const path_string_cleanup(path_string);
|
||||
|
||||
size_t const file_name_length = traits::tcslen(file_name);
|
||||
|
||||
size_t const local_path_count = _MAX_PATH + 4;
|
||||
Character local_path_buffer[local_path_count];
|
||||
|
||||
size_t path_count = local_path_count;
|
||||
Character* path_buffer = local_path_buffer;
|
||||
if (file_name_length >= result_count)
|
||||
{
|
||||
// The local buffer is not large enough; dynamically allocate a new
|
||||
// buffer. We add two to the size to account for a trailing slash
|
||||
// that we may need to add and for the null terminator:
|
||||
path_count = traits::tcslen(path_string) + file_name_length + 2;
|
||||
path_buffer = _calloc_crt_t(Character, path_count).detach(); // We'll retake ownership below
|
||||
if (!path_buffer)
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
__crt_unique_heap_ptr<Character> path_buffer_cleanup(path_buffer == local_path_buffer
|
||||
? nullptr
|
||||
: path_buffer);
|
||||
|
||||
saved_errno = errno;
|
||||
while (path_string)
|
||||
{
|
||||
Character* const previous_path_string = path_string;
|
||||
path_string = traits::tgetpath(path_string, path_buffer, path_count - file_name_length - 1);
|
||||
if (!path_string && path_buffer == local_path_buffer && errno == ERANGE)
|
||||
{
|
||||
// If the getpath operation failed because the buffer was not large
|
||||
// enough, try allocating a larger buffer:
|
||||
size_t const required_count = traits::tcslen(previous_path_string) + file_name_length + 2;
|
||||
|
||||
path_buffer_cleanup = _calloc_crt_t(Character, required_count);
|
||||
if (!path_buffer_cleanup)
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ENOMEM;
|
||||
}
|
||||
|
||||
path_count = required_count;
|
||||
path_buffer = path_buffer_cleanup.get();
|
||||
|
||||
path_string = traits::tgetpath(previous_path_string, path_buffer, path_count - file_name_length);
|
||||
}
|
||||
|
||||
if (!path_string || path_buffer[0] == '\0')
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ENOENT;
|
||||
}
|
||||
|
||||
// The result_buffer now holds a non-empty path name from path_string
|
||||
// and we know that the buffer is large enough to hold the concatenation
|
||||
// of the path with the file name (if not, the call to getpath would
|
||||
// have failed. So, we concatenate the path and file names:
|
||||
size_t path_length = traits::tcslen(path_buffer);
|
||||
Character* path_it = path_buffer + path_length;
|
||||
|
||||
// Add a trailing '\' if one is required:
|
||||
Character const last_character = *(path_it - 1);
|
||||
if (last_character != '/' && last_character != '\\' && last_character != ':')
|
||||
{
|
||||
*path_it++ = '\\';
|
||||
++path_length;
|
||||
}
|
||||
|
||||
// The path_it now points to the character following the trailing '\',
|
||||
// '/', or ':'; this is where we copy the file name:
|
||||
_ERRCHECK(traits::tcscpy_s(path_it, path_count - path_length, file_name));
|
||||
|
||||
// If we can't access the file at this path, it isn't a match:
|
||||
if (traits::taccess_s(path_buffer, 0) != 0)
|
||||
continue;
|
||||
|
||||
// Otherwise, we can access the file, and we copy the full path into the
|
||||
// caller-provided buffer:
|
||||
if (path_length + file_name_length + 1 > result_count)
|
||||
{
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ERANGE;
|
||||
}
|
||||
|
||||
errno = saved_errno;
|
||||
|
||||
_ERRCHECK(traits::tcscpy_s(result_buffer, result_count, path_buffer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we get here, we must have tried every path in the environment and not
|
||||
// found the file name:
|
||||
result_buffer[0] = '\0';
|
||||
return errno = ENOENT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" errno_t __cdecl _searchenv_s(
|
||||
char const* const file_name,
|
||||
char const* const environment_variable,
|
||||
char* const result_buffer,
|
||||
size_t const result_count
|
||||
)
|
||||
{
|
||||
return common_searchenv_s(file_name, environment_variable, result_buffer, result_count);
|
||||
}
|
||||
|
||||
extern "C" errno_t __cdecl _wsearchenv_s(
|
||||
wchar_t const* const file_name,
|
||||
wchar_t const* const environment_variable,
|
||||
wchar_t* const result_buffer,
|
||||
size_t const result_count
|
||||
)
|
||||
{
|
||||
return common_searchenv_s(file_name, environment_variable, result_buffer, result_count);
|
||||
}
|
||||
|
||||
|
||||
|
||||
extern "C" void __cdecl _searchenv(
|
||||
char const* const file_name,
|
||||
char const* const environment_variable,
|
||||
char* const result_buffer
|
||||
)
|
||||
{
|
||||
common_searchenv_s(file_name, environment_variable, result_buffer, _MAX_PATH);
|
||||
}
|
||||
|
||||
extern "C" void __cdecl _wsearchenv(
|
||||
wchar_t const* const file_name,
|
||||
wchar_t const* const environment_variable,
|
||||
wchar_t* const result_buffer
|
||||
)
|
||||
{
|
||||
common_searchenv_s(file_name, environment_variable, result_buffer, _MAX_PATH);
|
||||
}
|
373
sdk/lib/ucrt/env/setenv.cpp
vendored
Normal file
373
sdk/lib/ucrt/env/setenv.cpp
vendored
Normal file
|
@ -0,0 +1,373 @@
|
|||
//
|
||||
// setenv.cpp
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// Internal functions for setting or removing variables from an environment. The
|
||||
// logic for manipulating the environment data structures is split across this
|
||||
// file and environment_initialization.cpp.
|
||||
//
|
||||
#include <corecrt_internal.h>
|
||||
#include <corecrt_internal_traits.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
|
||||
static char**& __cdecl get_environment(char) throw() { return _environ_table.value(); }
|
||||
static wchar_t**& __cdecl get_environment(wchar_t) throw() { return _wenviron_table.value(); }
|
||||
|
||||
static wchar_t**& __cdecl get_other_environment(char) throw() { return _wenviron_table.value(); }
|
||||
static char**& __cdecl get_other_environment(wchar_t) throw() { return _environ_table.value(); }
|
||||
|
||||
static char**& __cdecl get_initial_environment(char) throw() { return __dcrt_initial_narrow_environment; }
|
||||
static wchar_t**& __cdecl get_initial_environment(wchar_t) throw() { return __dcrt_initial_wide_environment; }
|
||||
|
||||
|
||||
|
||||
// Makes a copy of the provided environment and returns the copy. The caller is
|
||||
// responsible for freeing the returned array (using the CRT free). Returns
|
||||
// nullptr on failure; terminates the process on allocation failure.
|
||||
template <typename Character>
|
||||
static Character** __cdecl copy_environment(Character** const old_environment) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
if (!old_environment)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Count the number of environment variables:
|
||||
size_t entry_count = 0;
|
||||
for (Character** it = old_environment; *it; ++it)
|
||||
{
|
||||
++entry_count;
|
||||
}
|
||||
|
||||
// We need one pointer for each string, plus one null pointer at the end:
|
||||
__crt_unique_heap_ptr<Character*> new_environment(_calloc_crt_t(Character*, entry_count + 1));
|
||||
if (!new_environment)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
Character** old_it = old_environment;
|
||||
Character** new_it = new_environment.get();
|
||||
for (; *old_it; ++old_it, ++new_it)
|
||||
{
|
||||
size_t const required_count = traits::tcslen(*old_it) + 1;
|
||||
*new_it = _calloc_crt_t(Character, required_count).detach();
|
||||
if (!*new_it)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
_ERRCHECK(traits::tcscpy_s(*new_it, required_count, *old_it));
|
||||
}
|
||||
|
||||
return new_environment.detach();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// If the current environment is the initial environment, this function clones
|
||||
// the current environment so that it is not the initial environment. This
|
||||
// should be called any time that we are about to modify the current environment
|
||||
// but we do not know whether the current environment is the initial environment.
|
||||
template <typename Character>
|
||||
static void __cdecl ensure_current_environment_is_not_initial_environment_nolock() throw()
|
||||
{
|
||||
if (get_environment(Character()) == get_initial_environment(Character()))
|
||||
{
|
||||
get_environment(Character()) = copy_environment(get_environment(Character()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Finds an environment variable in the specified environment. If a variable
|
||||
// with the given name is found, its index in the environment is returned. If
|
||||
// no such environment is found, the total number of environment variables is
|
||||
// returned, multiplied by -1. Note that a return value of 0 may indicate
|
||||
// either that the variable was found at index 0 or there are zero variables
|
||||
// in the environment. Be sure to check for this case.
|
||||
template <typename Character>
|
||||
static ptrdiff_t __cdecl find_in_environment_nolock(
|
||||
Character const* const name,
|
||||
size_t const length
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
Character** const environment = get_environment(Character());
|
||||
|
||||
Character** it = nullptr;
|
||||
for (it = environment; *it; ++it)
|
||||
{
|
||||
// See if the first 'length' characters match:
|
||||
if (traits::tcsnicoll(name, *it, length) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure that the next character of the environment is an '=' or '\0':
|
||||
if ((*it)[length] != '=' && (*it)[length] != '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, this entry matched; return its index in the environment:
|
||||
return static_cast<ptrdiff_t>(it - environment);
|
||||
}
|
||||
|
||||
// No entry matched; return the total number of strings, multiplied by -1:
|
||||
return -static_cast<ptrdiff_t>(it - environment);
|
||||
}
|
||||
|
||||
|
||||
/***
|
||||
*int __dcrt_set_variable_in_narrow_environment(option) - add/replace/remove variable in environment
|
||||
*
|
||||
*Purpose:
|
||||
* option should be of the form "option=value". If a string with the
|
||||
* given option part already exists, it is replaced with the given
|
||||
* string; otherwise the given string is added to the environment.
|
||||
* If the string is of the form "option=", then the string is
|
||||
* removed from the environment, if it exists. If the string has
|
||||
* no equals sign, error is returned.
|
||||
*
|
||||
*Entry:
|
||||
* TCHAR **poption - pointer to option string to set in the environment list.
|
||||
* should be of the form "option=value".
|
||||
* This function takes ownership of this pointer in the success case.
|
||||
* int primary - Only the primary call to _crt[w]setenv needs to
|
||||
* create new copies or set the OS environment.
|
||||
* 1 indicates that this is the primary call.
|
||||
*
|
||||
*Exit:
|
||||
* returns 0 if OK, -1 if fails.
|
||||
* If *poption is non-null on exit, we did not free it, and the caller should
|
||||
* If *poption is null on exit, we did free it, and the caller should not.
|
||||
*
|
||||
*Exceptions:
|
||||
*
|
||||
*Warnings:
|
||||
* This code will not work if variables are removed from the environment
|
||||
* by deleting them from environ[]. Use _putenv("option=") to remove a
|
||||
* variable.
|
||||
*
|
||||
* The option argument will be taken ownership of by this code and may be freed!
|
||||
*
|
||||
*******************************************************************************/
|
||||
template <typename Character>
|
||||
static int __cdecl common_set_variable_in_environment_nolock(
|
||||
Character* const option,
|
||||
int const is_top_level_call
|
||||
) throw()
|
||||
{
|
||||
typedef __crt_char_traits<Character> traits;
|
||||
|
||||
// Check that the option string is valid first. Find the '=' and verify
|
||||
// that '=' is not the first character in the string:
|
||||
_VALIDATE_RETURN_NOEXC(option != nullptr, EINVAL, -1);
|
||||
__crt_unique_heap_ptr<Character> owned_option(option);
|
||||
|
||||
Character* const equal_sign = traits::tcschr(option, '=');
|
||||
_VALIDATE_RETURN_NOEXC(equal_sign != nullptr && equal_sign != option, EINVAL, -1);
|
||||
|
||||
// Internal consistency check: The environment string should never use
|
||||
// buffers larger than _MAX_ENV. See also the SetEnvironmentVariable SDK
|
||||
// function.
|
||||
_ASSERTE(equal_sign - option < _MAX_ENV);
|
||||
_ASSERTE(traits::tcsnlen(equal_sign + 1, _MAX_ENV) < _MAX_ENV);
|
||||
|
||||
// If the character following '=' is the terminator, we are removing the
|
||||
// environment variable. Otherwise, we are adding or updating the variable:
|
||||
bool const is_removal = *(equal_sign + 1) == '\0';
|
||||
|
||||
// At program startup, the initial environment (__dcrt_initial_narrow_environment), which is passed
|
||||
// to main(), is backed by the same environment arrays as the global
|
||||
// environment used by getenv, setenv, et al. We cannot modify thie initial
|
||||
// environment, so we make a copy of it the first time we need to make any
|
||||
// modifications to the global environment:
|
||||
ensure_current_environment_is_not_initial_environment_nolock<Character>();
|
||||
|
||||
// If the required environment does not exist, see if the other environment
|
||||
// exists; if it does, convert it to create the required environment. These
|
||||
// functions will reenter this function once for each environment variable;
|
||||
// we use the top-level call flag to stop recursion.
|
||||
if (!get_environment(Character()))
|
||||
{
|
||||
if (is_top_level_call && get_other_environment(Character()))
|
||||
{
|
||||
_VALIDATE_RETURN_NOEXC(traits::get_or_create_environment_nolock() != nullptr, EINVAL, -1);
|
||||
|
||||
// The call to get_or_create_environment() may have initialized the
|
||||
// current environment to the same environment that is the initial
|
||||
// environment. Re-check and make a new copy of the environment to
|
||||
// modify if necessary.
|
||||
ensure_current_environment_is_not_initial_environment_nolock<Character>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the environment doesn't exist and the requested operation is a
|
||||
// removal, there is nothing to do (there is nothing to remove):
|
||||
if (is_removal)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create a new environment for each environment that does not exist.
|
||||
// Just start each off as an empty environment:
|
||||
if (!_environ_table.value())
|
||||
{
|
||||
_environ_table.value() = _calloc_crt_t(char*, 1).detach();
|
||||
}
|
||||
|
||||
if (!_environ_table.value())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!_wenviron_table.value())
|
||||
{
|
||||
_wenviron_table.value() = _calloc_crt_t(wchar_t*, 1).detach();
|
||||
}
|
||||
|
||||
if (!_wenviron_table.value())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, either [1] only one environment exists, or [2] both of the
|
||||
// environments exist and are in-sync. The only way they can get out of sync
|
||||
// is if there are conversion problems. For example, if the user sets two
|
||||
// Unicode environment variables, FOO1 and FOO2, and the conversion of these
|
||||
// to multibyte yields FOO? and FOO?, then these environment blocks will
|
||||
// differ.
|
||||
Character** const environment = get_environment(Character());
|
||||
if (!environment)
|
||||
{
|
||||
_ASSERTE(("CRT logic error in setenv", 0));
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Try to find the option in the environment...
|
||||
ptrdiff_t const option_index = find_in_environment_nolock(option, equal_sign - option);
|
||||
|
||||
// ... if the string is already in the environment, we free up the original
|
||||
// string, then install the new string or shrink the environment:
|
||||
if (option_index >= 0 && environment[0])
|
||||
{
|
||||
_free_crt(environment[option_index]);
|
||||
|
||||
// If this is a removal, shrink the environment:
|
||||
if (is_removal)
|
||||
{
|
||||
// Shift all of the entries down by one element:
|
||||
size_t i = static_cast<size_t>(option_index);
|
||||
for (; environment[i]; ++i)
|
||||
{
|
||||
environment[i] = environment[i + 1];
|
||||
}
|
||||
|
||||
// Shrink the environment memory block. At this point, i is the
|
||||
// number of elements remaining in the environment. This realloc
|
||||
// should never fail, since we are shrinking the block, but it is
|
||||
// best to be careful. If it does fail, it doesn't matter.
|
||||
Character** new_environment = _recalloc_crt_t(Character*, environment, i).detach();
|
||||
if (new_environment)
|
||||
{
|
||||
get_environment(Character()) = new_environment;
|
||||
}
|
||||
}
|
||||
// If this is a replacement, replace the variable:
|
||||
else
|
||||
{
|
||||
environment[option_index] = owned_option.detach();
|
||||
}
|
||||
}
|
||||
// Otherwise, the string is not in the environment:
|
||||
else
|
||||
{
|
||||
// If this is a removal, it is a no-op: the variable does not exist.
|
||||
if (is_removal)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// Otherwise, we need to append the string to the environment table, and
|
||||
// we must grow the table to do this:
|
||||
else
|
||||
{
|
||||
size_t const environment_count = static_cast<size_t>(-option_index);
|
||||
if (environment_count + 2 < environment_count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (environment_count + 2 >= SIZE_MAX / sizeof(Character*))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
Character** const new_environment = _recalloc_crt_t(Character*, environment, environment_count + 2).detach();
|
||||
if (!new_environment)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
new_environment[environment_count] = owned_option.detach();
|
||||
new_environment[environment_count + 1] = nullptr;
|
||||
|
||||
get_environment(Character()) = new_environment;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the operating system environment. Do not give an error if this
|
||||
// fails since the failure will not affect the user code unless it is making
|
||||
// direct calls to the operating system. We only need to do this for one of
|
||||
// the environments; the operating system synchronizes with the other
|
||||
// environment automatically.
|
||||
if (is_top_level_call)
|
||||
{
|
||||
size_t const count = traits::tcslen(option) + 2;
|
||||
__crt_unique_heap_ptr<Character> const buffer(_calloc_crt_t(Character, count));
|
||||
if (!buffer)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Character* const name = buffer.get();
|
||||
_ERRCHECK(traits::tcscpy_s(name, count, option));
|
||||
|
||||
Character* const value = name + (equal_sign - option) + 1;
|
||||
*(value - 1) = '\0'; // Overwrite the '=' with a null terminator
|
||||
|
||||
if (traits::set_environment_variable(name, is_removal ? nullptr : value) == 0)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int __cdecl __dcrt_set_variable_in_narrow_environment_nolock(
|
||||
char* const option,
|
||||
int const is_top_level_call
|
||||
)
|
||||
{
|
||||
return common_set_variable_in_environment_nolock(option, is_top_level_call);
|
||||
}
|
||||
|
||||
extern "C" int __cdecl __dcrt_set_variable_in_wide_environment_nolock(
|
||||
wchar_t* const option,
|
||||
int const is_top_level_call
|
||||
)
|
||||
{
|
||||
return common_set_variable_in_environment_nolock(option, is_top_level_call);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue