Fix WSLENV environment variable duplication in ConptyConnection (#19167)

This PR fixes issue #7130 where WT_SESSION and WT_PROFILE_ID environment
variables were being duplicated in the WSLENV environment variable when
multiple terminal sessions were created.

The previous implementation always appended WT_SESSION:WT_PROFILE_ID: to
WSLENV without checking if these variables already existed, causing
duplication.

Closes #7130

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
This commit is contained in:
Weichen Li 2025-07-29 09:47:06 +08:00 committed by GitHub
parent 65788d9099
commit 7d6e0c8b8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 10 deletions

View File

@ -66,10 +66,41 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
// WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL // WSLENV is a colon-delimited list of environment variables (+flags) that should appear inside WSL
// https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/ // https://devblogs.microsoft.com/commandline/share-environment-vars-between-wsl-and-windows/
std::wstring wslEnv{ L"WT_SESSION:WT_PROFILE_ID:" };
// WSLENV.1: Get a handle to the WSLENV environment variable.
auto& wslEnv = environment.as_map()[L"WSLENV"];
std::wstring additionalWslEnv;
// WSLENV.2: Figure out what variables are already in WSLENV.
std::unordered_set<std::wstring_view> wslEnvVars;
for (const auto& part : til::split_iterator{ std::wstring_view{ wslEnv }, L':' })
{
// Each part may contain a variable name and flags (e.g., /p, /l, etc.)
// We only care about the variable name for WSLENV.
const auto key = til::safe_slice_len(part, 0, part.rfind(L'/'));
wslEnvVars.emplace(key);
}
// WSLENV.3: Add our terminal-specific environment variables to WSLENV.
static constexpr std::wstring_view builtinWslEnvVars[] = {
L"WT_SESSION",
L"WT_PROFILE_ID",
};
// Misdiagnosis in MSVC 14.44.35207. No pointer arithmetic in sight.
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
for (const auto& key : builtinWslEnvVars)
{
if (wslEnvVars.emplace(key).second)
{
additionalWslEnv.append(key);
additionalWslEnv.push_back(L':');
}
}
if (_environment) if (_environment)
{ {
// Order the environment variable names so that resolution order is consistent // Order the environment variable names so that resolution order is consistent
// NOTE(lhecker): I'm like 99% sure that this is unnecessary.
std::set<std::wstring, til::env_key_sorter> keys{}; std::set<std::wstring, til::env_key_sorter> keys{};
for (const auto item : _environment) for (const auto item : _environment)
{ {
@ -85,18 +116,39 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
const auto value = winrt::unbox_value<hstring>(_environment.Lookup(key)); const auto value = winrt::unbox_value<hstring>(_environment.Lookup(key));
environment.set_user_environment_var(key.c_str(), value.c_str()); environment.set_user_environment_var(key.c_str(), value.c_str());
// For each environment variable added to the environment, also add it to WSLENV
wslEnv += key + L":"; // WSLENV.4: Add custom user environment variables to WSLENV.
if (wslEnvVars.emplace(key).second)
{
additionalWslEnv.append(key);
additionalWslEnv.push_back(L':');
}
} }
CATCH_LOG(); CATCH_LOG();
} }
} }
// We want to prepend new environment variables to WSLENV - that way if a variable already if (!additionalWslEnv.empty())
// exists in WSLENV but with a flag, the flag will be respected. {
// (This behaviour was empirically observed) // WSLENV.5: In the next step we'll prepend `additionalWslEnv` to `wslEnv`,
wslEnv += environment.as_map()[L"WSLENV"]; // so make sure that we have a single colon in between them.
environment.as_map().insert_or_assign(L"WSLENV", wslEnv); const auto hasColon = additionalWslEnv.ends_with(L':');
const auto needsColon = !wslEnv.starts_with(L':');
if (hasColon != needsColon)
{
if (hasColon)
{
additionalWslEnv.pop_back();
}
else
{
additionalWslEnv.push_back(L':');
}
}
// WSLENV.6: Prepend our additional environment variables to WSLENV.
wslEnv.insert(0, additionalWslEnv);
}
} }
auto newEnvVars = environment.to_string(); auto newEnvVars = environment.to_string();

View File

@ -55,20 +55,22 @@
namespace til // Terminal Implementation Library. Also: "Today I Learned" namespace til // Terminal Implementation Library. Also: "Today I Learned"
{ {
template<typename T> template<typename T>
as_view_t<T> safe_slice_abs(const T& view, size_t beg, size_t end) as_view_t<T> safe_slice_abs(const T& view, size_t beg, size_t end) noexcept
{ {
const size_t len = view.size(); const size_t len = view.size();
end = std::min(end, len); end = std::min(end, len);
beg = std::min(beg, end); beg = std::min(beg, end);
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
return { view.data() + beg, end - beg }; return { view.data() + beg, end - beg };
} }
template<typename T> template<typename T>
as_view_t<T> safe_slice_len(const T& view, size_t start, size_t count) as_view_t<T> safe_slice_len(const T& view, size_t start, size_t count) noexcept
{ {
const size_t len = view.size(); const size_t len = view.size();
start = std::min(start, len); start = std::min(start, len);
count = std::min(count, len - start); count = std::min(count, len - start);
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
return { view.data() + start, count }; return { view.data() + start, count };
} }