Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions localization/strings/en-US/Resources.resw
Original file line number Diff line number Diff line change
Expand Up @@ -1935,6 +1935,15 @@ Usage:
<value>Session termination failed: '{}'</value>
<comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
</data>
<data name="MessageWslcDefaultSessionNotFound" xml:space="preserve">
<value>Default session not found</value>
</data>
<data name="MessageWslcOpenDefaultSessionFailed" xml:space="preserve">
<value>Failed to open default session</value>
</data>
<data name="MessageWslcTerminateDefaultSessionFailed" xml:space="preserve">
<value>Default session termination failed</value>
</data>
Comment on lines +1938 to +1946
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New user-facing resource keys were added only to en-US/Resources.resw. Other locales currently lack these entries (the localization validator only warns), which can cause missing translations/fallback-to-English behavior for non-en-US users. Consider adding placeholder entries for these keys in the other locale .resw files so localization can pick them up consistently.

Copilot uses AI. Check for mistakes.
<data name="MessageWslcShellExited" xml:space="preserve">
<value>{} exited with: {}</value>
<comment>{FixedPlaceholder="{}"}{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
Expand Down
11 changes: 10 additions & 1 deletion src/windows/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ set(SOURCES
WslTelemetry.cpp
wslutil.cpp
install.cpp
WSLCUserSettings.cpp
)

set(HEADERS
Expand Down Expand Up @@ -130,11 +131,19 @@ set(HEADERS
WslSecurity.h
WslTelemetry.h
wslutil.h
EnumVariantMap.h
WSLCUserSettings.h
WSLCSessionDefaults.h
)

add_library(common STATIC ${SOURCES} ${HEADERS})
add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl)
add_dependencies(common wslserviceidl localization wslservicemc wslinstalleridl yaml-cpp)

target_precompile_headers(common PRIVATE precomp.h)
set_target_properties(common PROPERTIES FOLDER windows)
target_include_directories(common PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/../service/mc/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE})

# WSLCUserSettings.cpp uses yaml-cpp headers.
set_source_files_properties(WSLCUserSettings.cpp PROPERTIES
INCLUDE_DIRECTORIES "${yaml-cpp_SOURCE_DIR}/include"
COMPILE_DEFINITIONS "YAML_CPP_STATIC_DEFINE")
25 changes: 25 additions & 0 deletions src/windows/common/WSLCSessionDefaults.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*++

Copyright (c) Microsoft. All rights reserved.

Module Name:

WSLCSessionDefaults.h

Abstract:

Shared constants for WSLc session naming and storage.

--*/
#pragma once

#include <cstdint>

namespace wsl::windows::wslc {

inline constexpr const wchar_t DefaultSessionName[] = L"wslc-cli";
inline constexpr const wchar_t DefaultAdminSessionName[] = L"wslc-cli-admin";
inline constexpr const wchar_t DefaultStorageSubPath[] = L"wslc\\sessions";
inline constexpr uint32_t DefaultBootTimeoutMs = 30000;

} // namespace wsl::windows::wslc
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ Copyright (c) Microsoft. All rights reserved.

Module Name:

UserSettings.cpp
WSLCUserSettings.cpp

Abstract:

Implementation of UserSettings — YAML loading and validation.

--*/
#include "UserSettings.h"
#include "precomp.h"
#include "WSLCUserSettings.h"
#include "filesystem.hpp"
#include "string.hpp"
#include "wslutil.h"

#pragma warning(push)
#pragma warning(disable : 4251 4275)
#include <yaml-cpp/yaml.h>
#pragma warning(pop)
#include <algorithm>
#include <format>
#include <fstream>
Expand All @@ -25,7 +30,6 @@ using namespace wsl::windows::common::string;

namespace wsl::windows::wslc::settings {

// Default settings file template — written on first run.
// All entries are commented out; the values shown are the built-in defaults.
// TODO: localization for comments needed?
static constexpr std::string_view s_DefaultSettingsTemplate =
Expand Down Expand Up @@ -239,6 +243,22 @@ UserSettings const& UserSettings::Instance()
return instance;
}

WSLCFeatureFlags UserSettings::BuildFeatureFlags() const
{
WSLCFeatureFlags flags = WslcFeatureFlagsNone;
if (Get<Setting::SessionHostFileShareMode>() == HostFileShareMode::VirtioFs)
{
WI_SetFlag(flags, WslcFeatureFlagsVirtioFs);
}

if (Get<Setting::SessionDnsTunneling>())
{
WI_SetFlag(flags, WslcFeatureFlagsDnsTunneling);
}

return flags;
}

UserSettings::UserSettings() : UserSettings(SettingsDir())
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright (c) Microsoft. All rights reserved.

Module Name:

UserSettings.h
WSLCUserSettings.h

Abstract:

Expand Down Expand Up @@ -137,6 +137,9 @@ class UserSettings
return m_settings.GetOrDefault<S>();
}

// Builds WSLCFeatureFlags from the current settings.
WSLCFeatureFlags BuildFeatureFlags() const;

std::vector<Warning> const& GetWarnings() const
{
return m_warnings;
Expand All @@ -156,9 +159,7 @@ class UserSettings
// Overwrites the settings file with the commented-out defaults template.
void Reset() const;

protected:
// Loads settings from an explicit directory. Used by the singleton (via
// the private zero-arg constructor) and by test subclasses.
// Loads settings from an explicit directory.
explicit UserSettings(const std::filesystem::path& settingsDir);
~UserSettings() = default;

Expand Down
3 changes: 2 additions & 1 deletion src/windows/service/exe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ target_link_libraries(wslservice
legacy_stdio_definitions
VirtDisk.lib
Winhttp.lib
Synchronization.lib)
Synchronization.lib
yaml-cpp)

target_precompile_headers(wslservice REUSE_FROM common)
set_target_properties(wslservice PROPERTIES FOLDER windows)
144 changes: 127 additions & 17 deletions src/windows/service/exe/WSLCSessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,71 @@ Module Name:

#include "WSLCSessionManager.h"
#include "HcsVirtualMachine.h"
#include "WSLCUserSettings.h"
#include "WSLCSessionDefaults.h"
#include "wslutil.h"
#include "filesystem.hpp"

using wsl::windows::service::wslc::CallingProcessTokenInfo;
using wsl::windows::service::wslc::HcsVirtualMachine;
using wsl::windows::service::wslc::WSLCSessionManagerImpl;
namespace wslutil = wsl::windows::common::wslutil;
namespace settings = wsl::windows::wslc::settings;

namespace {

// Session settings built server-side from the caller's settings.yaml.
struct SessionSettings
{
std::wstring DisplayName;
std::wstring StoragePath;
WSLCSessionSettings Settings{};

NON_COPYABLE(SessionSettings);
NON_MOVABLE(SessionSettings);

// Default session: name and storage path determined from caller's token.
static std::unique_ptr<SessionSettings> Default(HANDLE UserToken, bool Elevated)
{
auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken);
settings::UserSettings userSettings(localAppData / L"wslc");

std::wstring name = Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;

auto customPath = userSettings.Get<settings::Setting::SessionStoragePath>();
std::filesystem::path basePath =
customPath.empty() ? (localAppData / wsl::windows::wslc::DefaultStorageSubPath) : std::filesystem::path{customPath};
auto storagePath = (basePath / name).wstring();

return std::unique_ptr<SessionSettings>(
new SessionSettings(std::move(name), std::move(storagePath), WSLCSessionStorageFlagsNone, userSettings));
}

// Custom session: caller provides name and storage path.
static SessionSettings Custom(HANDLE UserToken, LPCWSTR Name, LPCWSTR Path, WSLCSessionStorageFlags StorageFlags = WSLCSessionStorageFlagsNone)
{
auto localAppData = wsl::windows::common::filesystem::GetLocalAppDataPath(UserToken);
settings::UserSettings userSettings(localAppData / L"wslc");
return SessionSettings(Name, Path, StorageFlags, userSettings);
}

private:
SessionSettings(std::wstring name, std::wstring path, WSLCSessionStorageFlags storageFlags, const settings::UserSettings& userSettings) :
DisplayName(std::move(name)), StoragePath(std::move(path))
{
Settings.DisplayName = DisplayName.c_str();
Settings.StoragePath = StoragePath.c_str();
Settings.CpuCount = userSettings.Get<settings::Setting::SessionCpuCount>();
Settings.MemoryMb = userSettings.Get<settings::Setting::SessionMemoryMb>();
Settings.MaximumStorageSizeMb = userSettings.Get<settings::Setting::SessionStorageSizeMb>();
Settings.BootTimeoutMs = wsl::windows::wslc::DefaultBootTimeoutMs;
Settings.NetworkingMode = userSettings.Get<settings::Setting::SessionNetworkingMode>();
Settings.FeatureFlags = userSettings.BuildFeatureFlags();
Settings.StorageFlags = storageFlags;
}
};

} // namespace

WSLCSessionManagerImpl::~WSLCSessionManagerImpl()
{
Expand All @@ -51,22 +110,42 @@ WSLCSessionManagerImpl::~WSLCSessionManagerImpl()

void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, IWSLCSession** WslcSession)
{
// Ensure that the session display name is non-null and not too long.
THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr);
THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName));
THROW_HR_IF_MSG(
E_INVALIDARG,
WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid),
"Invalid storage flags: %i",
Settings->StorageFlags);

auto tokenInfo = GetCallingProcessTokenInfo();
const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);

// Resolve display name upfront (for both default and custom sessions).
std::wstring resolvedDisplayName;
if (Settings == nullptr)
{
// Default session: name determined from token.
resolvedDisplayName = tokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;
Flags = WSLCSessionFlagsOpenExisting | WSLCSessionFlagsPersistent;
}
else
{
THROW_HR_IF(E_INVALIDARG, Settings->DisplayName == nullptr || wcslen(Settings->DisplayName) == 0);
THROW_HR_IF(E_INVALIDARG, Settings->StoragePath != nullptr && wcslen(Settings->StoragePath) == 0);
THROW_HR_IF(E_INVALIDARG, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName));
THROW_HR_IF_MSG(
E_INVALIDARG,
WI_IsAnyFlagSet(Settings->StorageFlags, ~WSLCSessionStorageFlagsValid),
"Invalid storage flags: %i",
Settings->StorageFlags);

// Reserved names can only be assigned server-side via null Settings.
THROW_HR_IF(
E_ACCESSDENIED,
wsl::shared::string::IsEqual(Settings->DisplayName, wsl::windows::wslc::DefaultSessionName, true) ||
wsl::shared::string::IsEqual(Settings->DisplayName, wsl::windows::wslc::DefaultAdminSessionName, true));

resolvedDisplayName = Settings->DisplayName;
}

std::lock_guard lock(m_wslcSessionsLock);

// Check for an existing session first.
auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), Settings->DisplayName))
if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), resolvedDisplayName.c_str()))
{
return {};
}
Expand All @@ -88,6 +167,14 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,

wslutil::StopWatch stopWatch;

// Initialize settings for the default session.
std::unique_ptr<SessionSettings> defaultSettings;
if (Settings == nullptr)
{
defaultSettings = SessionSettings::Default(callerToken.get(), tokenInfo.Elevated);
Settings = &defaultSettings->Settings;
}
Comment on lines 144 to +176
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CreateSession holds m_wslcSessionsLock while loading/parsing user settings (YAML) for default sessions (SessionSettings::Default(...)). This introduces file I/O and YAML parsing under the session manager lock, which can block other session operations and increase contention. Consider building the resolved/default SessionSettings outside the lock (or caching per-user settings) and only holding the lock for the session lookup/mutation portion.

Copilot uses AI. Check for mistakes.

HRESULT creationResult = wil::ResultFromException([&]() {
// Get caller info.
const auto callerProcess = wslutil::OpenCallingProcess(PROCESS_QUERY_LIMITED_INFORMATION);
Expand All @@ -103,13 +190,13 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
AddSessionProcessToJobObject(factory.get());

// Create the session via the factory.
const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings);
const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str());
wil::com_ptr<IWSLCSession> session;
wil::com_ptr<IWSLCSessionReference> serviceRef;
THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), &session, &serviceRef));

// Track the session via its service ref, along with metadata and security info.
m_sessions.push_back({std::move(serviceRef), sessionId, creatorPid, Settings->DisplayName, std::move(tokenInfo)});
m_sessions.push_back({std::move(serviceRef), sessionId, creatorPid, resolvedDisplayName, std::move(tokenInfo)});

// For persistent sessions, also hold a strong reference to keep them alive.
const bool persistent = WI_IsFlagSet(Flags, WSLCSessionFlagsPersistent);
Expand All @@ -122,19 +209,18 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
});

// This telemetry event is used to keep track of session creation performance (via CreationTimeMs) and failure reasons (via Result).

WSL_LOG_TELEMETRY(
"WSLCCreateSession",
PDT_ProductAndServicePerformance,
TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA),
TraceLoggingValue(Settings->DisplayName, "Name"),
TraceLoggingValue(resolvedDisplayName.c_str(), "Name"),
TraceLoggingValue(stopWatch.ElapsedMilliseconds(), "CreationTimeMs"),
TraceLoggingValue(creationResult, "Result"),
TraceLoggingValue(tokenInfo.Elevated, "Elevated"),
TraceLoggingValue(static_cast<uint32_t>(Flags), "Flags"),
TraceLoggingLevel(WINEVENT_LEVEL_INFO));

THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", Settings->DisplayName);
THROW_IF_FAILED_MSG(creationResult, "Failed to create session: %ls", resolvedDisplayName.c_str());
}

void WSLCSessionManagerImpl::OpenSession(ULONG Id, IWSLCSession** Session)
Expand All @@ -160,6 +246,14 @@ void WSLCSessionManagerImpl::OpenSessionByName(LPCWSTR DisplayName, IWSLCSession
{
auto tokenInfo = GetCallingProcessTokenInfo();

// Null name = default session, resolved from caller's token.
std::wstring resolvedName;
if (DisplayName == nullptr)
{
resolvedName = tokenInfo.Elevated ? wsl::windows::wslc::DefaultAdminSessionName : wsl::windows::wslc::DefaultSessionName;
DisplayName = resolvedName.c_str();
}

auto result = ForEachSession<HRESULT>([&](auto& entry, const wil::com_ptr<IWSLCSession>& session) noexcept -> std::optional<HRESULT> {
if (!wsl::shared::string::IsEqual(entry.DisplayName.c_str(), DisplayName))
{
Expand Down Expand Up @@ -207,12 +301,23 @@ void WSLCSessionManagerImpl::GetVersion(_Out_ WSLCVersion* Version)
Version->Revision = WSL_PACKAGE_VERSION_REVISION;
}

WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings)
void WSLCSessionManagerImpl::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
{
THROW_HR_IF(E_POINTER, DisplayName == nullptr || StoragePath == nullptr);
THROW_HR_IF(E_INVALIDARG, DisplayName[0] == L'\0' || StoragePath[0] == L'\0');

const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);
auto sessionSettings = SessionSettings::Custom(callerToken.get(), DisplayName, StoragePath, WSLCSessionStorageFlagsNoCreate);
CreateSession(&sessionSettings.Settings, WSLCSessionFlagsNone, WslcSession);
}

WSLCSessionInitSettings WSLCSessionManagerImpl::CreateSessionSettings(
_In_ ULONG SessionId, _In_ DWORD CreatorPid, _In_ const WSLCSessionSettings* Settings, _In_ LPCWSTR ResolvedDisplayName)
{
WSLCSessionInitSettings sessionSettings{};
sessionSettings.SessionId = SessionId;
sessionSettings.CreatorPid = CreatorPid;
sessionSettings.DisplayName = Settings->DisplayName;
sessionSettings.DisplayName = ResolvedDisplayName;
sessionSettings.StoragePath = Settings->StoragePath;
sessionSettings.MaximumStorageSizeMb = Settings->MaximumStorageSizeMb;
sessionSettings.BootTimeoutMs = Settings->BootTimeoutMs;
Expand Down Expand Up @@ -292,6 +397,11 @@ HRESULT WSLCSessionManager::CreateSession(const WSLCSessionSettings* WslcSession
return CallImpl(&WSLCSessionManagerImpl::CreateSession, WslcSessionSettings, Flags, WslcSession);
}

HRESULT WSLCSessionManager::EnterSession(_In_ LPCWSTR DisplayName, _In_ LPCWSTR StoragePath, IWSLCSession** WslcSession)
{
return CallImpl(&WSLCSessionManagerImpl::EnterSession, DisplayName, StoragePath, WslcSession);
}

HRESULT WSLCSessionManager::ListSessions(_Out_ WSLCSessionInformation** Sessions, _Out_ ULONG* SessionsCount)
{
return CallImpl(&WSLCSessionManagerImpl::ListSessions, Sessions, SessionsCount);
Expand Down
Loading