From c0ef1a1f09367bc9e1eb55e73ed9225afc505eb3 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 14:50:14 -0700 Subject: [PATCH 01/25] Add yaml-cpp --- CMakeLists.txt | 11 +++++++++++ src/windows/wslc/CMakeLists.txt | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7332438d8..e72d4a88e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,17 @@ FetchContent_Declare(nlohmannjson FetchContent_MakeAvailable(nlohmannjson) FetchContent_GetProperties(nlohmannjson SOURCE_DIR NLOHMAN_JSON_SOURCE_DIR) +FetchContent_Declare(yaml-cpp + URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.9.0.tar.gz + URL_HASH SHA256=d5558cd419c8d46bdc958064cb97f963d1ea793866414c025906ec15033512ed) + +set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE) +set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "" FORCE) + +FetchContent_MakeAvailable(yaml-cpp) +FetchContent_GetProperties(yaml-cpp SOURCE_DIR YAML_CPP_SOURCE_DIR) + set(BOOST_VERSION "1.90.0") set(BOOST_TARBALL "boost_${BOOST_VERSION}") string(REPLACE "." "_" BOOST_TARBALL "${BOOST_TARBALL}") diff --git a/src/windows/wslc/CMakeLists.txt b/src/windows/wslc/CMakeLists.txt index 7d57ee9c8..c160840c5 100644 --- a/src/windows/wslc/CMakeLists.txt +++ b/src/windows/wslc/CMakeLists.txt @@ -1,4 +1,4 @@ -set(WSLC_SUBDIRS arguments commands core services tasks) +set(WSLC_SUBDIRS arguments commands core services settings tasks) list(TRANSFORM WSLC_SUBDIRS PREPEND ${CMAKE_CURRENT_SOURCE_DIR}/ OUTPUT_VARIABLE WSLC_SUBDIR_PATHS) list(TRANSFORM WSLC_SUBDIR_PATHS APPEND /*.h OUTPUT_VARIABLE HEADER_PATTERNS) @@ -13,7 +13,8 @@ target_include_directories(wslclib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${WSLC_SUB target_link_libraries(wslclib ${COMMON_LINK_LIBRARIES} - common) + common + yaml-cpp) target_precompile_headers(wslclib REUSE_FROM common) set_target_properties(wslclib PROPERTIES FOLDER windows) From e12830e3e0ada4021c164ee854b5c7731b0e893a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:06:41 -0700 Subject: [PATCH 02/25] Settings def --- .../wslc/settings/SettingDefinitions.h | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 src/windows/wslc/settings/SettingDefinitions.h diff --git a/src/windows/wslc/settings/SettingDefinitions.h b/src/windows/wslc/settings/SettingDefinitions.h new file mode 100644 index 000000000..da0680a7f --- /dev/null +++ b/src/windows/wslc/settings/SettingDefinitions.h @@ -0,0 +1,93 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + SettingDefinitions.h + +Abstract: + + Declaration of user settings with their YAML paths, types, and defaults. + +--*/ +#pragma once +#include "EnumVariantMap.h" +#include +#include +#include +#include + +// How to add a setting: +// 1 - Add an entry to the Setting enum. +// 2 - Add a DEFINE_SETTING_MAPPING specialization with yaml_t, value_t, default, and YAML path. +// 3 - Implement the Validate function in UserSettings.cpp. + +namespace wsl::windows::wslc::settings { + +// Enum of all user settings. +// Must start at 0 to enable direct variant indexing. +// Max must be last and unused. +enum class Setting : size_t +{ + SessionCpuCount, + SessionMemoryMb, + SessionStorageSizeMb, + SessionStoragePath, + + Max +}; + +namespace details { + +template +struct SettingMapping +{ + // yaml_t - the C++ type read from the YAML node via node.as() + // value_t - the native type stored in SettingsMap + // DefaultValue - used when the key is absent or fails validation + // YamlPath - dot-separated path into the YAML document (e.g. "session.cpuCount") + // Validate - semantic validation; returns nullopt to reject and fall back to default +}; + +// clang-format off +#define DEFINE_SETTING_MAPPING(_setting_, _yaml_t_, _value_t_, _default_, _path_) \ + template <> \ + struct SettingMapping \ + { \ + using yaml_t = _yaml_t_; \ + using value_t = _value_t_; \ + inline static const value_t DefaultValue = _default_; \ + static constexpr std::string_view YamlPath = _path_; \ + static std::optional Validate(const yaml_t& value); \ + }; + +// Setting yaml_t value_t default YamlPath +DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") +DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 8192, "session.memorySizeMb") +DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 51200, "session.maxStorageSizeMb") +DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") +// clang-format on + +#undef DEFINE_SETTING_MAPPING + +} // namespace details + +// Type-safe enum-indexed map of all settings values, backed by EnumBasedVariantMap. +// Each setting key holds at most one value; Contains(S) indicates whether the setting +// was explicitly loaded from the file (vs. falling back to DefaultValue). +struct SettingsMap : wsl::windows::wslc::EnumBasedVariantMap +{ + // Returns the stored value if present, otherwise the compile-time default. + template + typename details::SettingMapping::value_t GetOrDefault() const + { + if (Contains(S)) + { + return Get(); + } + return details::SettingMapping::DefaultValue; + } +}; + +} // namespace wsl::windows::wslc::settings From 9e951353a156fac2bc46c7a13fd39601816ea5a0 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:36:39 -0700 Subject: [PATCH 03/25] settings header --- src/windows/wslc/settings/UserSettings.h | 88 ++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/windows/wslc/settings/UserSettings.h diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h new file mode 100644 index 000000000..b113e0154 --- /dev/null +++ b/src/windows/wslc/settings/UserSettings.h @@ -0,0 +1,88 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + UserSettings.h + +Abstract: + + Declaration of UserSettings — the singleton that loads, validates, and + provides access to the wslc user settings file. + +--*/ +#pragma once +#include "SettingDefinitions.h" +#include +#include +#include + +namespace wsl::windows::wslc::settings { + +// Indicates which source the settings were loaded from. +enum class UserSettingsType +{ + Default, // Neither settings file existed (first run); built-in defaults are used. + Standard, // Primary file (UserSettings.yaml) loaded successfully. + Backup, // Primary file failed to parse; backup file (UserSettings.yaml.bak) was used. +}; + +struct Warning +{ + std::wstring Message; + std::wstring SettingPath; // Empty for file-level warnings; key path for per-field warnings. +}; + +// Singleton that owns the parsed settings for the current process lifetime. +// Load order: +// 1. UserSettings.yaml (Standard) +// 2. UserSettings.yaml.bak (Backup, if primary fails) +// 3. Built-in defaults (Default, if both fail; no warning if neither file exists) +class UserSettings +{ +public: + // Returns the singleton instance. Loaded on first call; subsequent calls are no-ops. + static UserSettings const& Instance(); + + UserSettings(const UserSettings&) = delete; + UserSettings& operator=(const UserSettings&) = delete; + UserSettings(UserSettings&&) = delete; + UserSettings& operator=(UserSettings&&) = delete; + + SettingsMap const& GetSettings() const + { + return m_settings; + } + + std::vector const& GetWarnings() const + { + return m_warnings; + } + + UserSettingsType GetType() const + { + return m_type; + } + + // Full path to %LOCALAPPDATA%\Microsoft\wslc\UserSettings.yaml + static std::filesystem::path PrimaryFilePath(); + + // Full path to %LOCALAPPDATA%\Microsoft\wslc\UserSettings.yaml.bak + static std::filesystem::path BackupFilePath(); + + // Called before opening the settings file in an editor. + // - If type is Standard: copies the primary file to the backup path. + // - If type is Default: creates the primary file from the commented-out defaults template. + void PrepareToShellExecuteFile() const; + +private: + UserSettings(); + ~UserSettings() = default; + + SettingsMap m_settings; + std::vector m_warnings; + UserSettingsType m_type = UserSettingsType::Default; +}; + +} // namespace wsl::windows::wslc::settings From 546d86fccc7b5de50a969aa5b72435c4736f602a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 15:44:12 -0700 Subject: [PATCH 04/25] Settings cpp --- src/windows/wslc/settings/UserSettings.cpp | 297 +++++++++++++++++++++ src/windows/wslc/settings/UserSettings.h | 4 + 2 files changed, 301 insertions(+) create mode 100644 src/windows/wslc/settings/UserSettings.cpp diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp new file mode 100644 index 000000000..9e6bedbfb --- /dev/null +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -0,0 +1,297 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + UserSettings.cpp + +Abstract: + + Implementation of UserSettings — YAML loading, validation, and fallback logic. + +--*/ +#include "UserSettings.h" +#include "filesystem.hpp" +#include "string.hpp" +#include +#include +#include +#include +#include + +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. +// --------------------------------------------------------------------------- +static constexpr std::string_view s_DefaultSettingsTemplate = + "# wslc user settings\n" + "# https://aka.ms/wslc-settings\n" + "\n" + "session:\n" + " # Number of virtual CPUs allocated to the session (default: 4)\n" + " # cpuCount: 4\n" + "\n" + " # Memory limit for the session in megabytes (default: 8192)\n" + " # memorySizeMb: 8192\n" + "\n" + " # Maximum disk image size in megabytes (default: 51200)\n" + " # maxStorageSizeMb: 51200\n" + "\n" + " # Default path for container storage (default: system managed)\n" + " # defaultStoragePath: \"\"\n"; + +// --------------------------------------------------------------------------- +// Validate specializations +// --------------------------------------------------------------------------- +namespace details { + +#define WSLC_VALIDATE_SETTING(_setting_) \ + std::optional::value_t> \ + SettingMapping::Validate( \ + const SettingMapping::yaml_t& value) + +WSLC_VALIDATE_SETTING(SessionCpuCount) +{ + return value > 0 ? std::optional{value} : std::nullopt; +} + +WSLC_VALIDATE_SETTING(SessionMemoryMb) +{ + return value > 0 ? std::optional{value} : std::nullopt; +} + +WSLC_VALIDATE_SETTING(SessionStorageSizeMb) +{ + return value > 0 ? std::optional{value} : std::nullopt; +} + +// yaml_t = std::string (UTF-8 from yaml-cpp), value_t = std::wstring +WSLC_VALIDATE_SETTING(SessionStoragePath) +{ + return MultiByteToWide(value); +} + +#undef WSLC_VALIDATE_SETTING + +} // namespace details + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- +namespace { + +// Traverses a dot-separated path (e.g. "session.cpuCount") through a YAML node tree. +// Returns an undefined node if any segment is missing. +YAML::Node NavigateYamlPath(const YAML::Node& root, std::string_view path) +{ + YAML::Node current = root; + size_t start = 0; + while (start < path.size() && current.IsDefined()) + { + const size_t dot = path.find('.', start); + const auto segment = std::string(path.substr(start, dot == std::string_view::npos ? dot : dot - start)); + current = current[segment]; + start = (dot == std::string_view::npos) ? path.size() : dot + 1; + } + return current; +} + +// Builds the set of all known YAML paths from the SettingMapping specializations. +template +std::set BuildKnownPaths(std::index_sequence) +{ + return {std::string(details::SettingMapping(S)>::YamlPath)...}; +} + +// Recursively walks a YAML map node and warns about any key whose full dot-separated +// path is not a known setting path or a known intermediate prefix. +void WarnUnknownKeysInMap( + const YAML::Node& node, + const std::set& knownPaths, + const std::string& prefix, + std::vector& warnings) +{ + if (!node.IsMap()) + { + return; + } + + for (const auto& kv : node) + { + const auto key = kv.first.as(); + const auto fullPath = prefix.empty() ? key : prefix + "." + key; + + const bool isKnownLeaf = knownPaths.count(fullPath) > 0; + const bool isKnownPrefix = std::any_of(knownPaths.begin(), knownPaths.end(), [&](const std::string& p) { + return p.starts_with(fullPath + "."); + }); + + if (!isKnownLeaf && !isKnownPrefix) + { + const auto widePath = MultiByteToWide(fullPath); + warnings.push_back({std::format(L"Warning: Unknown settings key '{}'. Ignoring.", widePath), widePath}); + } + else if (isKnownPrefix && kv.second.IsMap()) + { + WarnUnknownKeysInMap(kv.second, knownPaths, fullPath, warnings); + } + } +} + +// Validates and stores a single setting from the YAML document. +template +void ValidateSetting(const YAML::Node& root, SettingsMap& map, std::vector& warnings) +{ + constexpr auto path = details::SettingMapping::YamlPath; + const YAML::Node node = NavigateYamlPath(root, path); + + if (!node.IsDefined() || node.IsNull()) + { + // Key absent — silently use the built-in default. + return; + } + + try + { + auto rawValue = node.as::yaml_t>(); + auto validated = details::SettingMapping::Validate(rawValue); + if (validated.has_value()) + { + map.Add(std::move(validated.value())); + } + else + { + const auto widePath = MultiByteToWide(path); + warnings.push_back( + {std::format(L"Warning: Invalid value for setting '{}'. Using default.", widePath), widePath}); + } + } + catch (const YAML::Exception&) + { + const auto widePath = MultiByteToWide(path); + warnings.push_back( + {std::format(L"Warning: Invalid type for setting '{}'. Using default.", widePath), widePath}); + } +} + +// Validates all settings via a fold over the Setting enum index sequence. +template +void ValidateAll(const YAML::Node& root, SettingsMap& map, std::vector& warnings, std::index_sequence) +{ + (ValidateSetting(S)>(root, map, warnings), ...); +} + +// Attempts to parse a YAML document from the given file path. +// Returns an empty optional and pushes a warning if the file exists but fails to parse. +std::optional TryLoadYaml(const std::filesystem::path& path, std::vector& warnings) +{ + std::ifstream stream(path); + if (!stream.is_open()) + { + return std::nullopt; + } + + try + { + return YAML::Load(stream); + } + catch (const YAML::Exception& e) + { + warnings.push_back({std::format(L"Warning: '{}' could not be parsed: {}.", + path.filename().wstring(), + MultiByteToWide(e.what())), + {}}); + return std::nullopt; + } +} + +} // namespace + +// --------------------------------------------------------------------------- +// UserSettings +// --------------------------------------------------------------------------- + +// static +UserSettings const& UserSettings::Instance() +{ + static UserSettings instance; + return instance; +} + +// static +const std::filesystem::path& UserSettings::SettingsDir() +{ + static const std::filesystem::path dir = wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc"; + return dir; +} + +// static +std::filesystem::path UserSettings::PrimaryFilePath() +{ + return SettingsDir() / L"UserSettings.yaml"; +} + +// static +std::filesystem::path UserSettings::BackupFilePath() +{ + return SettingsDir() / L"UserSettings.yaml.bak"; +} + +UserSettings::UserSettings() +{ + const auto primaryPath = PrimaryFilePath(); + const auto backupPath = BackupFilePath(); + + // Try the primary file first. + auto root = TryLoadYaml(primaryPath, m_warnings); + if (root.has_value()) + { + m_type = UserSettingsType::Standard; + } + else + { + // Primary missing or failed — try the backup. + root = TryLoadYaml(backupPath, m_warnings); + if (root.has_value()) + { + m_type = UserSettingsType::Backup; + m_warnings.push_back({L"Warning: UserSettings.yaml could not be loaded. Using backup settings.", {}}); + } + // If neither file exists at all, emit no warning (first run). + } + + if (root.has_value()) + { + constexpr auto settingCount = static_cast(Setting::Max); + ValidateAll(root.value(), m_settings, m_warnings, std::make_index_sequence()); + + const auto knownPaths = BuildKnownPaths(std::make_index_sequence()); + WarnUnknownKeysInMap(root.value(), knownPaths, {}, m_warnings); + } +} + +void UserSettings::PrepareToShellExecuteFile() const +{ + const auto primaryPath = PrimaryFilePath(); + + if (m_type == UserSettingsType::Standard) + { + // Valid settings loaded — back them up before the user edits. + std::filesystem::copy_file(primaryPath, BackupFilePath(), std::filesystem::copy_options::overwrite_existing); + } + else if (m_type == UserSettingsType::Default) + { + // First run — create the directory and write the commented-out defaults template. + std::filesystem::create_directories(primaryPath.parent_path()); + std::ofstream file(primaryPath); + THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); + file << s_DefaultSettingsTemplate; + } +} + +} // namespace wsl::windows::wslc::settings diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index b113e0154..d36c68723 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -80,6 +80,10 @@ class UserSettings UserSettings(); ~UserSettings() = default; + // Base directory shared by PrimaryFilePath() and BackupFilePath(). + // Lazily initialized on first call; safe to call from any static context. + static const std::filesystem::path& SettingsDir(); + SettingsMap m_settings; std::vector m_warnings; UserSettingsType m_type = UserSettingsType::Default; From d46a22ec01db3deb29eefa32aa78d132e5094575 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 16:58:38 -0700 Subject: [PATCH 05/25] settings command --- src/windows/wslc/commands/SettingsCommand.cpp | 107 ++++++++++++++++++ src/windows/wslc/commands/SettingsCommand.h | 54 +++++++++ src/windows/wslc/settings/UserSettings.cpp | 10 ++ src/windows/wslc/settings/UserSettings.h | 4 + 4 files changed, 175 insertions(+) create mode 100644 src/windows/wslc/commands/SettingsCommand.cpp create mode 100644 src/windows/wslc/commands/SettingsCommand.h diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp new file mode 100644 index 000000000..077504569 --- /dev/null +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -0,0 +1,107 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + SettingsCommand.cpp + +Abstract: + + Implementation of SettingsCommand command tree. + +--*/ +#include "Argument.h" +#include "SettingsCommand.h" +#include "UserSettings.h" +#include "wslutil.h" + +using namespace wsl::windows::common::wslutil; +using namespace wsl::windows::wslc::execution; +using namespace wsl::windows::wslc::settings; + +namespace wsl::windows::wslc { + +// --------------------------------------------------------------------------- +// SettingsCommand +// --------------------------------------------------------------------------- + +std::vector> SettingsCommand::GetCommands() const +{ + std::vector> commands; + commands.push_back(std::make_unique(FullName())); + return commands; +} + +std::vector SettingsCommand::GetArguments() const +{ + return {}; +} + +std::wstring SettingsCommand::ShortDescription() const +{ + return {L"Open the settings file in the default editor."}; +} + +std::wstring SettingsCommand::LongDescription() const +{ + return {L"Opens the wslc user settings file in the system default editor for .yaml files.\n" + L"On first run, creates the file with all settings commented out at their defaults.\n" + L"A backup of the current settings is saved before the editor opens."}; +} + +void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + const auto& settings = UserSettings::Instance(); + settings.PrepareToShellExecuteFile(); + + const auto path = UserSettings::PrimaryFilePath(); + const auto result = reinterpret_cast( + ShellExecuteW(nullptr, L"open", path.wstring().c_str(), nullptr, nullptr, SW_SHOWNORMAL)); + + if (result <= 32) + { + THROW_HR_MSG(E_UNEXPECTED, "ShellExecuteW failed to open settings file (error %lld)", result); + } +} + +// --------------------------------------------------------------------------- +// SettingsResetCommand +// --------------------------------------------------------------------------- + +std::vector SettingsResetCommand::GetArguments() const +{ + return { + Argument::Create(ArgType::Force), + }; +} + +std::wstring SettingsResetCommand::ShortDescription() const +{ + return {L"Reset settings to built-in defaults."}; +} + +std::wstring SettingsResetCommand::LongDescription() const +{ + return {L"Overwrites the settings file with a commented-out defaults template.\n" + L"Use --force / -f to skip the confirmation prompt."}; +} + +void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const +{ + if (!context.Args.Contains(ArgType::Force)) + { + wprintf(L"This will reset all settings to defaults. Continue? [y/N] "); + std::wstring response; + std::getline(std::wcin, response); + if (response != L"y" && response != L"Y") + { + return; + } + } + + UserSettings::Reset(); + PrintMessage(L"Settings reset to defaults."); +} + +} // namespace wsl::windows::wslc diff --git a/src/windows/wslc/commands/SettingsCommand.h b/src/windows/wslc/commands/SettingsCommand.h new file mode 100644 index 000000000..5a26bdc20 --- /dev/null +++ b/src/windows/wslc/commands/SettingsCommand.h @@ -0,0 +1,54 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + SettingsCommand.h + +Abstract: + + Declaration of SettingsCommand command tree. + +--*/ +#pragma once +#include "Command.h" + +namespace wsl::windows::wslc { + +// Root settings command: opens the settings file in the user's default editor. +struct SettingsCommand final : public Command +{ + constexpr static std::wstring_view CommandName = L"settings"; + + SettingsCommand(const std::wstring& parent) : Command(CommandName, parent) + { + } + + std::vector> GetCommands() const override; + std::vector GetArguments() const override; + std::wstring ShortDescription() const override; + std::wstring LongDescription() const override; + +protected: + void ExecuteInternal(CLIExecutionContext& context) const override; +}; + +// Resets the settings file to built-in defaults. +struct SettingsResetCommand final : public Command +{ + constexpr static std::wstring_view CommandName = L"reset"; + + SettingsResetCommand(const std::wstring& parent) : Command(CommandName, parent) + { + } + + std::vector GetArguments() const override; + std::wstring ShortDescription() const override; + std::wstring LongDescription() const override; + +protected: + void ExecuteInternal(CLIExecutionContext& context) const override; +}; + +} // namespace wsl::windows::wslc diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 9e6bedbfb..7ddf7286c 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -275,6 +275,16 @@ UserSettings::UserSettings() } } +// static +void UserSettings::Reset() +{ + const auto primaryPath = PrimaryFilePath(); + std::filesystem::create_directories(primaryPath.parent_path()); + std::ofstream file(primaryPath); + THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); + file << s_DefaultSettingsTemplate; +} + void UserSettings::PrepareToShellExecuteFile() const { const auto primaryPath = PrimaryFilePath(); diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index d36c68723..406ea9f6f 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -76,6 +76,10 @@ class UserSettings // - If type is Default: creates the primary file from the commented-out defaults template. void PrepareToShellExecuteFile() const; + // Overwrites the primary settings file with the commented-out defaults template. + // Called by SettingsResetCommand. + static void Reset(); + private: UserSettings(); ~UserSettings() = default; From 992297090d15ab7bd8f2514e0c66237853953632 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:29:48 -0700 Subject: [PATCH 06/25] hook up --- src/windows/wslc/commands/RootCommand.cpp | 2 ++ src/windows/wslc/core/Main.cpp | 8 +++++ src/windows/wslc/services/SessionModel.cpp | 29 ++++++++++++------- .../wslc/settings/SettingDefinitions.h | 8 ++--- src/windows/wslc/settings/UserSettings.cpp | 8 ++--- src/windows/wslc/settings/UserSettings.h | 14 +++++++++ 6 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/windows/wslc/commands/RootCommand.cpp b/src/windows/wslc/commands/RootCommand.cpp index 1f7433b88..81e08a560 100644 --- a/src/windows/wslc/commands/RootCommand.cpp +++ b/src/windows/wslc/commands/RootCommand.cpp @@ -17,6 +17,7 @@ Module Name: #include "ContainerCommand.h" #include "ImageCommand.h" #include "SessionCommand.h" +#include "SettingsCommand.h" using namespace wsl::windows::wslc::execution; @@ -27,6 +28,7 @@ std::vector> RootCommand::GetCommands() const commands.push_back(std::make_unique(FullName())); commands.push_back(std::make_unique(FullName())); commands.push_back(std::make_unique(FullName())); + commands.push_back(std::make_unique(FullName())); commands.push_back(std::make_unique(FullName())); commands.push_back(std::make_unique(FullName())); commands.push_back(std::make_unique(FullName())); diff --git a/src/windows/wslc/core/Main.cpp b/src/windows/wslc/core/Main.cpp index 9a730f6d9..8c2f5bce8 100644 --- a/src/windows/wslc/core/Main.cpp +++ b/src/windows/wslc/core/Main.cpp @@ -20,6 +20,7 @@ Module Name: #include "CLIExecutionContext.h" #include "Invocation.h" #include "RootCommand.h" +#include "UserSettings.h" using namespace wsl::shared; using namespace wsl::windows::common; @@ -69,6 +70,13 @@ try subCommand = command->FindSubCommand(invocation); } + // Load settings once and emit any load warnings. + const auto& userSettings = settings::UserSettings::Instance(); + for (const auto& warning : userSettings.GetWarnings()) + { + wslutil::PrintMessage(warning.Message, stderr); + } + command->ParseArguments(invocation, context.Args); command->ValidateArguments(context.Args); command->Execute(context); diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index 64fd2190b..99e3dca23 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -14,22 +14,31 @@ Module Name: #include #include "SessionModel.h" +#include "UserSettings.h" namespace wsl::windows::wslc::models { SessionOptions SessionOptions::Default() { - // Use a function-local static to defer path initialization until first use. - static const std::filesystem::path defaultPath = {wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / "wsla"}; + namespace s = wsl::windows::wslc::settings; + + // StoragePath is PCWSTR; use a static to own the string for the process lifetime. + static const std::wstring storagePath = []() -> std::wstring { + auto configured = s::User().Get(); + if (!configured.empty()) + { + return configured; + } + return (wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wsla").wstring(); + }(); - // TODO: Have a configuration file for those. SessionOptions options{}; - options.m_sessionSettings.DisplayName = s_DefaultSessionName; - options.m_sessionSettings.CpuCount = 4; - options.m_sessionSettings.MemoryMb = 2048; - options.m_sessionSettings.BootTimeoutMs = 30 * 1000; - options.m_sessionSettings.StoragePath = defaultPath.c_str(); - options.m_sessionSettings.MaximumStorageSizeMb = 10000; // 10GB. - options.m_sessionSettings.NetworkingMode = WSLANetworkingModeVirtioProxy; + options.m_sessionSettings.DisplayName = s_DefaultSessionName; + options.m_sessionSettings.CpuCount = s::User().Get(); + options.m_sessionSettings.MemoryMb = s::User().Get(); + options.m_sessionSettings.BootTimeoutMs = 30 * 1000; + options.m_sessionSettings.StoragePath = storagePath.c_str(); + options.m_sessionSettings.MaximumStorageSizeMb = s::User().Get(); + options.m_sessionSettings.NetworkingMode = WSLANetworkingModeVirtioProxy; return options; } diff --git a/src/windows/wslc/settings/SettingDefinitions.h b/src/windows/wslc/settings/SettingDefinitions.h index da0680a7f..ba9753405 100644 --- a/src/windows/wslc/settings/SettingDefinitions.h +++ b/src/windows/wslc/settings/SettingDefinitions.h @@ -63,10 +63,10 @@ struct SettingMapping }; // Setting yaml_t value_t default YamlPath -DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") -DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 8192, "session.memorySizeMb") -DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 51200, "session.maxStorageSizeMb") -DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") +DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") +DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") +DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") +DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") // clang-format on #undef DEFINE_SETTING_MAPPING diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 7ddf7286c..03ab13fec 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -36,11 +36,11 @@ static constexpr std::string_view s_DefaultSettingsTemplate = " # Number of virtual CPUs allocated to the session (default: 4)\n" " # cpuCount: 4\n" "\n" - " # Memory limit for the session in megabytes (default: 8192)\n" - " # memorySizeMb: 8192\n" + " # Memory limit for the session in megabytes (default: 2048)\n" + " # memorySizeMb: 2048\n" "\n" - " # Maximum disk image size in megabytes (default: 51200)\n" - " # maxStorageSizeMb: 51200\n" + " # Maximum disk image size in megabytes (default: 10000)\n" + " # maxStorageSizeMb: 10000\n" "\n" " # Default path for container storage (default: system managed)\n" " # defaultStoragePath: \"\"\n"; diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 406ea9f6f..2c1c196b0 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -50,6 +50,13 @@ class UserSettings UserSettings(UserSettings&&) = delete; UserSettings& operator=(UserSettings&&) = delete; + // Returns the value for setting S, or its built-in default if not present in the file. + template + typename details::SettingMapping::value_t Get() const + { + return m_settings.GetOrDefault(); + } + SettingsMap const& GetSettings() const { return m_settings; @@ -93,4 +100,11 @@ class UserSettings UserSettingsType m_type = UserSettingsType::Default; }; +// Convenience free function — returns the singleton instance. +// Usage: settings::User().Get() +inline UserSettings const& User() +{ + return UserSettings::Instance(); +} + } // namespace wsl::windows::wslc::settings From 2ad3e32ed417802a30678007595b56212f683bce Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Tue, 17 Mar 2026 18:01:48 -0700 Subject: [PATCH 07/25] Fix build --- CMakeLists.txt | 5 +++-- src/windows/wslc/commands/SettingsCommand.cpp | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e72d4a88e..c07615ea0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,12 +51,13 @@ FetchContent_MakeAvailable(nlohmannjson) FetchContent_GetProperties(nlohmannjson SOURCE_DIR NLOHMAN_JSON_SOURCE_DIR) FetchContent_Declare(yaml-cpp - URL https://github.com/jbeder/yaml-cpp/archive/refs/tags/0.9.0.tar.gz - URL_HASH SHA256=d5558cd419c8d46bdc958064cb97f963d1ea793866414c025906ec15033512ed) + URL https://github.com/jbeder/yaml-cpp/releases/download/yaml-cpp-0.9.0/yaml-cpp-yaml-cpp-0.9.0.tar.gz + URL_HASH SHA256=298593d9c440fd9034b8b193d96318b76d49bc97c6ceadb7b0836edf0b6d7539) set(YAML_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(YAML_CPP_BUILD_TOOLS OFF CACHE BOOL "" FORCE) set(YAML_CPP_BUILD_CONTRIB OFF CACHE BOOL "" FORCE) +set(YAML_MSVC_SHARED_RT OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(yaml-cpp) FetchContent_GetProperties(yaml-cpp SOURCE_DIR YAML_CPP_SOURCE_DIR) diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index 077504569..90f8dbb23 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -15,6 +15,7 @@ Module Name: #include "SettingsCommand.h" #include "UserSettings.h" #include "wslutil.h" +#include using namespace wsl::windows::common::wslutil; using namespace wsl::windows::wslc::execution; From 93155aa09ce174cc8e95fb0539d43c296303ee5b Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:40:11 -0700 Subject: [PATCH 08/25] Remove SettingDefinitions --- .../wslc/settings/SettingDefinitions.h | 93 ------------------- src/windows/wslc/settings/UserSettings.h | 75 ++++++++++++++- 2 files changed, 74 insertions(+), 94 deletions(-) delete mode 100644 src/windows/wslc/settings/SettingDefinitions.h diff --git a/src/windows/wslc/settings/SettingDefinitions.h b/src/windows/wslc/settings/SettingDefinitions.h deleted file mode 100644 index ba9753405..000000000 --- a/src/windows/wslc/settings/SettingDefinitions.h +++ /dev/null @@ -1,93 +0,0 @@ -/*++ - -Copyright (c) Microsoft. All rights reserved. - -Module Name: - - SettingDefinitions.h - -Abstract: - - Declaration of user settings with their YAML paths, types, and defaults. - ---*/ -#pragma once -#include "EnumVariantMap.h" -#include -#include -#include -#include - -// How to add a setting: -// 1 - Add an entry to the Setting enum. -// 2 - Add a DEFINE_SETTING_MAPPING specialization with yaml_t, value_t, default, and YAML path. -// 3 - Implement the Validate function in UserSettings.cpp. - -namespace wsl::windows::wslc::settings { - -// Enum of all user settings. -// Must start at 0 to enable direct variant indexing. -// Max must be last and unused. -enum class Setting : size_t -{ - SessionCpuCount, - SessionMemoryMb, - SessionStorageSizeMb, - SessionStoragePath, - - Max -}; - -namespace details { - -template -struct SettingMapping -{ - // yaml_t - the C++ type read from the YAML node via node.as() - // value_t - the native type stored in SettingsMap - // DefaultValue - used when the key is absent or fails validation - // YamlPath - dot-separated path into the YAML document (e.g. "session.cpuCount") - // Validate - semantic validation; returns nullopt to reject and fall back to default -}; - -// clang-format off -#define DEFINE_SETTING_MAPPING(_setting_, _yaml_t_, _value_t_, _default_, _path_) \ - template <> \ - struct SettingMapping \ - { \ - using yaml_t = _yaml_t_; \ - using value_t = _value_t_; \ - inline static const value_t DefaultValue = _default_; \ - static constexpr std::string_view YamlPath = _path_; \ - static std::optional Validate(const yaml_t& value); \ - }; - -// Setting yaml_t value_t default YamlPath -DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") -DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") -DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") -DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") -// clang-format on - -#undef DEFINE_SETTING_MAPPING - -} // namespace details - -// Type-safe enum-indexed map of all settings values, backed by EnumBasedVariantMap. -// Each setting key holds at most one value; Contains(S) indicates whether the setting -// was explicitly loaded from the file (vs. falling back to DefaultValue). -struct SettingsMap : wsl::windows::wslc::EnumBasedVariantMap -{ - // Returns the stored value if present, otherwise the compile-time default. - template - typename details::SettingMapping::value_t GetOrDefault() const - { - if (Contains(S)) - { - return Get(); - } - return details::SettingMapping::DefaultValue; - } -}; - -} // namespace wsl::windows::wslc::settings diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 2c1c196b0..988df2e6f 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -13,13 +13,86 @@ Module Name: --*/ #pragma once -#include "SettingDefinitions.h" +#include "EnumVariantMap.h" +#include #include +#include #include +#include #include +// How to add a setting: +// 1 - Add an entry to the Setting enum. +// 2 - Add a DEFINE_SETTING_MAPPING specialization with yaml_t, value_t, default, and YAML path. +// 3 - Implement the Validate function in UserSettings.cpp. + namespace wsl::windows::wslc::settings { +// Enum of all user settings. +// Must start at 0 to enable direct variant indexing. +// Max must be last and unused. +enum class Setting : size_t +{ + SessionCpuCount, + SessionMemoryMb, + SessionStorageSizeMb, + SessionStoragePath, + + Max +}; + +namespace details { + +template +struct SettingMapping +{ + // yaml_t - the C++ type read from the YAML node via node.as() + // value_t - the native type stored in SettingsMap + // DefaultValue - used when the key is absent or fails validation + // YamlPath - dot-separated path into the YAML document (e.g. "session.cpuCount") + // Validate - semantic validation; returns nullopt to reject and fall back to default +}; + +// clang-format off +#define DEFINE_SETTING_MAPPING(_setting_, _yaml_t_, _value_t_, _default_, _path_) \ + template <> \ + struct SettingMapping \ + { \ + using yaml_t = _yaml_t_; \ + using value_t = _value_t_; \ + inline static const value_t DefaultValue = _default_; \ + static constexpr std::string_view YamlPath = _path_; \ + static std::optional Validate(const yaml_t& value); \ + }; + +// Setting yaml_t value_t default YamlPath +DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") +DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") +DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") +DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") +// clang-format on + +#undef DEFINE_SETTING_MAPPING + +} // namespace details + +// Type-safe enum-indexed map of all settings values, backed by EnumBasedVariantMap. +// Each setting key holds at most one value; Contains(S) indicates whether the setting +// was explicitly loaded from the file (vs. falling back to DefaultValue). +struct SettingsMap : wsl::windows::wslc::EnumBasedVariantMap +{ + // Returns the stored value if present, otherwise the compile-time default. + template + typename details::SettingMapping::value_t GetOrDefault() const + { + if (Contains(S)) + { + return Get(); + } + return details::SettingMapping::DefaultValue; + } +}; + // Indicates which source the settings were loaded from. enum class UserSettingsType { From 68ebd0c4e76b9b71a64d2d2d7052c78cee60c12b Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:46:50 -0700 Subject: [PATCH 09/25] cleanup --- src/windows/wslc/services/SessionModel.cpp | 29 ++++++++-------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index 99e3dca23..e1600d5ba 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -19,26 +19,19 @@ Module Name: namespace wsl::windows::wslc::models { SessionOptions SessionOptions::Default() { - namespace s = wsl::windows::wslc::settings; - - // StoragePath is PCWSTR; use a static to own the string for the process lifetime. - static const std::wstring storagePath = []() -> std::wstring { - auto configured = s::User().Get(); - if (!configured.empty()) - { - return configured; - } - return (wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wsla").wstring(); - }(); + // Use a function-local static to defer path initialization until first use. + static const std::filesystem::path defaultPath = {wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / "wsla"}; SessionOptions options{}; - options.m_sessionSettings.DisplayName = s_DefaultSessionName; - options.m_sessionSettings.CpuCount = s::User().Get(); - options.m_sessionSettings.MemoryMb = s::User().Get(); - options.m_sessionSettings.BootTimeoutMs = 30 * 1000; - options.m_sessionSettings.StoragePath = storagePath.c_str(); - options.m_sessionSettings.MaximumStorageSizeMb = s::User().Get(); - options.m_sessionSettings.NetworkingMode = WSLANetworkingModeVirtioProxy; + options.m_sessionSettings.DisplayName = s_DefaultSessionName; + options.m_sessionSettings.CpuCount = settings::User().Get(); + options.m_sessionSettings.MemoryMb = settings::User().Get(); + options.m_sessionSettings.BootTimeoutMs = 30 * 1000; + options.m_sessionSettings.StoragePath = settings::User().Get().empty() + ? defaultPath.c_str() + : settings::User().Get().c_str(); + options.m_sessionSettings.MaximumStorageSizeMb = settings::User().Get(); + options.m_sessionSettings.NetworkingMode = WSLANetworkingModeVirtioProxy; return options; } From 98188ef921dd83df4e02f8886742bd216906231a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:33:11 -0700 Subject: [PATCH 10/25] improvements --- src/windows/wslc/commands/SettingsCommand.cpp | 30 +---- src/windows/wslc/core/Main.cpp | 14 +- src/windows/wslc/services/SessionModel.cpp | 2 +- src/windows/wslc/settings/UserSettings.cpp | 124 ++++++------------ src/windows/wslc/settings/UserSettings.h | 32 +---- 5 files changed, 58 insertions(+), 144 deletions(-) diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index 90f8dbb23..f4d50b7e4 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -23,10 +23,8 @@ using namespace wsl::windows::wslc::settings; namespace wsl::windows::wslc { -// --------------------------------------------------------------------------- -// SettingsCommand -// --------------------------------------------------------------------------- +// SettingsCommand std::vector> SettingsCommand::GetCommands() const { std::vector> commands; @@ -53,10 +51,9 @@ std::wstring SettingsCommand::LongDescription() const void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const { - const auto& settings = UserSettings::Instance(); - settings.PrepareToShellExecuteFile(); + settings::User().PrepareToShellExecuteFile(); - const auto path = UserSettings::PrimaryFilePath(); + const auto path = settings::User().SettingsFilePath(); const auto result = reinterpret_cast( ShellExecuteW(nullptr, L"open", path.wstring().c_str(), nullptr, nullptr, SW_SHOWNORMAL)); @@ -66,15 +63,10 @@ void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const } } -// --------------------------------------------------------------------------- // SettingsResetCommand -// --------------------------------------------------------------------------- - std::vector SettingsResetCommand::GetArguments() const { - return { - Argument::Create(ArgType::Force), - }; + return {}; } std::wstring SettingsResetCommand::ShortDescription() const @@ -90,18 +82,8 @@ std::wstring SettingsResetCommand::LongDescription() const void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const { - if (!context.Args.Contains(ArgType::Force)) - { - wprintf(L"This will reset all settings to defaults. Continue? [y/N] "); - std::wstring response; - std::getline(std::wcin, response); - if (response != L"y" && response != L"Y") - { - return; - } - } - - UserSettings::Reset(); + // Todo: do we need prompt support? + settings::User().Reset(); PrintMessage(L"Settings reset to defaults."); } diff --git a/src/windows/wslc/core/Main.cpp b/src/windows/wslc/core/Main.cpp index 8c2f5bce8..9702309af 100644 --- a/src/windows/wslc/core/Main.cpp +++ b/src/windows/wslc/core/Main.cpp @@ -56,6 +56,13 @@ try try { + // Emit any settings load warnings. + for (const auto& warning : settings::User().GetWarnings()) + { + // Todo: print as warning after reporter support, or maybe move to output warnings only in settings commands. + wslutil::PrintMessage(warning.Message); + } + std::vector args; for (int i = 1; i < argc; ++i) { @@ -70,13 +77,6 @@ try subCommand = command->FindSubCommand(invocation); } - // Load settings once and emit any load warnings. - const auto& userSettings = settings::UserSettings::Instance(); - for (const auto& warning : userSettings.GetWarnings()) - { - wslutil::PrintMessage(warning.Message, stderr); - } - command->ParseArguments(invocation, context.Args); command->ValidateArguments(context.Args); command->Execute(context); diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index e1600d5ba..f5ec7ecd4 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -20,7 +20,7 @@ namespace wsl::windows::wslc::models { SessionOptions SessionOptions::Default() { // Use a function-local static to defer path initialization until first use. - static const std::filesystem::path defaultPath = {wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / "wsla"}; + static const std::filesystem::path defaultPath = {wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\defaultstorage"}; SessionOptions options{}; options.m_sessionSettings.DisplayName = s_DefaultSessionName; diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 03ab13fec..04ac902c0 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -24,10 +24,9 @@ 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 = "# wslc user settings\n" "# https://aka.ms/wslc-settings\n" @@ -42,12 +41,11 @@ static constexpr std::string_view s_DefaultSettingsTemplate = " # Maximum disk image size in megabytes (default: 10000)\n" " # maxStorageSizeMb: 10000\n" "\n" - " # Default path for container storage (default: system managed)\n" + " # Default path for container storage (default: %LocalAppData%\\wslc\\storage)\n" " # defaultStoragePath: \"\"\n"; -// --------------------------------------------------------------------------- -// Validate specializations -// --------------------------------------------------------------------------- + +// Validate individual setting specializations namespace details { #define WSLC_VALIDATE_SETTING(_setting_) \ @@ -80,67 +78,27 @@ WSLC_VALIDATE_SETTING(SessionStoragePath) } // namespace details -// --------------------------------------------------------------------------- -// Internal helpers -// --------------------------------------------------------------------------- +// Helpers namespace { // Traverses a dot-separated path (e.g. "session.cpuCount") through a YAML node tree. -// Returns an undefined node if any segment is missing. -YAML::Node NavigateYamlPath(const YAML::Node& root, std::string_view path) +// Returns nullopt if any segment is invalid or missing. +std::optional NavigateYamlPath(const YAML::Node& root, std::string_view path) { YAML::Node current = root; - size_t start = 0; - while (start < path.size() && current.IsDefined()) - { - const size_t dot = path.find('.', start); - const auto segment = std::string(path.substr(start, dot == std::string_view::npos ? dot : dot - start)); - current = current[segment]; - start = (dot == std::string_view::npos) ? path.size() : dot + 1; - } - return current; -} - -// Builds the set of all known YAML paths from the SettingMapping specializations. -template -std::set BuildKnownPaths(std::index_sequence) -{ - return {std::string(details::SettingMapping(S)>::YamlPath)...}; -} - -// Recursively walks a YAML map node and warns about any key whose full dot-separated -// path is not a known setting path or a known intermediate prefix. -void WarnUnknownKeysInMap( - const YAML::Node& node, - const std::set& knownPaths, - const std::string& prefix, - std::vector& warnings) -{ - if (!node.IsMap()) + auto subPaths = wsl::shared::string::Split(std::string{path}, '.'); + for (auto const& subPath : subPaths) { - return; - } - - for (const auto& kv : node) - { - const auto key = kv.first.as(); - const auto fullPath = prefix.empty() ? key : prefix + "." + key; - - const bool isKnownLeaf = knownPaths.count(fullPath) > 0; - const bool isKnownPrefix = std::any_of(knownPaths.begin(), knownPaths.end(), [&](const std::string& p) { - return p.starts_with(fullPath + "."); - }); - - if (!isKnownLeaf && !isKnownPrefix) + if (current.IsDefined() && current.IsMap()) { - const auto widePath = MultiByteToWide(fullPath); - warnings.push_back({std::format(L"Warning: Unknown settings key '{}'. Ignoring.", widePath), widePath}); + current = current[subPath]; } - else if (isKnownPrefix && kv.second.IsMap()) + else { - WarnUnknownKeysInMap(kv.second, knownPaths, fullPath, warnings); + return std::nullopt; } } + return current; } // Validates and stores a single setting from the YAML document. @@ -210,38 +168,31 @@ std::optional TryLoadYaml(const std::filesystem::path& path, std::ve } } -} // namespace - -// --------------------------------------------------------------------------- -// UserSettings -// --------------------------------------------------------------------------- - -// static -UserSettings const& UserSettings::Instance() -{ - static UserSettings instance; - return instance; -} - -// static -const std::filesystem::path& UserSettings::SettingsDir() +const std::filesystem::path& SettingsDir() { - static const std::filesystem::path dir = wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc"; + static const std::filesystem::path dir = wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\settings"; return dir; } -// static -std::filesystem::path UserSettings::PrimaryFilePath() +std::filesystem::path PrimaryFilePath() { return SettingsDir() / L"UserSettings.yaml"; } -// static -std::filesystem::path UserSettings::BackupFilePath() +std::filesystem::path BackupFilePath() { return SettingsDir() / L"UserSettings.yaml.bak"; } +} // namespace + + +UserSettings const& UserSettings::Instance() +{ + static UserSettings instance; + return instance; +} + UserSettings::UserSettings() { const auto primaryPath = PrimaryFilePath(); @@ -262,7 +213,6 @@ UserSettings::UserSettings() m_type = UserSettingsType::Backup; m_warnings.push_back({L"Warning: UserSettings.yaml could not be loaded. Using backup settings.", {}}); } - // If neither file exists at all, emit no warning (first run). } if (root.has_value()) @@ -270,12 +220,10 @@ UserSettings::UserSettings() constexpr auto settingCount = static_cast(Setting::Max); ValidateAll(root.value(), m_settings, m_warnings, std::make_index_sequence()); - const auto knownPaths = BuildKnownPaths(std::make_index_sequence()); - WarnUnknownKeysInMap(root.value(), knownPaths, {}, m_warnings); + // TODO: Iterate through all nodes and warn about unknown keys? } } -// static void UserSettings::Reset() { const auto primaryPath = PrimaryFilePath(); @@ -283,25 +231,27 @@ void UserSettings::Reset() std::ofstream file(primaryPath); THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); file << s_DefaultSettingsTemplate; + file.flush(); + file.close(); } void UserSettings::PrepareToShellExecuteFile() const { - const auto primaryPath = PrimaryFilePath(); - if (m_type == UserSettingsType::Standard) { // Valid settings loaded — back them up before the user edits. - std::filesystem::copy_file(primaryPath, BackupFilePath(), std::filesystem::copy_options::overwrite_existing); + std::filesystem::copy_file(PrimaryFilePath(), BackupFilePath(), std::filesystem::copy_options::overwrite_existing); } else if (m_type == UserSettingsType::Default) { // First run — create the directory and write the commented-out defaults template. - std::filesystem::create_directories(primaryPath.parent_path()); - std::ofstream file(primaryPath); - THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); - file << s_DefaultSettingsTemplate; + Reset(); } } +std::filesystem::path UserSettings::SettingsFilePath() +{ + return PrimaryFilePath(); +} + } // namespace wsl::windows::wslc::settings diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 988df2e6f..e45d157fe 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -13,6 +13,7 @@ Module Name: --*/ #pragma once +#include "defs.h" #include "EnumVariantMap.h" #include #include @@ -24,7 +25,7 @@ Module Name: // How to add a setting: // 1 - Add an entry to the Setting enum. // 2 - Add a DEFINE_SETTING_MAPPING specialization with yaml_t, value_t, default, and YAML path. -// 3 - Implement the Validate function in UserSettings.cpp. +// 3 - Implement the Validate function in UserSettings.cpp if needed, otherwise use pass through. namespace wsl::windows::wslc::settings { @@ -33,7 +34,7 @@ namespace wsl::windows::wslc::settings { // Max must be last and unused. enum class Setting : size_t { - SessionCpuCount, + SessionCpuCount = 0, SessionMemoryMb, SessionStorageSizeMb, SessionStoragePath, @@ -65,7 +66,6 @@ struct SettingMapping static std::optional Validate(const yaml_t& value); \ }; -// Setting yaml_t value_t default YamlPath DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") @@ -77,8 +77,6 @@ DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, " } // namespace details // Type-safe enum-indexed map of all settings values, backed by EnumBasedVariantMap. -// Each setting key holds at most one value; Contains(S) indicates whether the setting -// was explicitly loaded from the file (vs. falling back to DefaultValue). struct SettingsMap : wsl::windows::wslc::EnumBasedVariantMap { // Returns the stored value if present, otherwise the compile-time default. @@ -118,10 +116,8 @@ class UserSettings // Returns the singleton instance. Loaded on first call; subsequent calls are no-ops. static UserSettings const& Instance(); - UserSettings(const UserSettings&) = delete; - UserSettings& operator=(const UserSettings&) = delete; - UserSettings(UserSettings&&) = delete; - UserSettings& operator=(UserSettings&&) = delete; + NON_COPYABLE(UserSettings); + NON_MOVABLE(UserSettings); // Returns the value for setting S, or its built-in default if not present in the file. template @@ -130,11 +126,6 @@ class UserSettings return m_settings.GetOrDefault(); } - SettingsMap const& GetSettings() const - { - return m_settings; - } - std::vector const& GetWarnings() const { return m_warnings; @@ -145,29 +136,20 @@ class UserSettings return m_type; } - // Full path to %LOCALAPPDATA%\Microsoft\wslc\UserSettings.yaml - static std::filesystem::path PrimaryFilePath(); - - // Full path to %LOCALAPPDATA%\Microsoft\wslc\UserSettings.yaml.bak - static std::filesystem::path BackupFilePath(); - // Called before opening the settings file in an editor. // - If type is Standard: copies the primary file to the backup path. // - If type is Default: creates the primary file from the commented-out defaults template. void PrepareToShellExecuteFile() const; + static std::filesystem::path SettingsFilePath(); + // Overwrites the primary settings file with the commented-out defaults template. - // Called by SettingsResetCommand. static void Reset(); private: UserSettings(); ~UserSettings() = default; - // Base directory shared by PrimaryFilePath() and BackupFilePath(). - // Lazily initialized on first call; safe to call from any static context. - static const std::filesystem::path& SettingsDir(); - SettingsMap m_settings; std::vector m_warnings; UserSettingsType m_type = UserSettingsType::Default; From 57901e24000d241b94633a8cda0a5677e925ec65 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:44:35 -0700 Subject: [PATCH 11/25] clang --- src/windows/wslc/commands/SettingsCommand.cpp | 23 ++- src/windows/wslc/services/SessionModel.cpp | 3 +- src/windows/wslc/settings/UserSettings.cpp | 192 +++++++++--------- src/windows/wslc/settings/UserSettings.h | 34 ++-- 4 files changed, 125 insertions(+), 127 deletions(-) diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index f4d50b7e4..b21aec2e5 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -23,7 +23,6 @@ using namespace wsl::windows::wslc::settings; namespace wsl::windows::wslc { - // SettingsCommand std::vector> SettingsCommand::GetCommands() const { @@ -44,9 +43,10 @@ std::wstring SettingsCommand::ShortDescription() const std::wstring SettingsCommand::LongDescription() const { - return {L"Opens the wslc user settings file in the system default editor for .yaml files.\n" - L"On first run, creates the file with all settings commented out at their defaults.\n" - L"A backup of the current settings is saved before the editor opens."}; + return { + L"Opens the wslc user settings file in the system default editor for .yaml files.\n" + L"On first run, creates the file with all settings commented out at their defaults.\n" + L"A backup of the current settings is saved before the editor opens."}; } void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const @@ -54,12 +54,14 @@ void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const settings::User().PrepareToShellExecuteFile(); const auto path = settings::User().SettingsFilePath(); - const auto result = reinterpret_cast( - ShellExecuteW(nullptr, L"open", path.wstring().c_str(), nullptr, nullptr, SW_SHOWNORMAL)); - if (result <= 32) + // Some versions of windows will fail if no file extension association exists, other will pop up the dialog + // to make the user pick their default. + HINSTANCE res = ShellExecuteW(nullptr, nullptr, path.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast(reinterpret_cast(res)) <= 32) { - THROW_HR_MSG(E_UNEXPECTED, "ShellExecuteW failed to open settings file (error %lld)", result); + // User doesn't have file type association. Default to notepad + ShellExecuteW(nullptr, nullptr, L"notepad", path.c_str(), nullptr, SW_SHOW); } } @@ -76,8 +78,9 @@ std::wstring SettingsResetCommand::ShortDescription() const std::wstring SettingsResetCommand::LongDescription() const { - return {L"Overwrites the settings file with a commented-out defaults template.\n" - L"Use --force / -f to skip the confirmation prompt."}; + return { + L"Overwrites the settings file with a commented-out defaults template.\n" + L"Use --force / -f to skip the confirmation prompt."}; } void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index f5ec7ecd4..ac19cd079 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -20,7 +20,8 @@ namespace wsl::windows::wslc::models { SessionOptions SessionOptions::Default() { // Use a function-local static to defer path initialization until first use. - static const std::filesystem::path defaultPath = {wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\defaultstorage"}; + static const std::filesystem::path defaultPath = { + wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\defaultstorage"}; SessionOptions options{}; options.m_sessionSettings.DisplayName = s_DefaultSessionName; diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 04ac902c0..ce9ab5cef 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -44,35 +44,33 @@ static constexpr std::string_view s_DefaultSettingsTemplate = " # Default path for container storage (default: %LocalAppData%\\wslc\\storage)\n" " # defaultStoragePath: \"\"\n"; - // Validate individual setting specializations namespace details { -#define WSLC_VALIDATE_SETTING(_setting_) \ - std::optional::value_t> \ - SettingMapping::Validate( \ +#define WSLC_VALIDATE_SETTING(_setting_) \ + std::optional::value_t> SettingMapping::Validate( \ const SettingMapping::yaml_t& value) -WSLC_VALIDATE_SETTING(SessionCpuCount) -{ - return value > 0 ? std::optional{value} : std::nullopt; -} + WSLC_VALIDATE_SETTING(SessionCpuCount) + { + return value > 0 ? std::optional{value} : std::nullopt; + } -WSLC_VALIDATE_SETTING(SessionMemoryMb) -{ - return value > 0 ? std::optional{value} : std::nullopt; -} + WSLC_VALIDATE_SETTING(SessionMemoryMb) + { + return value > 0 ? std::optional{value} : std::nullopt; + } -WSLC_VALIDATE_SETTING(SessionStorageSizeMb) -{ - return value > 0 ? std::optional{value} : std::nullopt; -} + WSLC_VALIDATE_SETTING(SessionStorageSizeMb) + { + return value > 0 ? std::optional{value} : std::nullopt; + } -// yaml_t = std::string (UTF-8 from yaml-cpp), value_t = std::wstring -WSLC_VALIDATE_SETTING(SessionStoragePath) -{ - return MultiByteToWide(value); -} + // yaml_t = std::string (UTF-8 from yaml-cpp), value_t = std::wstring + WSLC_VALIDATE_SETTING(SessionStoragePath) + { + return MultiByteToWide(value); + } #undef WSLC_VALIDATE_SETTING @@ -81,112 +79,108 @@ WSLC_VALIDATE_SETTING(SessionStoragePath) // Helpers namespace { -// Traverses a dot-separated path (e.g. "session.cpuCount") through a YAML node tree. -// Returns nullopt if any segment is invalid or missing. -std::optional NavigateYamlPath(const YAML::Node& root, std::string_view path) -{ - YAML::Node current = root; - auto subPaths = wsl::shared::string::Split(std::string{path}, '.'); - for (auto const& subPath : subPaths) + // Traverses a dot-separated path (e.g. "session.cpuCount") through a YAML node tree. + // Returns nullopt if any segment is invalid or missing. + std::optional NavigateYamlPath(const YAML::Node& root, std::string_view path) { - if (current.IsDefined() && current.IsMap()) + YAML::Node current = root; + auto subPaths = wsl::shared::string::Split(std::string{path}, '.'); + for (auto const& subPath : subPaths) { - current = current[subPath]; + if (current.IsDefined() && current.IsMap()) + { + current = current[subPath]; + } + else + { + return std::nullopt; + } } - else + return current; + } + + // Validates and stores a single setting from the YAML document. + template + void ValidateSetting(const YAML::Node& root, SettingsMap& map, std::vector& warnings) + { + constexpr auto path = details::SettingMapping::YamlPath; + const YAML::Node node = NavigateYamlPath(root, path); + + if (!node.IsDefined() || node.IsNull()) { - return std::nullopt; + // Key absent — silently use the built-in default. + return; } - } - return current; -} -// Validates and stores a single setting from the YAML document. -template -void ValidateSetting(const YAML::Node& root, SettingsMap& map, std::vector& warnings) -{ - constexpr auto path = details::SettingMapping::YamlPath; - const YAML::Node node = NavigateYamlPath(root, path); + try + { + auto rawValue = node.as::yaml_t>(); + auto validated = details::SettingMapping::Validate(rawValue); + if (validated.has_value()) + { + map.Add(std::move(validated.value())); + } + else + { + const auto widePath = MultiByteToWide(path); + warnings.push_back({std::format(L"Warning: Invalid value for setting '{}'. Using default.", widePath), widePath}); + } + } + catch (const YAML::Exception&) + { + const auto widePath = MultiByteToWide(path); + warnings.push_back({std::format(L"Warning: Invalid type for setting '{}'. Using default.", widePath), widePath}); + } + } - if (!node.IsDefined() || node.IsNull()) + // Validates all settings via a fold over the Setting enum index sequence. + template + void ValidateAll(const YAML::Node& root, SettingsMap& map, std::vector& warnings, std::index_sequence) { - // Key absent — silently use the built-in default. - return; + (ValidateSetting(S)>(root, map, warnings), ...); } - try + // Attempts to parse a YAML document from the given file path. + // Returns an empty optional and pushes a warning if the file exists but fails to parse. + std::optional TryLoadYaml(const std::filesystem::path& path, std::vector& warnings) { - auto rawValue = node.as::yaml_t>(); - auto validated = details::SettingMapping::Validate(rawValue); - if (validated.has_value()) + std::ifstream stream(path); + if (!stream.is_open()) { - map.Add(std::move(validated.value())); + return std::nullopt; } - else + + try + { + return YAML::Load(stream); + } + catch (const YAML::Exception& e) { - const auto widePath = MultiByteToWide(path); warnings.push_back( - {std::format(L"Warning: Invalid value for setting '{}'. Using default.", widePath), widePath}); + {std::format(L"Warning: '{}' could not be parsed: {}.", path.filename().wstring(), MultiByteToWide(e.what())), {}}); + return std::nullopt; } } - catch (const YAML::Exception&) - { - const auto widePath = MultiByteToWide(path); - warnings.push_back( - {std::format(L"Warning: Invalid type for setting '{}'. Using default.", widePath), widePath}); - } -} - -// Validates all settings via a fold over the Setting enum index sequence. -template -void ValidateAll(const YAML::Node& root, SettingsMap& map, std::vector& warnings, std::index_sequence) -{ - (ValidateSetting(S)>(root, map, warnings), ...); -} -// Attempts to parse a YAML document from the given file path. -// Returns an empty optional and pushes a warning if the file exists but fails to parse. -std::optional TryLoadYaml(const std::filesystem::path& path, std::vector& warnings) -{ - std::ifstream stream(path); - if (!stream.is_open()) + const std::filesystem::path& SettingsDir() { - return std::nullopt; + static const std::filesystem::path dir = + wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\settings"; + return dir; } - try + std::filesystem::path PrimaryFilePath() { - return YAML::Load(stream); + return SettingsDir() / L"UserSettings.yaml"; } - catch (const YAML::Exception& e) + + std::filesystem::path BackupFilePath() { - warnings.push_back({std::format(L"Warning: '{}' could not be parsed: {}.", - path.filename().wstring(), - MultiByteToWide(e.what())), - {}}); - return std::nullopt; + return SettingsDir() / L"UserSettings.yaml.bak"; } -} - -const std::filesystem::path& SettingsDir() -{ - static const std::filesystem::path dir = wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\settings"; - return dir; -} - -std::filesystem::path PrimaryFilePath() -{ - return SettingsDir() / L"UserSettings.yaml"; -} - -std::filesystem::path BackupFilePath() -{ - return SettingsDir() / L"UserSettings.yaml.bak"; -} } // namespace - UserSettings const& UserSettings::Instance() { static UserSettings instance; diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index e45d157fe..0a23bd61d 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -44,17 +44,17 @@ enum class Setting : size_t namespace details { -template -struct SettingMapping -{ - // yaml_t - the C++ type read from the YAML node via node.as() - // value_t - the native type stored in SettingsMap - // DefaultValue - used when the key is absent or fails validation - // YamlPath - dot-separated path into the YAML document (e.g. "session.cpuCount") - // Validate - semantic validation; returns nullopt to reject and fall back to default -}; + template + struct SettingMapping + { + // yaml_t - the C++ type read from the YAML node via node.as() + // value_t - the native type stored in SettingsMap + // DefaultValue - used when the key is absent or fails validation + // YamlPath - dot-separated path into the YAML document (e.g. "session.cpuCount") + // Validate - semantic validation; returns nullopt to reject and fall back to default + }; -// clang-format off + // clang-format off #define DEFINE_SETTING_MAPPING(_setting_, _yaml_t_, _value_t_, _default_, _path_) \ template <> \ struct SettingMapping \ @@ -66,13 +66,13 @@ struct SettingMapping static std::optional Validate(const yaml_t& value); \ }; -DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") -DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") -DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") -DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") -// clang-format on + DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") + DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") + DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") + DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") #undef DEFINE_SETTING_MAPPING + // clang-format on } // namespace details @@ -150,9 +150,9 @@ class UserSettings UserSettings(); ~UserSettings() = default; - SettingsMap m_settings; + SettingsMap m_settings; std::vector m_warnings; - UserSettingsType m_type = UserSettingsType::Default; + UserSettingsType m_type = UserSettingsType::Default; }; // Convenience free function — returns the singleton instance. From 0482059b07c4ac64cabb3893eed4d3880209a5ea Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:53:26 -0700 Subject: [PATCH 12/25] fix build --- src/windows/wslc/settings/UserSettings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index ce9ab5cef..e67c1571c 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -104,9 +104,9 @@ namespace { void ValidateSetting(const YAML::Node& root, SettingsMap& map, std::vector& warnings) { constexpr auto path = details::SettingMapping::YamlPath; - const YAML::Node node = NavigateYamlPath(root, path); + auto node = NavigateYamlPath(root, path); - if (!node.IsDefined() || node.IsNull()) + if (!node || !node->IsDefined() || node->IsNull()) { // Key absent — silently use the built-in default. return; @@ -114,7 +114,7 @@ namespace { try { - auto rawValue = node.as::yaml_t>(); + auto rawValue = node->as::yaml_t>(); auto validated = details::SettingMapping::Validate(rawValue); if (validated.has_value()) { From 69bc3eeb454f903864feae3c70f8be8fb1ee7e65 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:28:52 -0700 Subject: [PATCH 13/25] Test --- src/windows/wslc/settings/UserSettings.cpp | 38 +- src/windows/wslc/settings/UserSettings.h | 13 +- test/windows/wslc/CMakeLists.txt | 1 + .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 330 ++++++++++++++++++ 4 files changed, 356 insertions(+), 26 deletions(-) create mode 100644 test/windows/wslc/WSLCCLISettingsUnitTests.cpp diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index e67c1571c..8075b439a 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -168,17 +168,6 @@ namespace { wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\settings"; return dir; } - - std::filesystem::path PrimaryFilePath() - { - return SettingsDir() / L"UserSettings.yaml"; - } - - std::filesystem::path BackupFilePath() - { - return SettingsDir() / L"UserSettings.yaml.bak"; - } - } // namespace UserSettings const& UserSettings::Instance() @@ -187,13 +176,17 @@ UserSettings const& UserSettings::Instance() return instance; } -UserSettings::UserSettings() +UserSettings::UserSettings() : UserSettings(SettingsDir()) +{ +} + +UserSettings::UserSettings(const std::filesystem::path& settingsDir) { - const auto primaryPath = PrimaryFilePath(); - const auto backupPath = BackupFilePath(); + m_primaryPath = settingsDir / L"UserSettings.yaml"; + m_backupPath = settingsDir / L"UserSettings.yaml.bak"; // Try the primary file first. - auto root = TryLoadYaml(primaryPath, m_warnings); + auto root = TryLoadYaml(m_primaryPath, m_warnings); if (root.has_value()) { m_type = UserSettingsType::Standard; @@ -201,7 +194,7 @@ UserSettings::UserSettings() else { // Primary missing or failed — try the backup. - root = TryLoadYaml(backupPath, m_warnings); + root = TryLoadYaml(m_backupPath, m_warnings); if (root.has_value()) { m_type = UserSettingsType::Backup; @@ -218,11 +211,10 @@ UserSettings::UserSettings() } } -void UserSettings::Reset() +void UserSettings::Reset() const { - const auto primaryPath = PrimaryFilePath(); - std::filesystem::create_directories(primaryPath.parent_path()); - std::ofstream file(primaryPath); + std::filesystem::create_directories(m_primaryPath.parent_path()); + std::ofstream file(m_primaryPath); THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); file << s_DefaultSettingsTemplate; file.flush(); @@ -234,7 +226,7 @@ void UserSettings::PrepareToShellExecuteFile() const if (m_type == UserSettingsType::Standard) { // Valid settings loaded — back them up before the user edits. - std::filesystem::copy_file(PrimaryFilePath(), BackupFilePath(), std::filesystem::copy_options::overwrite_existing); + std::filesystem::copy_file(m_primaryPath, m_backupPath, std::filesystem::copy_options::overwrite_existing); } else if (m_type == UserSettingsType::Default) { @@ -243,9 +235,9 @@ void UserSettings::PrepareToShellExecuteFile() const } } -std::filesystem::path UserSettings::SettingsFilePath() +std::filesystem::path UserSettings::SettingsFilePath() const { - return PrimaryFilePath(); + return m_primaryPath; } } // namespace wsl::windows::wslc::settings diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 0a23bd61d..82ab803a5 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -141,18 +141,25 @@ class UserSettings // - If type is Default: creates the primary file from the commented-out defaults template. void PrepareToShellExecuteFile() const; - static std::filesystem::path SettingsFilePath(); + std::filesystem::path SettingsFilePath() const; // Overwrites the primary settings file with the commented-out defaults template. - static void Reset(); + void Reset() const; + +protected: + // Loads settings from an explicit directory. Used by the singleton (via + // the private zero-arg constructor) and by test subclasses. + explicit UserSettings(const std::filesystem::path& settingsDir); + ~UserSettings() = default; private: UserSettings(); - ~UserSettings() = default; SettingsMap m_settings; std::vector m_warnings; UserSettingsType m_type = UserSettingsType::Default; + std::filesystem::path m_primaryPath; + std::filesystem::path m_backupPath; }; // Convenience free function — returns the singleton instance. diff --git a/test/windows/wslc/CMakeLists.txt b/test/windows/wslc/CMakeLists.txt index 23418d7cb..4e91a5411 100644 --- a/test/windows/wslc/CMakeLists.txt +++ b/test/windows/wslc/CMakeLists.txt @@ -28,4 +28,5 @@ target_include_directories(wsltests PRIVATE ${CMAKE_SOURCE_DIR}/src/windows/wslc/arguments ${CMAKE_SOURCE_DIR}/src/windows/wslc/services ${CMAKE_SOURCE_DIR}/src/windows/wslc/tasks + ${CMAKE_SOURCE_DIR}/src/windows/wslc/settings ) diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp new file mode 100644 index 000000000..24154b289 --- /dev/null +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -0,0 +1,330 @@ +/*++ + +Copyright (c) Microsoft. All rights reserved. + +Module Name: + + WSLCCLISettingsUnitTests.cpp + +Abstract: + + Unit tests for the wslc UserSettings system: SettingsMap, YAML loading, + per-setting validation, fallback logic, and UserSettingsType detection. + +--*/ + +#include "precomp.h" +#include "windows/Common.h" +#include "WSLCCLITestHelpers.h" +#include "UserSettings.h" + +#include +#include + +using namespace wsl::windows::wslc::settings; +using namespace WSLCTestHelpers; +using namespace WEX::Logging; +using namespace WEX::Common; +using namespace WEX::TestExecution; + +namespace WSLCCLISettingsUnitTests { + +// Thin subclass that makes the protected constructor publicly accessible, +// allowing tests to load settings from an arbitrary directory without +// going through the singleton. +class UserSettingsTest : public UserSettings +{ +public: + explicit UserSettingsTest(const std::filesystem::path& settingsDir) : UserSettings(settingsDir) + { + } +}; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +static std::atomic s_dirCounter{0}; + +static std::filesystem::path UniqueTempDir() +{ + auto dir = std::filesystem::temp_directory_path() / L"WSLCSettingsTests" / + std::to_wstring(GetCurrentProcessId()) / std::to_wstring(++s_dirCounter); + std::filesystem::create_directories(dir); + return dir; +} + +static void WriteFile(const std::filesystem::path& path, std::string_view content) +{ + std::filesystem::create_directories(path.parent_path()); + std::ofstream f(path, std::ios::binary); + VERIFY_IS_TRUE(f.is_open()); + f.write(content.data(), static_cast(content.size())); +} + +// --------------------------------------------------------------------------- +// Test class +// --------------------------------------------------------------------------- + +class WSLCCLISettingsUnitTests +{ + WSL_TEST_CLASS(WSLCCLISettingsUnitTests) + + TEST_CLASS_SETUP(TestClassSetup) + { + return true; + } + + TEST_CLASS_CLEANUP(TestClassCleanup) + { + std::error_code ec; + std::filesystem::remove_all( + std::filesystem::temp_directory_path() / L"WSLCSettingsTests", ec); + return true; + } + + // ----------------------------------------------------------------------- + // SettingsMap — pure unit tests, no I/O + // ----------------------------------------------------------------------- + + // All four settings should return their compile-time defaults when the map + // is empty (no values have been inserted). + TEST_METHOD(SettingsMap_GetOrDefault_ReturnsBuiltInWhenAbsent) + { + SettingsMap map; + VERIFY_ARE_EQUAL(map.GetOrDefault(), 4u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), 2048u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), 10000u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), std::wstring{}); + } + + // After inserting a value, GetOrDefault must return it rather than the default. + TEST_METHOD(SettingsMap_GetOrDefault_ReturnsStoredWhenPresent) + { + SettingsMap map; + map.Add(16u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), 16u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), 2048u); + } + + // ----------------------------------------------------------------------- + // Default (neither file exists) + // ----------------------------------------------------------------------- + + // When neither primary nor backup exists the type must be Default, there + // must be no warnings, and all values must be at their built-in defaults. + TEST_METHOD(LoadSettings_NoFiles_YieldsDefaultTypeAndNoWarnings) + { + UserSettingsTest s{UniqueTempDir()}; + + VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Default)); + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(s.Get(), 10000u); + VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + } + + // ----------------------------------------------------------------------- + // Standard (valid primary) + // ----------------------------------------------------------------------- + + // A well-formed primary file must set the type to Standard with no + // warnings and the specified values loaded. + TEST_METHOD(LoadSettings_ValidPrimary_YieldsStandardTypeAndValues) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", + "session:\n" + " cpuCount: 8\n" + " memorySizeMb: 4096\n" + " maxStorageSizeMb: 20000\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Standard)); + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(s.Get(), 8u); + VERIFY_ARE_EQUAL(s.Get(), 4096u); + VERIFY_ARE_EQUAL(s.Get(), 20000u); + // Unspecified setting falls back to built-in default. + VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + } + + // An empty primary file is valid YAML (null document); all settings use + // their defaults with no warnings. + TEST_METHOD(LoadSettings_EmptyPrimary_AllDefaultsNoWarnings) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", ""); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(s.Get(), 10000u); + } + + // ----------------------------------------------------------------------- + // Backup fallback + // ----------------------------------------------------------------------- + + // When the primary exists but fails to parse, the backup file is used and + // the type is Backup. + TEST_METHOD(LoadSettings_InvalidPrimary_ValidBackup_YieldsBackupType) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", "session: [\n"); // broken YAML + WriteFile(dir / L"UserSettings.yaml.bak", "session:\n cpuCount: 2\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Backup)); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); + VERIFY_ARE_EQUAL(s.Get(), 2u); + } + + // When the primary is absent and only the backup exists, the type is Backup. + TEST_METHOD(LoadSettings_NoPrimary_ValidBackup_YieldsBackupType) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml.bak", "session:\n memorySizeMb: 1024\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Backup)); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); + VERIFY_ARE_EQUAL(s.Get(), 1024u); + } + + // When both files fail to parse, the type is Default and at least one + // warning is emitted per parse failure. + TEST_METHOD(LoadSettings_BothInvalid_YieldsDefaultTypeWithWarnings) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", ": bad: [\n"); + WriteFile(dir / L"UserSettings.yaml.bak", ": also: bad [\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Default)); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 2u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + } + + // ----------------------------------------------------------------------- + // Per-setting validation + // ----------------------------------------------------------------------- + + // cpuCount: 0 must be rejected; the default (4) is used and a warning emitted. + TEST_METHOD(Validation_CpuCount_Zero_UsesDefaultAndWarns) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", "session:\n cpuCount: 0\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); + VERIFY_IS_FALSE(s.GetWarnings().front().SettingPath.empty()); + } + + // memorySizeMb: 0 must be rejected; the default (2048) is used. + TEST_METHOD(Validation_MemoryMb_Zero_UsesDefaultAndWarns) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", "session:\n memorySizeMb: 0\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); + } + + // maxStorageSizeMb: 0 must be rejected; the default (10000) is used. + TEST_METHOD(Validation_StorageSizeMb_Zero_UsesDefaultAndWarns) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", "session:\n maxStorageSizeMb: 0\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.Get(), 10000u); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); + } + + // A string where a uint32_t is expected must emit a type warning and fall + // back to the default. + TEST_METHOD(Validation_WrongType_UsesDefaultAndWarns) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", "session:\n cpuCount: \"not-a-number\"\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); + } + + // A valid defaultStoragePath string must survive the UTF-8 → wstring round-trip. + TEST_METHOD(Validation_StoragePath_NonEmpty_RoundTrips) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", + "session:\n defaultStoragePath: \"/mnt/data/storage\"\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.Get(), std::wstring(L"/mnt/data/storage")); + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + } + + // An empty defaultStoragePath string is valid. + TEST_METHOD(Validation_StoragePath_Empty_IsValid) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", + "session:\n defaultStoragePath: \"\"\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + } + + // Absent keys must silently use defaults — no warnings emitted. + TEST_METHOD(Validation_AbsentKeys_NoWarningsAndDefaults) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", "session:\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(s.Get(), 10000u); + VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + } + + // Extra unknown keys at any level must not cause errors or warnings. + TEST_METHOD(Validation_UnknownKeys_NoErrorsOrWarnings) + { + auto dir = UniqueTempDir(); + WriteFile(dir / L"UserSettings.yaml", + "session:\n" + " cpuCount: 4\n" + " unknownSetting: 99\n" + "unknownSection:\n" + " foo: bar\n"); + + UserSettingsTest s{dir}; + + VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Standard)); + VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + } +}; + +} // namespace WSLCCLISettingsUnitTests From 2589a01cfe6dd3dff8bf68fcd18c23b1c63b605a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:20:52 -0700 Subject: [PATCH 14/25] fix --- src/windows/wslc/settings/UserSettings.cpp | 10 +++++++++- test/windows/wslc/WSLCCLISettingsUnitTests.cpp | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 8075b439a..d3c17b08a 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -89,7 +89,15 @@ namespace { { if (current.IsDefined() && current.IsMap()) { - current = current[subPath]; + // Use the const operator[] to avoid yaml-cpp's AssignNode/set_ref side-effect, + // which mutates the shared detail::node and corrupts subsequent lookups. + // Then use reset() to rebind 'current' without triggering set_ref. + auto child = static_cast(current)[subPath]; + if (!child.IsDefined()) + { + return std::nullopt; + } + current.reset(child); } else { diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index 24154b289..2a89dd95f 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -203,8 +203,8 @@ class WSLCCLISettingsUnitTests TEST_METHOD(LoadSettings_BothInvalid_YieldsDefaultTypeWithWarnings) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", ": bad: [\n"); - WriteFile(dir / L"UserSettings.yaml.bak", ": also: bad [\n"); + WriteFile(dir / L"UserSettings.yaml", ": bad: [\n"); // broken YAML (unclosed flow seq) + WriteFile(dir / L"UserSettings.yaml.bak", "session: [\n"); // broken YAML (unclosed flow seq) UserSettingsTest s{dir}; From 59808c826e45300806484d50c2733b0379b05ea9 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 13:28:16 -0700 Subject: [PATCH 15/25] clang --- .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 67 +++++++++---------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index 2a89dd95f..b49df7870 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -48,8 +48,8 @@ static std::atomic s_dirCounter{0}; static std::filesystem::path UniqueTempDir() { - auto dir = std::filesystem::temp_directory_path() / L"WSLCSettingsTests" / - std::to_wstring(GetCurrentProcessId()) / std::to_wstring(++s_dirCounter); + auto dir = std::filesystem::temp_directory_path() / L"WSLCSettingsTests" / std::to_wstring(GetCurrentProcessId()) / + std::to_wstring(++s_dirCounter); std::filesystem::create_directories(dir); return dir; } @@ -78,8 +78,7 @@ class WSLCCLISettingsUnitTests TEST_CLASS_CLEANUP(TestClassCleanup) { std::error_code ec; - std::filesystem::remove_all( - std::filesystem::temp_directory_path() / L"WSLCSettingsTests", ec); + std::filesystem::remove_all(std::filesystem::temp_directory_path() / L"WSLCSettingsTests", ec); return true; } @@ -92,10 +91,10 @@ class WSLCCLISettingsUnitTests TEST_METHOD(SettingsMap_GetOrDefault_ReturnsBuiltInWhenAbsent) { SettingsMap map; - VERIFY_ARE_EQUAL(map.GetOrDefault(), 4u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), 2048u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), 4u); + VERIFY_ARE_EQUAL(map.GetOrDefault(), 2048u); VERIFY_ARE_EQUAL(map.GetOrDefault(), 10000u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), std::wstring{}); + VERIFY_ARE_EQUAL(map.GetOrDefault(), std::wstring{}); } // After inserting a value, GetOrDefault must return it rather than the default. @@ -119,10 +118,10 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Default)); VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); - VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(s.Get(), 2048u); VERIFY_ARE_EQUAL(s.Get(), 10000u); - VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); } // ----------------------------------------------------------------------- @@ -134,18 +133,19 @@ class WSLCCLISettingsUnitTests TEST_METHOD(LoadSettings_ValidPrimary_YieldsStandardTypeAndValues) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", - "session:\n" - " cpuCount: 8\n" - " memorySizeMb: 4096\n" - " maxStorageSizeMb: 20000\n"); + WriteFile( + dir / L"UserSettings.yaml", + "session:\n" + " cpuCount: 8\n" + " memorySizeMb: 4096\n" + " maxStorageSizeMb: 20000\n"); UserSettingsTest s{dir}; VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Standard)); VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 8u); - VERIFY_ARE_EQUAL(s.Get(), 4096u); + VERIFY_ARE_EQUAL(s.Get(), 8u); + VERIFY_ARE_EQUAL(s.Get(), 4096u); VERIFY_ARE_EQUAL(s.Get(), 20000u); // Unspecified setting falls back to built-in default. VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); @@ -161,8 +161,8 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); - VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(s.Get(), 2048u); VERIFY_ARE_EQUAL(s.Get(), 10000u); } @@ -175,7 +175,7 @@ class WSLCCLISettingsUnitTests TEST_METHOD(LoadSettings_InvalidPrimary_ValidBackup_YieldsBackupType) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", "session: [\n"); // broken YAML + WriteFile(dir / L"UserSettings.yaml", "session: [\n"); // broken YAML WriteFile(dir / L"UserSettings.yaml.bak", "session:\n cpuCount: 2\n"); UserSettingsTest s{dir}; @@ -203,7 +203,7 @@ class WSLCCLISettingsUnitTests TEST_METHOD(LoadSettings_BothInvalid_YieldsDefaultTypeWithWarnings) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", ": bad: [\n"); // broken YAML (unclosed flow seq) + WriteFile(dir / L"UserSettings.yaml", ": bad: [\n"); // broken YAML (unclosed flow seq) WriteFile(dir / L"UserSettings.yaml.bak", "session: [\n"); // broken YAML (unclosed flow seq) UserSettingsTest s{dir}; @@ -271,8 +271,7 @@ class WSLCCLISettingsUnitTests TEST_METHOD(Validation_StoragePath_NonEmpty_RoundTrips) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", - "session:\n defaultStoragePath: \"/mnt/data/storage\"\n"); + WriteFile(dir / L"UserSettings.yaml", "session:\n defaultStoragePath: \"/mnt/data/storage\"\n"); UserSettingsTest s{dir}; @@ -284,8 +283,7 @@ class WSLCCLISettingsUnitTests TEST_METHOD(Validation_StoragePath_Empty_IsValid) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", - "session:\n defaultStoragePath: \"\"\n"); + WriteFile(dir / L"UserSettings.yaml", "session:\n defaultStoragePath: \"\"\n"); UserSettingsTest s{dir}; @@ -302,22 +300,23 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); - VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(s.Get(), 2048u); VERIFY_ARE_EQUAL(s.Get(), 10000u); - VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); } // Extra unknown keys at any level must not cause errors or warnings. TEST_METHOD(Validation_UnknownKeys_NoErrorsOrWarnings) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", - "session:\n" - " cpuCount: 4\n" - " unknownSetting: 99\n" - "unknownSection:\n" - " foo: bar\n"); + WriteFile( + dir / L"UserSettings.yaml", + "session:\n" + " cpuCount: 4\n" + " unknownSetting: 99\n" + "unknownSection:\n" + " foo: bar\n"); UserSettingsTest s{dir}; From 3a91accd23d55e4b243b9b5ecad07b19ad57a49c Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:00:58 -0700 Subject: [PATCH 16/25] fix storage path --- src/windows/wslc/services/SessionModel.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index ac19cd079..cbcc86889 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -20,17 +20,17 @@ namespace wsl::windows::wslc::models { SessionOptions SessionOptions::Default() { // Use a function-local static to defer path initialization until first use. - static const std::filesystem::path defaultPath = { - wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\defaultstorage"}; + static const std::filesystem::path storagePath = + settings::User().Get().empty() + ? std::filesystem::path{wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\defaultstorage"} + : settings::User().Get().c_str(); SessionOptions options{}; options.m_sessionSettings.DisplayName = s_DefaultSessionName; options.m_sessionSettings.CpuCount = settings::User().Get(); options.m_sessionSettings.MemoryMb = settings::User().Get(); options.m_sessionSettings.BootTimeoutMs = 30 * 1000; - options.m_sessionSettings.StoragePath = settings::User().Get().empty() - ? defaultPath.c_str() - : settings::User().Get().c_str(); + options.m_sessionSettings.StoragePath = storagePath.c_str(); options.m_sessionSettings.MaximumStorageSizeMb = settings::User().Get(); options.m_sessionSettings.NetworkingMode = WSLANetworkingModeVirtioProxy; return options; From e8fdaf447a8229b75132a63533a19d73eb553485 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:48:09 -0700 Subject: [PATCH 17/25] pr comments --- src/windows/wslc/commands/SettingsCommand.cpp | 4 +--- src/windows/wslc/settings/UserSettings.cpp | 19 ++++++++++++++++++- test/windows/wslc/CommandLineTestCases.h | 4 ++++ .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 4 ++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index b21aec2e5..d0728dcc6 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -78,9 +78,7 @@ std::wstring SettingsResetCommand::ShortDescription() const std::wstring SettingsResetCommand::LongDescription() const { - return { - L"Overwrites the settings file with a commented-out defaults template.\n" - L"Use --force / -f to skip the confirmation prompt."}; + return {L"Overwrites the settings file with a commented-out defaults template."}; } void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index d3c17b08a..1b8477bb1 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -41,7 +41,7 @@ static constexpr std::string_view s_DefaultSettingsTemplate = " # Maximum disk image size in megabytes (default: 10000)\n" " # maxStorageSizeMb: 10000\n" "\n" - " # Default path for container storage (default: %LocalAppData%\\wslc\\storage)\n" + " # Default path for container storage (default: %LocalAppData%\\wslc\\defaultstorage)\n" " # defaultStoragePath: \"\"\n"; // Validate individual setting specializations @@ -155,6 +155,14 @@ namespace { std::ifstream stream(path); if (!stream.is_open()) { + // If the file exists but cannot be opened (permissions, sharing violation, etc.), + // emit a warning so the user understands why settings were ignored. + if (std::filesystem::exists(path)) + { + warnings.push_back( + {std::format(L"Warning: '{}' exists but could not be opened. Settings will be ignored.", path.filename().wstring()), {}}); + } + return std::nullopt; } @@ -241,6 +249,15 @@ void UserSettings::PrepareToShellExecuteFile() const // First run — create the directory and write the commented-out defaults template. Reset(); } + else if (m_type == UserSettingsType::Backup) + { + // Reset to defaults only when primary file does not exist. Otherwise, give + // user the chance to continue working on their incomplete settings. + if (!std::filesystem::exists(m_primaryPath)) + { + Reset(); + } + } } std::filesystem::path UserSettings::SettingsFilePath() const diff --git a/test/windows/wslc/CommandLineTestCases.h b/test/windows/wslc/CommandLineTestCases.h index 32c471461..04bd94c1c 100644 --- a/test/windows/wslc/CommandLineTestCases.h +++ b/test/windows/wslc/CommandLineTestCases.h @@ -111,6 +111,10 @@ COMMAND_LINE_TEST_CASE(L"image list -q", L"list", true) COMMAND_LINE_TEST_CASE(L"image pull ubuntu", L"pull", true) COMMAND_LINE_TEST_CASE(L"pull ubuntu", L"pull", true) +// Settings command +COMMAND_LINE_TEST_CASE(L"settings", L"settings", true) +COMMAND_LINE_TEST_CASE(L"settings reset", L"reset", true) + // Error cases COMMAND_LINE_TEST_CASE(L"invalid command", L"", false) COMMAND_LINE_TEST_CASE(L"CONTAINER list", L"list", false) // We are intentionally case-sensitive diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index b49df7870..88629a5e4 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -271,11 +271,11 @@ class WSLCCLISettingsUnitTests TEST_METHOD(Validation_StoragePath_NonEmpty_RoundTrips) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", "session:\n defaultStoragePath: \"/mnt/data/storage\"\n"); + WriteFile(dir / L"UserSettings.yaml", "session:\n defaultStoragePath: \"C:\\\\TestFolder\"\n"); UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), std::wstring(L"/mnt/data/storage")); + VERIFY_ARE_EQUAL(s.Get(), std::wstring(L"C:\\TestFolder")); VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); } From 1cc304436a7a9137d70c9248336bba5e4a013f7b Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:05:05 -0700 Subject: [PATCH 18/25] swap --- .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index 88629a5e4..8e304eae3 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -91,10 +91,10 @@ class WSLCCLISettingsUnitTests TEST_METHOD(SettingsMap_GetOrDefault_ReturnsBuiltInWhenAbsent) { SettingsMap map; - VERIFY_ARE_EQUAL(map.GetOrDefault(), 4u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), 2048u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), 10000u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), std::wstring{}); + VERIFY_ARE_EQUAL(4u, map.GetOrDefault()); + VERIFY_ARE_EQUAL(2048u, map.GetOrDefault()); + VERIFY_ARE_EQUAL(10000u, map.GetOrDefault()); + VERIFY_ARE_EQUAL(std::wstring{}, map.GetOrDefault()); } // After inserting a value, GetOrDefault must return it rather than the default. @@ -102,8 +102,8 @@ class WSLCCLISettingsUnitTests { SettingsMap map; map.Add(16u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), 16u); - VERIFY_ARE_EQUAL(map.GetOrDefault(), 2048u); + VERIFY_ARE_EQUAL(16u, map.GetOrDefault()); + VERIFY_ARE_EQUAL(2048u, map.GetOrDefault()); } // ----------------------------------------------------------------------- @@ -116,12 +116,12 @@ class WSLCCLISettingsUnitTests { UserSettingsTest s{UniqueTempDir()}; - VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Default)); - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); - VERIFY_ARE_EQUAL(s.Get(), 2048u); - VERIFY_ARE_EQUAL(s.Get(), 10000u); - VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Default), static_cast(s.GetType())); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); + VERIFY_ARE_EQUAL(4u, s.Get()); + VERIFY_ARE_EQUAL(2048u, s.Get()); + VERIFY_ARE_EQUAL(10000u, s.Get()); + VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } // ----------------------------------------------------------------------- @@ -142,13 +142,13 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Standard)); - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 8u); - VERIFY_ARE_EQUAL(s.Get(), 4096u); - VERIFY_ARE_EQUAL(s.Get(), 20000u); + VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Standard), static_cast(s.GetType())); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); + VERIFY_ARE_EQUAL(8u, s.Get()); + VERIFY_ARE_EQUAL(4096u, s.Get()); + VERIFY_ARE_EQUAL(20000u, s.Get()); // Unspecified setting falls back to built-in default. - VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } // An empty primary file is valid YAML (null document); all settings use @@ -160,10 +160,10 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); - VERIFY_ARE_EQUAL(s.Get(), 2048u); - VERIFY_ARE_EQUAL(s.Get(), 10000u); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); + VERIFY_ARE_EQUAL(4u, s.Get()); + VERIFY_ARE_EQUAL(2048u, s.Get()); + VERIFY_ARE_EQUAL(10000u, s.Get()); } // ----------------------------------------------------------------------- @@ -180,9 +180,9 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Backup)); + VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Backup), static_cast(s.GetType())); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); - VERIFY_ARE_EQUAL(s.Get(), 2u); + VERIFY_ARE_EQUAL(2u, s.Get()); } // When the primary is absent and only the backup exists, the type is Backup. @@ -193,9 +193,9 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Backup)); + VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Backup), static_cast(s.GetType())); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); - VERIFY_ARE_EQUAL(s.Get(), 1024u); + VERIFY_ARE_EQUAL(1024u, s.Get()); } // When both files fail to parse, the type is Default and at least one @@ -208,9 +208,9 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Default)); + VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Default), static_cast(s.GetType())); VERIFY_IS_TRUE(s.GetWarnings().size() >= 2u); - VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(4u, s.Get()); } // ----------------------------------------------------------------------- @@ -225,7 +225,7 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); VERIFY_IS_FALSE(s.GetWarnings().front().SettingPath.empty()); } @@ -238,7 +238,7 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), 2048u); + VERIFY_ARE_EQUAL(2048u, s.Get()); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } @@ -250,7 +250,7 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), 10000u); + VERIFY_ARE_EQUAL(10000u, s.Get()); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } @@ -263,7 +263,7 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } @@ -275,8 +275,8 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), std::wstring(L"C:\\TestFolder")); - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(std::wstring(L"C:\\TestFolder"), s.Get()); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); } // An empty defaultStoragePath string is valid. @@ -287,8 +287,8 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); + VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); } // Absent keys must silently use defaults — no warnings emitted. @@ -299,11 +299,11 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); - VERIFY_ARE_EQUAL(s.Get(), 2048u); - VERIFY_ARE_EQUAL(s.Get(), 10000u); - VERIFY_ARE_EQUAL(s.Get(), std::wstring{}); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); + VERIFY_ARE_EQUAL(4u, s.Get()); + VERIFY_ARE_EQUAL(2048u, s.Get()); + VERIFY_ARE_EQUAL(10000u, s.Get()); + VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } // Extra unknown keys at any level must not cause errors or warnings. @@ -320,9 +320,9 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(static_cast(s.GetType()), static_cast(UserSettingsType::Standard)); - VERIFY_ARE_EQUAL(s.GetWarnings().size(), 0u); - VERIFY_ARE_EQUAL(s.Get(), 4u); + VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Standard), static_cast(s.GetType())); + VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); + VERIFY_ARE_EQUAL(4u, s.Get()); } }; From fddd922d8e5510b4f0bdd01b37a37cc7c2defe50 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:01:03 -0700 Subject: [PATCH 19/25] fix test --- test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp index c2927c950..388d3d02a 100644 --- a/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EGlobalTests.cpp @@ -74,6 +74,7 @@ class WSLCE2EGlobalTests << L" container Container command.\r\n" << L" image Image command.\r\n" << L" session Session command.\r\n" + << L" settings Open the settings file in the default editor.\r\n" << L" attach Attach to a container.\r\n" << L" build Build an image from a Dockerfile.\r\n" << L" create Create a container.\r\n" From 7118b30ac89894921f322f2022940c38233af931 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:21:17 -0700 Subject: [PATCH 20/25] pr comments --- src/windows/wslc/commands/SettingsCommand.cpp | 7 ++++--- src/windows/wslc/settings/UserSettings.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index d0728dcc6..0cc94668f 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -15,7 +15,6 @@ Module Name: #include "SettingsCommand.h" #include "UserSettings.h" #include "wslutil.h" -#include using namespace wsl::windows::common::wslutil; using namespace wsl::windows::wslc::execution; @@ -53,7 +52,7 @@ void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const { settings::User().PrepareToShellExecuteFile(); - const auto path = settings::User().SettingsFilePath(); + const auto& path = settings::User().SettingsFilePath(); // Some versions of windows will fail if no file extension association exists, other will pop up the dialog // to make the user pick their default. @@ -61,7 +60,9 @@ void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const if (static_cast(reinterpret_cast(res)) <= 32) { // User doesn't have file type association. Default to notepad - ShellExecuteW(nullptr, nullptr, L"notepad", path.c_str(), nullptr, SW_SHOW); + // Quote the path so that Notepad treats it as a single argument even if it contains spaces. + std::wstring quotedPath = L"\"" + path.wstring() + L"\""; + ShellExecuteW(nullptr, nullptr, L"notepad", quotedPath.c_str(), nullptr, SW_SHOW); } } diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 1b8477bb1..e0daaf5c3 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -134,7 +134,7 @@ namespace { warnings.push_back({std::format(L"Warning: Invalid value for setting '{}'. Using default.", widePath), widePath}); } } - catch (const YAML::Exception&) + catch (...) { const auto widePath = MultiByteToWide(path); warnings.push_back({std::format(L"Warning: Invalid type for setting '{}'. Using default.", widePath), widePath}); @@ -176,6 +176,11 @@ namespace { {std::format(L"Warning: '{}' could not be parsed: {}.", path.filename().wstring(), MultiByteToWide(e.what())), {}}); return std::nullopt; } + catch (...) + { + warnings.push_back({std::format(L"Warning: '{}' could not be parsed.", path.filename().wstring()), {}}); + return std::nullopt; + } } const std::filesystem::path& SettingsDir() From 7e5baa37b071244c1f2c0a294f08ab0556f3b124 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:49:59 -0700 Subject: [PATCH 21/25] pr comments --- src/windows/wslc/commands/SettingsCommand.cpp | 8 ++++---- src/windows/wslc/core/Main.cpp | 7 ------- src/windows/wslc/services/SessionModel.cpp | 2 +- src/windows/wslc/settings/UserSettings.cpp | 15 ++++++++++----- src/windows/wslc/settings/UserSettings.h | 8 ++++---- test/windows/wslc/WSLCCLISettingsUnitTests.cpp | 12 ++++++------ 6 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/windows/wslc/commands/SettingsCommand.cpp b/src/windows/wslc/commands/SettingsCommand.cpp index 0cc94668f..886b40092 100644 --- a/src/windows/wslc/commands/SettingsCommand.cpp +++ b/src/windows/wslc/commands/SettingsCommand.cpp @@ -44,8 +44,7 @@ std::wstring SettingsCommand::LongDescription() const { return { L"Opens the wslc user settings file in the system default editor for .yaml files.\n" - L"On first run, creates the file with all settings commented out at their defaults.\n" - L"A backup of the current settings is saved before the editor opens."}; + L"On first run, creates the file with all settings commented out at their defaults."}; } void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const @@ -61,8 +60,9 @@ void SettingsCommand::ExecuteInternal(CLIExecutionContext& context) const { // User doesn't have file type association. Default to notepad // Quote the path so that Notepad treats it as a single argument even if it contains spaces. + std::filesystem::path notepadPath = std::filesystem::path{wil::GetSystemDirectoryW().get()} / L"notepad.exe"; std::wstring quotedPath = L"\"" + path.wstring() + L"\""; - ShellExecuteW(nullptr, nullptr, L"notepad", quotedPath.c_str(), nullptr, SW_SHOW); + ShellExecuteW(nullptr, nullptr, notepadPath.c_str(), quotedPath.c_str(), nullptr, SW_SHOW); } } @@ -84,7 +84,7 @@ std::wstring SettingsResetCommand::LongDescription() const void SettingsResetCommand::ExecuteInternal(CLIExecutionContext& context) const { - // Todo: do we need prompt support? + // TODO: do we need prompt support? settings::User().Reset(); PrintMessage(L"Settings reset to defaults."); } diff --git a/src/windows/wslc/core/Main.cpp b/src/windows/wslc/core/Main.cpp index 1616e5c37..810053730 100644 --- a/src/windows/wslc/core/Main.cpp +++ b/src/windows/wslc/core/Main.cpp @@ -56,13 +56,6 @@ try try { - // Emit any settings load warnings. - for (const auto& warning : settings::User().GetWarnings()) - { - // Todo: print as warning after reporter support, or maybe move to output warnings only in settings commands. - wslutil::PrintMessage(warning.Message); - } - std::vector args; for (int i = 1; i < argc; ++i) { diff --git a/src/windows/wslc/services/SessionModel.cpp b/src/windows/wslc/services/SessionModel.cpp index cbcc86889..ae755d3ff 100644 --- a/src/windows/wslc/services/SessionModel.cpp +++ b/src/windows/wslc/services/SessionModel.cpp @@ -22,7 +22,7 @@ SessionOptions SessionOptions::Default() // Use a function-local static to defer path initialization until first use. static const std::filesystem::path storagePath = settings::User().Get().empty() - ? std::filesystem::path{wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\defaultstorage"} + ? std::filesystem::path{wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\storage"} : settings::User().Get().c_str(); SessionOptions options{}; diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index e0daaf5c3..1d21742eb 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -14,6 +14,7 @@ Module Name: #include "UserSettings.h" #include "filesystem.hpp" #include "string.hpp" +#include "wslutil.h" #include #include #include @@ -38,10 +39,10 @@ static constexpr std::string_view s_DefaultSettingsTemplate = " # Memory limit for the session in megabytes (default: 2048)\n" " # memorySizeMb: 2048\n" "\n" - " # Maximum disk image size in megabytes (default: 10000)\n" - " # maxStorageSizeMb: 10000\n" + " # Maximum disk image size in megabytes (default: 100000)\n" + " # maxStorageSizeMb: 100000\n" "\n" - " # Default path for container storage (default: %LocalAppData%\\wslc\\defaultstorage)\n" + " # Default path for container storage (default: %LocalAppData%\\wslc\\storage)\n" " # defaultStoragePath: \"\"\n"; // Validate individual setting specializations @@ -230,6 +231,12 @@ UserSettings::UserSettings(const std::filesystem::path& settingsDir) // TODO: Iterate through all nodes and warn about unknown keys? } + + // Emit any settings load warnings. + for (const auto& warning : m_warnings) + { + wsl::windows::common::wslutil::PrintMessage(warning.Message, stderr); + } } void UserSettings::Reset() const @@ -238,8 +245,6 @@ void UserSettings::Reset() const std::ofstream file(m_primaryPath); THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); file << s_DefaultSettingsTemplate; - file.flush(); - file.close(); } void UserSettings::PrepareToShellExecuteFile() const diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 82ab803a5..10f61bcb5 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -66,10 +66,10 @@ namespace details { static std::optional Validate(const yaml_t& value); \ }; - DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") - DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") - DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 10000, "session.maxStorageSizeMb") - DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") + DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") + DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") + DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 100000, "session.maxStorageSizeMb") + DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") #undef DEFINE_SETTING_MAPPING // clang-format on diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index 8e304eae3..8735c31a4 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -93,7 +93,7 @@ class WSLCCLISettingsUnitTests SettingsMap map; VERIFY_ARE_EQUAL(4u, map.GetOrDefault()); VERIFY_ARE_EQUAL(2048u, map.GetOrDefault()); - VERIFY_ARE_EQUAL(10000u, map.GetOrDefault()); + VERIFY_ARE_EQUAL(100000u, map.GetOrDefault()); VERIFY_ARE_EQUAL(std::wstring{}, map.GetOrDefault()); } @@ -120,7 +120,7 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_ARE_EQUAL(2048u, s.Get()); - VERIFY_ARE_EQUAL(10000u, s.Get()); + VERIFY_ARE_EQUAL(100000u, s.Get()); VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } @@ -163,7 +163,7 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_ARE_EQUAL(2048u, s.Get()); - VERIFY_ARE_EQUAL(10000u, s.Get()); + VERIFY_ARE_EQUAL(100000u, s.Get()); } // ----------------------------------------------------------------------- @@ -242,7 +242,7 @@ class WSLCCLISettingsUnitTests VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } - // maxStorageSizeMb: 0 must be rejected; the default (10000) is used. + // maxStorageSizeMb: 0 must be rejected; the default (100000) is used. TEST_METHOD(Validation_StorageSizeMb_Zero_UsesDefaultAndWarns) { auto dir = UniqueTempDir(); @@ -250,7 +250,7 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(10000u, s.Get()); + VERIFY_ARE_EQUAL(100000u, s.Get()); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } @@ -302,7 +302,7 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_ARE_EQUAL(2048u, s.Get()); - VERIFY_ARE_EQUAL(10000u, s.Get()); + VERIFY_ARE_EQUAL(100000u, s.Get()); VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } From 70d42e4ab8f219bc64f04b12aa2a44c6510d163a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:02:44 -0700 Subject: [PATCH 22/25] remove backup settings --- src/windows/wslc/settings/UserSettings.cpp | 40 +++---------- src/windows/wslc/settings/UserSettings.h | 16 ++---- .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 56 ++++--------------- 3 files changed, 24 insertions(+), 88 deletions(-) diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index 1d21742eb..e3c8151d0 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -8,7 +8,7 @@ Module Name: Abstract: - Implementation of UserSettings — YAML loading, validation, and fallback logic. + Implementation of UserSettings — YAML loading and validation. --*/ #include "UserSettings.h" @@ -204,25 +204,13 @@ UserSettings::UserSettings() : UserSettings(SettingsDir()) UserSettings::UserSettings(const std::filesystem::path& settingsDir) { - m_primaryPath = settingsDir / L"UserSettings.yaml"; - m_backupPath = settingsDir / L"UserSettings.yaml.bak"; + m_settingsPath = settingsDir / L"UserSettings.yaml"; - // Try the primary file first. - auto root = TryLoadYaml(m_primaryPath, m_warnings); + auto root = TryLoadYaml(m_settingsPath, m_warnings); if (root.has_value()) { m_type = UserSettingsType::Standard; } - else - { - // Primary missing or failed — try the backup. - root = TryLoadYaml(m_backupPath, m_warnings); - if (root.has_value()) - { - m_type = UserSettingsType::Backup; - m_warnings.push_back({L"Warning: UserSettings.yaml could not be loaded. Using backup settings.", {}}); - } - } if (root.has_value()) { @@ -241,38 +229,24 @@ UserSettings::UserSettings(const std::filesystem::path& settingsDir) void UserSettings::Reset() const { - std::filesystem::create_directories(m_primaryPath.parent_path()); - std::ofstream file(m_primaryPath); + std::filesystem::create_directories(m_settingsPath.parent_path()); + std::ofstream file(m_settingsPath); THROW_HR_IF_MSG(E_UNEXPECTED, !file.is_open(), "Failed to create settings file"); file << s_DefaultSettingsTemplate; } void UserSettings::PrepareToShellExecuteFile() const { - if (m_type == UserSettingsType::Standard) - { - // Valid settings loaded — back them up before the user edits. - std::filesystem::copy_file(m_primaryPath, m_backupPath, std::filesystem::copy_options::overwrite_existing); - } - else if (m_type == UserSettingsType::Default) + if (m_type == UserSettingsType::Default) { // First run — create the directory and write the commented-out defaults template. Reset(); } - else if (m_type == UserSettingsType::Backup) - { - // Reset to defaults only when primary file does not exist. Otherwise, give - // user the chance to continue working on their incomplete settings. - if (!std::filesystem::exists(m_primaryPath)) - { - Reset(); - } - } } std::filesystem::path UserSettings::SettingsFilePath() const { - return m_primaryPath; + return m_settingsPath; } } // namespace wsl::windows::wslc::settings diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 10f61bcb5..ed113ed6a 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -94,9 +94,8 @@ struct SettingsMap : wsl::windows::wslc::EnumBasedVariantMap m_warnings; UserSettingsType m_type = UserSettingsType::Default; - std::filesystem::path m_primaryPath; - std::filesystem::path m_backupPath; + std::filesystem::path m_settingsPath; }; // Convenience free function — returns the singleton instance. diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index 8735c31a4..6b01f5b29 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -107,10 +107,10 @@ class WSLCCLISettingsUnitTests } // ----------------------------------------------------------------------- - // Default (neither file exists) + // Default (setting file missing) // ----------------------------------------------------------------------- - // When neither primary nor backup exists the type must be Default, there + // When settings file missing, the type must be Default, there // must be no warnings, and all values must be at their built-in defaults. TEST_METHOD(LoadSettings_NoFiles_YieldsDefaultTypeAndNoWarnings) { @@ -125,12 +125,12 @@ class WSLCCLISettingsUnitTests } // ----------------------------------------------------------------------- - // Standard (valid primary) + // Standard (valid settings) // ----------------------------------------------------------------------- - // A well-formed primary file must set the type to Standard with no + // A well-formed settings file must set the type to Standard with no // warnings and the specified values loaded. - TEST_METHOD(LoadSettings_ValidPrimary_YieldsStandardTypeAndValues) + TEST_METHOD(LoadSettings_ValidSettings_YieldsStandardTypeAndValues) { auto dir = UniqueTempDir(); WriteFile( @@ -151,9 +151,9 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } - // An empty primary file is valid YAML (null document); all settings use + // An empty settings file is valid YAML (null document); all settings use // their defaults with no warnings. - TEST_METHOD(LoadSettings_EmptyPrimary_AllDefaultsNoWarnings) + TEST_METHOD(LoadSettings_EmptySettings_AllDefaultsNoWarnings) { auto dir = UniqueTempDir(); WriteFile(dir / L"UserSettings.yaml", ""); @@ -166,50 +166,16 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(100000u, s.Get()); } - // ----------------------------------------------------------------------- - // Backup fallback - // ----------------------------------------------------------------------- - - // When the primary exists but fails to parse, the backup file is used and - // the type is Backup. - TEST_METHOD(LoadSettings_InvalidPrimary_ValidBackup_YieldsBackupType) - { - auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", "session: [\n"); // broken YAML - WriteFile(dir / L"UserSettings.yaml.bak", "session:\n cpuCount: 2\n"); - - UserSettingsTest s{dir}; - - VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Backup), static_cast(s.GetType())); - VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); - VERIFY_ARE_EQUAL(2u, s.Get()); - } - - // When the primary is absent and only the backup exists, the type is Backup. - TEST_METHOD(LoadSettings_NoPrimary_ValidBackup_YieldsBackupType) - { - auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml.bak", "session:\n memorySizeMb: 1024\n"); - - UserSettingsTest s{dir}; - - VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Backup), static_cast(s.GetType())); - VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); - VERIFY_ARE_EQUAL(1024u, s.Get()); - } - - // When both files fail to parse, the type is Default and at least one - // warning is emitted per parse failure. - TEST_METHOD(LoadSettings_BothInvalid_YieldsDefaultTypeWithWarnings) + // When the settings file fails to parse, the type is Default and a warning is emitted. + TEST_METHOD(LoadSettings_InvalidSettings_YieldsDefaultTypeWithWarning) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", ": bad: [\n"); // broken YAML (unclosed flow seq) - WriteFile(dir / L"UserSettings.yaml.bak", "session: [\n"); // broken YAML (unclosed flow seq) + WriteFile(dir / L"UserSettings.yaml", "session: [\n"); // broken YAML (unclosed flow seq) UserSettingsTest s{dir}; VERIFY_ARE_EQUAL(static_cast(UserSettingsType::Default), static_cast(s.GetType())); - VERIFY_IS_TRUE(s.GetWarnings().size() >= 2u); + VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); VERIFY_ARE_EQUAL(4u, s.Get()); } From 53e1a2ccd329dbf1664b0ddf36c1af9f11fc91d9 Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:12:52 -0700 Subject: [PATCH 23/25] comments --- cgmanifest.json | 11 ++++++ src/windows/wslc/settings/UserSettings.cpp | 34 ++++++++++--------- src/windows/wslc/settings/UserSettings.h | 8 ++--- .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 8 ++--- 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/cgmanifest.json b/cgmanifest.json index c37443585..87de3a5f1 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -41,6 +41,17 @@ "hash": "sha1:44500f8d6b279ec314a4cdce1290ddc30f530ed7" } } + }, + { + "component": { + "type": "other", + "other": { + "name": "yaml-cpp", + "version": "0.9.0", + "downloadUrl": "https://github.com/jbeder/yaml-cpp/releases/download/yaml-cpp-0.9.0/yaml-cpp-yaml-cpp-0.9.0.tar.gz", + "hash": "sha256:298593d9c440fd9034b8b193d96318b76d49bc97c6ceadb7b0836edf0b6d7539" + } + } } ] } \ No newline at end of file diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index e3c8151d0..c3befab23 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -36,11 +36,11 @@ static constexpr std::string_view s_DefaultSettingsTemplate = " # Number of virtual CPUs allocated to the session (default: 4)\n" " # cpuCount: 4\n" "\n" - " # Memory limit for the session in megabytes (default: 2048)\n" - " # memorySizeMb: 2048\n" + " # Memory limit for the session in megabytes (default: 2GB)\n" + " # memorySize: 2GB\n" "\n" - " # Maximum disk image size in megabytes (default: 100000)\n" - " # maxStorageSizeMb: 100000\n" + " # Maximum disk image size in megabytes (default: 100GB)\n" + " # maxStorageSize: 100GB\n" "\n" " # Default path for container storage (default: %LocalAppData%\\wslc\\storage)\n" " # defaultStoragePath: \"\"\n"; @@ -48,6 +48,13 @@ static constexpr std::string_view s_DefaultSettingsTemplate = // Validate individual setting specializations namespace details { + std::optional ParseSettingsMemoryValue(const std::string& value) + { + auto parsed = wsl::shared::string::ParseMemorySize(value.c_str()); + auto converted = parsed.has_value() ? *parsed / 1048576 : 0; // To Mb, and anything less than 1Mb is considered invalid. + return converted > 0 ? std::optional{static_cast(converted)} : std::nullopt; + } + #define WSLC_VALIDATE_SETTING(_setting_) \ std::optional::value_t> SettingMapping::Validate( \ const SettingMapping::yaml_t& value) @@ -59,12 +66,12 @@ namespace details { WSLC_VALIDATE_SETTING(SessionMemoryMb) { - return value > 0 ? std::optional{value} : std::nullopt; + return ParseSettingsMemoryValue(value); } WSLC_VALIDATE_SETTING(SessionStorageSizeMb) { - return value > 0 ? std::optional{value} : std::nullopt; + return ParseSettingsMemoryValue(value); } // yaml_t = std::string (UTF-8 from yaml-cpp), value_t = std::wstring @@ -156,12 +163,13 @@ namespace { std::ifstream stream(path); if (!stream.is_open()) { + auto err = errno; // If the file exists but cannot be opened (permissions, sharing violation, etc.), // emit a warning so the user understands why settings were ignored. - if (std::filesystem::exists(path)) + if (err != ENOENT) { warnings.push_back( - {std::format(L"Warning: '{}' exists but could not be opened. Settings will be ignored.", path.filename().wstring()), {}}); + {std::format(L"Warning: Failed to open '{}', errno: {}. Using default settings.", path.filename().wstring(), err), {}}); } return std::nullopt; @@ -171,23 +179,17 @@ namespace { { return YAML::Load(stream); } - catch (const YAML::Exception& e) + catch (const std::exception& e) { warnings.push_back( {std::format(L"Warning: '{}' could not be parsed: {}.", path.filename().wstring(), MultiByteToWide(e.what())), {}}); return std::nullopt; } - catch (...) - { - warnings.push_back({std::format(L"Warning: '{}' could not be parsed.", path.filename().wstring()), {}}); - return std::nullopt; - } } const std::filesystem::path& SettingsDir() { - static const std::filesystem::path dir = - wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc\\settings"; + static const std::filesystem::path dir = wsl::windows::common::filesystem::GetLocalAppDataPath(nullptr) / L"wslc"; return dir; } } // namespace diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index ed113ed6a..3f805f37c 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -66,10 +66,10 @@ namespace details { static std::optional Validate(const yaml_t& value); \ }; - DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") - DEFINE_SETTING_MAPPING(SessionMemoryMb, uint32_t, uint32_t, 2048, "session.memorySizeMb") - DEFINE_SETTING_MAPPING(SessionStorageSizeMb, uint32_t, uint32_t, 100000, "session.maxStorageSizeMb") - DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") + DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") + DEFINE_SETTING_MAPPING(SessionMemoryMb, std::string, uint32_t, "2GB", "session.memorySize") + DEFINE_SETTING_MAPPING(SessionStorageSizeMb, std::string, uint32_t, "100GB", "session.maxStorageSize") + DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") #undef DEFINE_SETTING_MAPPING // clang-format on diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index 6b01f5b29..bae399f30 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -137,8 +137,8 @@ class WSLCCLISettingsUnitTests dir / L"UserSettings.yaml", "session:\n" " cpuCount: 8\n" - " memorySizeMb: 4096\n" - " maxStorageSizeMb: 20000\n"); + " memorySize: 4GB\n" + " maxStorageSize: 20000MB\n"); UserSettingsTest s{dir}; @@ -208,7 +208,7 @@ class WSLCCLISettingsUnitTests VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } - // maxStorageSizeMb: 0 must be rejected; the default (100000) is used. + // maxStorageSize: 0 must be rejected; the default (100GB) is used. TEST_METHOD(Validation_StorageSizeMb_Zero_UsesDefaultAndWarns) { auto dir = UniqueTempDir(); @@ -216,7 +216,7 @@ class WSLCCLISettingsUnitTests UserSettingsTest s{dir}; - VERIFY_ARE_EQUAL(100000u, s.Get()); + VERIFY_ARE_EQUAL(102400u, s.Get()); VERIFY_IS_TRUE(s.GetWarnings().size() >= 1u); } From 03d9833eeb0dc768d0443033e6cec7bb32fb008f Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:26:02 -0700 Subject: [PATCH 24/25] fix --- src/windows/wslc/settings/UserSettings.h | 8 ++++---- test/windows/wslc/WSLCCLISettingsUnitTests.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index 3f805f37c..a76490302 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -66,10 +66,10 @@ namespace details { static std::optional Validate(const yaml_t& value); \ }; - DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") - DEFINE_SETTING_MAPPING(SessionMemoryMb, std::string, uint32_t, "2GB", "session.memorySize") - DEFINE_SETTING_MAPPING(SessionStorageSizeMb, std::string, uint32_t, "100GB", "session.maxStorageSize") - DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") + DEFINE_SETTING_MAPPING(SessionCpuCount, uint32_t, uint32_t, 4, "session.cpuCount") + DEFINE_SETTING_MAPPING(SessionMemoryMb, std::string, uint32_t, 2048, "session.memorySize") + DEFINE_SETTING_MAPPING(SessionStorageSizeMb, std::string, uint32_t, 102400, "session.maxStorageSize") + DEFINE_SETTING_MAPPING(SessionStoragePath, std::string, std::wstring, {}, "session.defaultStoragePath") #undef DEFINE_SETTING_MAPPING // clang-format on diff --git a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp index bae399f30..47abb1388 100644 --- a/test/windows/wslc/WSLCCLISettingsUnitTests.cpp +++ b/test/windows/wslc/WSLCCLISettingsUnitTests.cpp @@ -93,7 +93,7 @@ class WSLCCLISettingsUnitTests SettingsMap map; VERIFY_ARE_EQUAL(4u, map.GetOrDefault()); VERIFY_ARE_EQUAL(2048u, map.GetOrDefault()); - VERIFY_ARE_EQUAL(100000u, map.GetOrDefault()); + VERIFY_ARE_EQUAL(102400u, map.GetOrDefault()); VERIFY_ARE_EQUAL(std::wstring{}, map.GetOrDefault()); } @@ -120,7 +120,7 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_ARE_EQUAL(2048u, s.Get()); - VERIFY_ARE_EQUAL(100000u, s.Get()); + VERIFY_ARE_EQUAL(102400u, s.Get()); VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } @@ -163,7 +163,7 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_ARE_EQUAL(2048u, s.Get()); - VERIFY_ARE_EQUAL(100000u, s.Get()); + VERIFY_ARE_EQUAL(102400u, s.Get()); } // When the settings file fails to parse, the type is Default and a warning is emitted. @@ -196,11 +196,11 @@ class WSLCCLISettingsUnitTests VERIFY_IS_FALSE(s.GetWarnings().front().SettingPath.empty()); } - // memorySizeMb: 0 must be rejected; the default (2048) is used. + // memorySize: 0 must be rejected; the default (2048) is used. TEST_METHOD(Validation_MemoryMb_Zero_UsesDefaultAndWarns) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", "session:\n memorySizeMb: 0\n"); + WriteFile(dir / L"UserSettings.yaml", "session:\n memorySize: 0\n"); UserSettingsTest s{dir}; @@ -212,7 +212,7 @@ class WSLCCLISettingsUnitTests TEST_METHOD(Validation_StorageSizeMb_Zero_UsesDefaultAndWarns) { auto dir = UniqueTempDir(); - WriteFile(dir / L"UserSettings.yaml", "session:\n maxStorageSizeMb: 0\n"); + WriteFile(dir / L"UserSettings.yaml", "session:\n maxStorageSize: 0\n"); UserSettingsTest s{dir}; @@ -268,7 +268,7 @@ class WSLCCLISettingsUnitTests VERIFY_ARE_EQUAL(0u, s.GetWarnings().size()); VERIFY_ARE_EQUAL(4u, s.Get()); VERIFY_ARE_EQUAL(2048u, s.Get()); - VERIFY_ARE_EQUAL(100000u, s.Get()); + VERIFY_ARE_EQUAL(102400u, s.Get()); VERIFY_ARE_EQUAL(std::wstring{}, s.Get()); } From 0b8aa80b635d355ba501a3f4962b393005225a4a Mon Sep 17 00:00:00 2001 From: yao-msft <50888816+yao-msft@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:01:55 -0700 Subject: [PATCH 25/25] settings.yaml --- src/windows/wslc/settings/UserSettings.cpp | 2 +- src/windows/wslc/settings/UserSettings.h | 4 ++-- .../windows/wslc/WSLCCLISettingsUnitTests.cpp | 22 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/windows/wslc/settings/UserSettings.cpp b/src/windows/wslc/settings/UserSettings.cpp index c3befab23..6daed8c85 100644 --- a/src/windows/wslc/settings/UserSettings.cpp +++ b/src/windows/wslc/settings/UserSettings.cpp @@ -206,7 +206,7 @@ UserSettings::UserSettings() : UserSettings(SettingsDir()) UserSettings::UserSettings(const std::filesystem::path& settingsDir) { - m_settingsPath = settingsDir / L"UserSettings.yaml"; + m_settingsPath = settingsDir / L"settings.yaml"; auto root = TryLoadYaml(m_settingsPath, m_warnings); if (root.has_value()) diff --git a/src/windows/wslc/settings/UserSettings.h b/src/windows/wslc/settings/UserSettings.h index a76490302..00705619e 100644 --- a/src/windows/wslc/settings/UserSettings.h +++ b/src/windows/wslc/settings/UserSettings.h @@ -95,7 +95,7 @@ struct SettingsMap : wsl::windows::wslc::EnumBasedVariantMap