diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index de258d29d..1c7be4b78 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2565,6 +2565,14 @@ On first run, creates the file with all settings commented out at their defaults Remove the container after it stops + + Signal to stop the container (default: {}) + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + + Size of /dev/shm (e.g. 64m, 1g) + {Locked="/dev/shm"} + Session ID diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp index 3d75c7d73..fc007421d 100644 --- a/src/windows/common/WSLCContainerLauncher.cpp +++ b/src/windows/common/WSLCContainerLauncher.cpp @@ -129,6 +129,11 @@ void WSLCContainerLauncher::SetDefaultStopSignal(WSLCSignal Signal) m_stopSignal = Signal; } +void WSLCContainerLauncher::SetShmSize(ULONGLONG ShmSize) +{ + m_shmSize = ShmSize; +} + void WSLCContainerLauncher::SetEntrypoint(std::vector&& entrypoint) { m_entrypoint = std::move(entrypoint); @@ -254,6 +259,7 @@ std::pair> WSLCContainerLauncher::C options.PortsCount = static_cast(m_ports.size()); options.StopSignal = m_stopSignal; options.Flags = m_containerFlags; + options.ShmSize = m_shmSize; if (!entrypointStorage.empty()) { diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h index 4643bc875..e33b72c45 100644 --- a/src/windows/common/WSLCContainerLauncher.h +++ b/src/windows/common/WSLCContainerLauncher.h @@ -72,6 +72,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher void SetName(std::string&& Name); void SetEntrypoint(std::vector&& entrypoint); void SetDefaultStopSignal(WSLCSignal Signal); + void SetShmSize(ULONGLONG ShmSize); void SetContainerFlags(WSLCContainerFlags Flags); void SetHostname(std::string&& Hostname); void SetDomainname(std::string&& Domainame); @@ -95,6 +96,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher WSLCContainerNetworkType m_containerNetworkType; std::vector m_entrypoint; WSLCSignal m_stopSignal = WSLCSignalNone; + ULONGLONG m_shmSize = 0; WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone; std::string m_hostname; std::string m_domainname; diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index a7bfe4e9b..569eb6a22 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -57,11 +57,11 @@ _(Follow, "follow", L"f", Kind::Flag, L _(Format, "format", NO_ALIAS, Kind::Value, Localization::WSLCCLI_FormatArgDescription()) \ _(ForwardArgs, "arguments", NO_ALIAS, Kind::Forward, Localization::WSLCCLI_ForwardArgsDescription()) \ /*_(GroupId, "groupid", NO_ALIAS, Kind::Value, Localization::WSLCCLI_GroupIdArgDescription())*/ \ -_(Help, "help", WSLC_CLI_HELP_ARG, Kind::Flag, Localization::WSLCCLI_HelpArgDescription()) \ +_(Help, "help", WSLC_CLI_HELP_ARG, Kind::Flag, Localization::WSLCCLI_HelpArgDescription())\ _(Hostname, "hostname", L"h", Kind::Value, Localization::WSLCCLI_HostnameArgDescription()) \ _(ImageForce, "force", L"f", Kind::Flag, Localization::WSLCCLI_ImageForceArgDescription()) \ _(ImageId, "image", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_ImageIdArgDescription()) \ -_(Input, "input", L"i", Kind::Value, Localization::WSLCCLI_InputArgDescription()) \ +_(Input, "input", L"i", Kind::Value, Localization::WSLCCLI_InputArgDescription())\ _(Interactive, "interactive", L"i", Kind::Flag, Localization::WSLCCLI_InteractiveArgDescription()) \ _(Label, "label", NO_ALIAS, Kind::Value, L"Volume metadata setting") \ _(Name, "name", NO_ALIAS, Kind::Value, Localization::WSLCCLI_NameArgDescription()) \ @@ -85,9 +85,11 @@ _(Remove, "rm", NO_ALIAS, Kind::Flag, L _(Server, "server", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_LoginServerArgDescription()) \ _(Session, "session", NO_ALIAS, Kind::Value, Localization::WSLCCLI_SessionIdArgDescription()) \ _(SessionId, "session-id", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SessionIdPositionalArgDescription()) \ -_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \ +_(ShmSize, "shm-size", NO_ALIAS, Kind::Value, Localization::WSLCCLI_ShmSizeArgDescription()) \ _(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription(L"SIGKILL")) \ _(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \ +_(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L"Path to the session storage directory") \ +_(StopSignal, "stop-signal", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopSignalArgDescription(L"SIGTERM")) \ _(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \ _(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \ _(Time, "time", L"t", Kind::Value, Localization::WSLCCLI_TimeArgDescription()) \ diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index b96dadc4e..7666d4146 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -40,6 +40,14 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateWSLCSignalFromString(execArgs.GetAll(), m_name); break; + case ArgType::StopSignal: + validation::ValidateWSLCSignalFromString(execArgs.GetAll(), m_name); + break; + + case ArgType::ShmSize: + validation::ValidateMemorySize(execArgs.GetAll(), m_name); + break; + case ArgType::Time: validation::ValidateIntegerFromString(execArgs.GetAll(), m_name); break; @@ -191,4 +199,24 @@ InspectType GetInspectTypeFromString(const std::wstring& input, const std::wstri } } +void ValidateMemorySize(const std::vector& values, const std::wstring& argName) +{ + for (const auto& value : values) + { + std::ignore = GetMemorySizeFromString(value, argName); + } +} + +ULONGLONG GetMemorySizeFromString(const std::wstring& input, const std::wstring& argName) +{ + auto narrowInput = wsl::windows::common::string::WideToMultiByte(input); + auto parsed = wsl::shared::string::ParseMemorySize(narrowInput.c_str()); + if (!parsed.has_value()) + { + throw ArgumentException(std::format(L"Invalid {} argument value: '{}'. Expected a memory size (e.g. 64m, 256m, 1g)", argName, input)); + } + + return parsed.value(); +} + } // namespace wsl::windows::wslc::validation diff --git a/src/windows/wslc/arguments/ArgumentValidation.h b/src/windows/wslc/arguments/ArgumentValidation.h index 879582558..815336382 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.h +++ b/src/windows/wslc/arguments/ArgumentValidation.h @@ -58,6 +58,9 @@ T GetIntegerFromString(const std::wstring& value, const std::wstring& argName = void ValidateWSLCSignalFromString(const std::vector& values, const std::wstring& argName); WSLCSignal GetWSLCSignalFromString(const std::wstring& input, const std::wstring& argName = {}); +void ValidateMemorySize(const std::vector& values, const std::wstring& argName); +ULONGLONG GetMemorySizeFromString(const std::wstring& input, const std::wstring& argName = {}); + void ValidateFormatTypeFromString(const std::vector& values, const std::wstring& argName); FormatType GetFormatTypeFromString(const std::wstring& input, const std::wstring& argName = {}); diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp index 2135c0197..f6e48130f 100644 --- a/src/windows/wslc/commands/ContainerCreateCommand.cpp +++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp @@ -51,6 +51,8 @@ std::vector ContainerCreateCommand::GetArguments() const Argument::Create(ArgType::Remove), // Argument::Create(ArgType::Scheme), Argument::Create(ArgType::Session), + Argument::Create(ArgType::ShmSize), + Argument::Create(ArgType::StopSignal), Argument::Create(ArgType::TMPFS, false, NO_LIMIT), Argument::Create(ArgType::TTY), Argument::Create(ArgType::User), diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp index 7899d3f79..35cda01f4 100644 --- a/src/windows/wslc/commands/ContainerRunCommand.cpp +++ b/src/windows/wslc/commands/ContainerRunCommand.cpp @@ -52,6 +52,8 @@ std::vector ContainerRunCommand::GetArguments() const Argument::Create(ArgType::Remove), // Argument::Create(ArgType::Scheme), Argument::Create(ArgType::Session), + Argument::Create(ArgType::ShmSize), + Argument::Create(ArgType::StopSignal), Argument::Create(ArgType::TMPFS, false, NO_LIMIT), Argument::Create(ArgType::TTY), Argument::Create(ArgType::User), diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index 5997a2736..edad5c76f 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -37,6 +37,8 @@ struct ContainerOptions bool Remove = false; bool TTY = false; bool PublishAll = false; + WSLCSignal StopSignal = WSLCSignalNone; + ULONGLONG ShmSize = 0; std::vector Ports; std::vector Volumes; std::string WorkingDirectory; diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 90af89e87..8e1383893 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -95,6 +95,16 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(Session& sessio containerLauncher.SetContainerFlags(containerFlags); + if (options.StopSignal != WSLCSignalNone) + { + containerLauncher.SetDefaultStopSignal(options.StopSignal); + } + + if (options.ShmSize > 0) + { + containerLauncher.SetShmSize(options.ShmSize); + } + if (!options.Entrypoint.empty()) { auto entrypoints = options.Entrypoint; diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index 804f54282..52b713981 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -247,6 +247,16 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) options.Remove = true; } + if (context.Args.Contains(ArgType::StopSignal)) + { + options.StopSignal = validation::GetWSLCSignalFromString(context.Args.Get()); + } + + if (context.Args.Contains(ArgType::ShmSize)) + { + options.ShmSize = validation::GetMemorySizeFromString(context.Args.Get()); + } + if (context.Args.Contains(ArgType::Command)) { options.Arguments.emplace_back(WideToMultiByte(context.Args.Get())); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index aa80ddd8e..456bf6527 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -651,6 +651,26 @@ class WSLCE2EContainerCreateTests VERIFY_IS_TRUE(result.Stdout->find(L"options ndots:5 timeout:3") != std::wstring::npos); } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopSignal) + { + auto result = RunWslc(std::format( + L"container create --stop-signal SIGUSR1 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + std::wstring containerId = result.GetStdoutOneLine(); + VerifyContainerIsListed(containerId, L"created"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_ShmSize) + { + auto result = RunWslc( + std::format(L"container create --shm-size 128m --name {} {} df -h /dev/shm", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + result = RunWslc(std::format(L"container start -a {}", WslcContainerName)); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"128M") != std::wstring::npos); + } + private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; @@ -724,6 +744,8 @@ class WSLCE2EContainerCreateTests << L" -P,--publish-all Publish all exposed ports to random host ports\r\n" << L" --rm Remove the container after it stops\r\n" << L" --session Specify the session to use\r\n" + << L" --shm-size Size of /dev/shm (e.g. 64m, 1g)\r\n" + << L" --stop-signal Signal to stop the container (default: SIGTERM)\r\n" << L" --tmpfs Mount tmpfs to the container at the given path\r\n" << L" -t,--tty Open a TTY with the container process.\r\n" << L" -u,--user User ID for the process (name|uid|uid:gid)\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 506c2e81b..6886b9ea4 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -641,6 +641,22 @@ class WSLCE2EContainerRunTests result.Verify({.Stderr = std::format(L"Volume not found: '{}'\r\nError code: WSLC_E_VOLUME_NOT_FOUND\r\n", WslcVolumeName), .ExitCode = 1}); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopSignal) + { + auto result = RunWslc(std::format( + L"container run -d --stop-signal SIGUSR1 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + auto containerId = result.GetStdoutOneLine(); + VerifyContainerIsListed(containerId, L"running"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_ShmSize) + { + auto result = RunWslc(std::format(L"container run --rm --shm-size 128m {} df -h /dev/shm", DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + VERIFY_IS_TRUE(result.Stdout->find(L"128M") != std::wstring::npos); + } + private: // Test container name const std::wstring WslcContainerName = L"wslc-test-container"; @@ -720,6 +736,8 @@ class WSLCE2EContainerRunTests << L" -P,--publish-all Publish all exposed ports to random host ports\r\n" << L" --rm Remove the container after it stops\r\n" << L" --session Specify the session to use\r\n" + << L" --shm-size Size of /dev/shm (e.g. 64m, 1g)\r\n" + << L" --stop-signal Signal to stop the container (default: SIGTERM)\r\n" << L" --tmpfs Mount tmpfs to the container at the given path\r\n" << L" -t,--tty Open a TTY with the container process.\r\n" << L" -u,--user User ID for the process (name|uid|uid:gid)\r\n"