From be5728a53e17fa4a5c587b5b1981597ef1f2e5cd Mon Sep 17 00:00:00 2001 From: Blue Date: Thu, 25 Jun 2026 12:02:58 -0700 Subject: [PATCH 1/9] Save state --- localization/strings/en-US/Resources.resw | 7 + src/windows/common/APICompat.cpp | 1 + src/windows/common/WSLCContainerLauncher.cpp | 6 + src/windows/common/WSLCContainerLauncher.h | 4 + src/windows/inc/docker_schema.h | 7 +- src/windows/inc/wslc_schema.h | 3 +- src/windows/service/inc/wslc.idl | 1500 +++++++++-------- .../wslc/arguments/ArgumentDefinitions.h | 1 + .../wslc/arguments/ArgumentValidation.cpp | 7 + .../wslc/commands/ContainerCreateCommand.cpp | 1 + .../wslc/commands/ContainerRunCommand.cpp | 1 + src/windows/wslc/services/ContainerModel.h | 5 +- .../wslc/services/ContainerService.cpp | 5 + src/windows/wslc/tasks/ContainerTasks.cpp | 5 + src/windows/wslcsession/DockerHTTPClient.cpp | 2 +- src/windows/wslcsession/DockerHTTPClient.h | 2 +- src/windows/wslcsession/WSLCContainer.cpp | 26 +- .../wslc/e2e/WSLCE2EContainerCreateTests.cpp | 70 + .../wslc/e2e/WSLCE2EContainerRunTests.cpp | 71 + .../wslc/e2e/WSLCE2EContainerStopTests.cpp | 51 +- 20 files changed, 1014 insertions(+), 761 deletions(-) diff --git a/localization/strings/en-US/Resources.resw b/localization/strings/en-US/Resources.resw index 95932960e3..90a5249940 100644 --- a/localization/strings/en-US/Resources.resw +++ b/localization/strings/en-US/Resources.resw @@ -2355,6 +2355,10 @@ For privacy information about this product please visit https://aka.ms/privacy.< Unsupported network mode: '{}' {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + + Invalid stop timeout value: {} + {FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated + Additional networks are not allowed when the primary network mode is 'host' or 'none'. {Locked="host"}{Locked="none"}Command line arguments, file names and string inserts should not be translated @@ -2916,6 +2920,9 @@ On first run, creates the file with all settings commented out at their defaults Signal to stop the container + + Timeout (in seconds) to stop the container before killing it (-1 for no timeout) + Size of /dev/shm (e.g. 64M, 1G) {Locked="/dev/shm"}Command line arguments should not be translated diff --git a/src/windows/common/APICompat.cpp b/src/windows/common/APICompat.cpp index 8263420465..48d8433661 100644 --- a/src/windows/common/APICompat.cpp +++ b/src/windows/common/APICompat.cpp @@ -294,6 +294,7 @@ ContainerOptionsConversion::ContainerOptionsConversion(const WSLCCompatContainer m_value.Flags = Options.Flags; m_value.StopSignal = Options.StopSignal; + m_value.StopTimeout = WSLC_STOP_TIMEOUT_DEFAULT; m_value.HostName = Options.HostName; m_value.DomainName = Options.DomainName; diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp index e022d79767..dbaf4ba5af 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::SetStopTimeout(LONG Timeout) +{ + m_stopTimeout = Timeout; +} + void WSLCContainerLauncher::SetShmSize(int64_t ShmSize) { m_shmSize = ShmSize; @@ -295,6 +300,7 @@ std::pair> WSLCContainerLauncher::C options.Ports = m_ports.data(); options.PortsCount = static_cast(m_ports.size()); options.StopSignal = m_stopSignal; + options.StopTimeout = m_stopTimeout; options.Flags = m_containerFlags; options.ShmSize = m_shmSize; diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h index fa01c59a25..2842f2edc3 100644 --- a/src/windows/common/WSLCContainerLauncher.h +++ b/src/windows/common/WSLCContainerLauncher.h @@ -75,6 +75,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher void SetName(std::string&& Name); void SetEntrypoint(std::vector&& entrypoint); void SetDefaultStopSignal(WSLCSignal Signal); + void SetStopTimeout(LONG Timeout); void SetShmSize(int64_t ShmSize); void SetContainerFlags(WSLCContainerFlags Flags); void SetHostname(std::string&& Hostname); @@ -103,6 +104,9 @@ class WSLCContainerLauncher : private WSLCProcessLauncher std::string m_networkMode; std::vector m_entrypoint; WSLCSignal m_stopSignal = WSLCSignalNone; + // WSLC_STOP_TIMEOUT_DEFAULT means the stop timeout was not specified, so the + // container runtime default is used. + LONG m_stopTimeout = WSLC_STOP_TIMEOUT_DEFAULT; int64_t m_shmSize = 0; WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone; std::string m_hostname; diff --git a/src/windows/inc/docker_schema.h b/src/windows/inc/docker_schema.h index a7e998bc6d..fcc796a7ae 100644 --- a/src/windows/inc/docker_schema.h +++ b/src/windows/inc/docker_schema.h @@ -297,6 +297,7 @@ struct CreateContainer std::string Hostname; std::string Domainname; std::optional StopSignal; + std::optional StopTimeout; std::optional WorkingDir; std::optional> Cmd; std::optional> Entrypoint; @@ -307,7 +308,7 @@ struct CreateContainer NetworkingConfig NetworkingConfig; NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE( - CreateContainer, Image, Cmd, Tty, OpenStdin, StdinOnce, Entrypoint, Env, ExposedPorts, HostConfig, StopSignal, WorkingDir, User, Hostname, Domainname, Labels, NetworkingConfig); + CreateContainer, Image, Cmd, Tty, OpenStdin, StdinOnce, Entrypoint, Env, ExposedPorts, HostConfig, StopSignal, StopTimeout, WorkingDir, User, Hostname, Domainname, Labels, NetworkingConfig); }; struct ContainerInspectState @@ -329,8 +330,10 @@ struct ContainerConfig std::optional> Env; std::optional> Cmd; std::optional> Entrypoint; + std::optional StopSignal; + std::optional StopTimeout; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Image, User, WorkingDir, Env, Cmd, Entrypoint); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Image, User, WorkingDir, Env, Cmd, Entrypoint, StopSignal, StopTimeout); }; struct InspectMount diff --git a/src/windows/inc/wslc_schema.h b/src/windows/inc/wslc_schema.h index db4354ba3f..a936a33557 100644 --- a/src/windows/inc/wslc_schema.h +++ b/src/windows/inc/wslc_schema.h @@ -76,8 +76,9 @@ struct ContainerConfig std::optional> Entrypoint; std::string User; std::string WorkingDir; + std::optional StopTimeout; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Env, Cmd, Entrypoint, User, WorkingDir); + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Env, Cmd, Entrypoint, User, WorkingDir, StopTimeout); }; struct InspectEndpointSettings diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 147b44ce4d..0560810232 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -1,746 +1,754 @@ -/*++ - -Copyright (c) Microsoft Corporation. All rights reserved. - -Module Name: - - wslc.idl - -Abstract: - - This file contains the WSLC-related COM object definitions. - // N.B. ABI breaking changes in this file are OK, since both client & server always ship together. - // The WSLC SDK must not use this file, and instead use WSLCCompat.idl - ---*/ - -import "unknwn.idl"; -import "wtypes.idl"; - -// Enums and flags shared with the SDK-facing API (WSLCCompat.idl). -import "WSLCShared.idl"; - -cpp_quote("#ifdef __cplusplus") -cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLCSessionManager;") -cpp_quote("class DECLSPEC_UUID(\"9fcd2067-9fc6-4efa-9eb0-698169ebf7d3\") WSLCSessionFactory;") -cpp_quote("#endif") - -#define WSLC_MAX_CONTAINER_NAME_LENGTH 255 -#define WSLC_MAX_IMAGE_NAME_LENGTH 255 -#define WSLC_MAX_VOLUME_NAME_LENGTH 255 -#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255 -#define WSLC_MAX_NETWORK_NAME_LENGTH 255 -#define WSLC_CONTAINER_ID_LENGTH 64 -#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45 -#define WSLC_EPHEMERAL_PORT 0 -#define WSLC_MAX_SAVE_IMAGES_COUNT 256 - -cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255") -cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255") -cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255") -cpp_quote("#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255") -cpp_quote("#define WSLC_MAX_NETWORK_NAME_LENGTH 255") -cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64") -cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45") -cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256") -cpp_quote("#define WSLC_EPHEMERAL_PORT 0") - -typedef -struct _WSLCVersion { - ULONG Major; - ULONG Minor; - ULONG Revision; -} WSLCVersion; - -[ - uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801), - pointer_default(unique), - object -] -interface ICrashDumpCallback : IUnknown -{ - HRESULT OnCrashDump( - [in, string] LPCWSTR DumpPath, - [in, unique, string] LPCSTR ProcessName, - [in] ULONG Pid, - [in] ULONG Signal, - [in] ULONGLONG Timestamp); -}; - -[ - uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1), - pointer_default(unique), - object -] -interface IProgressCallback : IUnknown -{ - HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total); -}; - -[ - uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3), - pointer_default(unique), - object -] -interface IWarningCallback : IUnknown -{ - HRESULT OnWarning([in, string] LPCWSTR Message); -}; - -[ - uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2), - pointer_default(unique), - object -] -interface IWSLCPluginNotifier : IUnknown -{ - // 'InspectJson' follows the wslc_schema::InspectContainer format. - // Returning failure prevents the container creation. - HRESULT OnContainerStarted([in] LPCSTR InspectJson); - - // Called when a container is about to stop. 'ContainerId' is the container identifier. Errors are logged but ignored. - HRESULT OnContainerStopping([in] LPCSTR ContainerId); - - // 'InspectJson' follows the wslc_schema::InspectImage format. Errors are logged but ignored. - HRESULT OnImageCreated([in] LPCSTR InspectJson); - - // Called when an image is deleted. 'ImageId' is the image identifier. Errors are logged but ignored. - HRESULT OnImageDeleted([in] LPCSTR ImageId); -}; - -typedef struct _WSLCImageInformation -{ - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; - char Hash[256]; - char Digest[256]; - LONGLONG Size; // Matches Docker's int64 image size - LONGLONG Created; // Unix timestamp - char ParentId[256]; -} WSLCImageInformation; - -typedef struct _KeyValuePairInformation -{ - [string] LPSTR Key; - [string] LPSTR Value; -} KeyValuePairInformation; - -typedef struct _KeyValuePair -{ - [string] LPCSTR Key; - [string] LPCSTR Value; -} KeyValuePair; - -typedef KeyValuePair WSLCLabel; -typedef KeyValuePair WSLCDriverOption; -typedef KeyValuePair WSLCFilter; - -typedef KeyValuePairInformation WSLCLabelInformation; -typedef KeyValuePairInformation WSLCDriverOptionInformation; - -typedef struct _WSLCListImagesOptions -{ - DWORD Flags; // WSLCListImagesFlags (can combine with bitwise OR) - [unique, size_is(FiltersCount)] const WSLCFilter* Filters; - ULONG FiltersCount; -} WSLCListImagesOptions; - -typedef struct _WSLCStringArray -{ - [unique, size_is(Count)] LPCSTR const* Values; - ULONG Count; -} WSLCStringArray; - -typedef struct _WSLCProcessOptions -{ - [unique] LPCSTR CurrentDirectory; - [unique] LPCSTR User; - WSLCStringArray CommandLine; - WSLCStringArray Environment; - WSLCProcessFlags Flags; -} WSLCProcessOptions; - -typedef struct _WSLCProcessStartOptions -{ - ULONG TtyRows; // Only needed when tty fd's are passed. - ULONG TtyColumns; - [unique, string] LPCSTR DetachKeys; -} WSLCProcessStartOptions; - -typedef struct _WSLCNamedVolume -{ - LPCSTR Name; - LPCSTR ContainerPath; - BOOL ReadOnly; -} WSLCNamedVolume; - -typedef struct _WSLCVolume -{ - LPCWSTR HostPath; - LPCSTR ContainerPath; - BOOL ReadOnly; -} WSLCVolume; - -typedef struct _WSLCPortMapping -{ - USHORT HostPort; - USHORT ContainerPort; - int Family; - int Protocol; - char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1]; -} WSLCPortMapping; - -typedef struct _WSLCTmpfsMount -{ - LPCSTR Destination; - [unique] LPCSTR Options; -} WSLCTmpfsMount; - -typedef struct _WSLCUlimit -{ - [string] LPCSTR Name; - LONGLONG Soft; - LONGLONG Hard; -} WSLCUlimit; - -typedef struct _WSLCNetworkConnection -{ - [string] LPCSTR NetworkName; - [unique, size_is(SettingsCount)] const KeyValuePair* Settings; - ULONG SettingsCount; -} WSLCNetworkConnection; - -// Options for IWSLCContainer::ConnectToNetwork. -typedef struct _WSLCNetworkConnectionOptions -{ - [unique] LPCSTR NetworkName; - [unique] LPCSTR ContainerIpAddress; // Reserved for future --ip support; must be NULL today. -} WSLCNetworkConnectionOptions; - -typedef struct _WSLCContainerNetwork -{ - [unique, string] LPCSTR NetworkMode; - - [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks; - ULONG NetworksCount; - - // Settings for the primary endpoint (the network identified by NetworkMode). - // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries). - [unique, size_is(SettingsCount)] const KeyValuePair* Settings; - ULONG SettingsCount; -} WSLCContainerNetwork; - -typedef struct _WSLCContainerOptions -{ - LPCSTR Image; - [unique] LPCSTR Name; - WSLCStringArray Entrypoint; - WSLCProcessOptions InitProcessOptions; - [unique, size_is(VolumesCount)] WSLCVolume* Volumes; - ULONG VolumesCount; - [unique, size_is(PortsCount)] WSLCPortMapping* Ports; - ULONG PortsCount; - [unique, size_is(LabelsCount)] const WSLCLabel* Labels; - ULONG LabelsCount; - WSLCContainerFlags Flags; - WSLCSignal StopSignal; - // TODO: List specific GPU devices. - [unique] LPCSTR HostName; - [unique] LPCSTR DomainName; - - WSLCStringArray DnsServers; - WSLCStringArray DnsSearchDomains; - WSLCStringArray DnsOptions; - - LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus - WSLCContainerNetwork ContainerNetwork; - [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs; - ULONG TmpfsCount; - - [unique, size_is(NamedVolumesCount)] WSLCNamedVolume* NamedVolumes; - ULONG NamedVolumesCount; - - LONGLONG MemoryBytes; - LONGLONG NanoCpus; - [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits; - ULONG UlimitsCount; -} WSLCContainerOptions; - -typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ; - -typedef struct _WSLCContainerEntry -{ - char Name[WSLC_MAX_CONTAINER_NAME_LENGTH + 1]; - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; - WSLCContainerId Id; - ULONGLONG StateChangedAt; - ULONGLONG CreatedAt; - WSLCContainerState State; -} WSLCContainerEntry; - -typedef struct _WSLCContainerPortMapping -{ - WSLCContainerId Id; - WSLCPortMapping PortMapping; -} WSLCContainerPortMapping; - -typedef [system_handle(sh_file)] HANDLE FILE_HANDLE; -typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE; -typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE; - -typedef struct _WSLCHandle -{ - WSLCHandleType Type; - - [switch_type(WSLCHandleType), switch_is(Type)] - union - { - [case(WSLCHandleTypeFile)] - FILE_HANDLE File; - [case(WSLCHandleTypePipe)] - PIPE_HANDLE Pipe; - [case(WSLCHandleTypeSocket)] - SOCKET_HANDLE Socket; - [default]; - } Handle; -} WSLCHandle; - -[ - uuid(1AD163CD-393D-4B33-83A2-8A3F3F23E608), - pointer_default(unique), - object -] -interface IWSLCProcess : IUnknown -{ - HRESULT Signal([in] int Signal); - HRESULT GetExitEvent([out, system_handle(sh_event)] HANDLE* EventHandle); - HRESULT GetStdHandle([in] WSLCFD Fd, [out] WSLCHandle* Handle); - HRESULT GetFlags([out] WSLCProcessFlags* Flags); - HRESULT GetPid([out] int* Pid); - HRESULT GetState([out] WSLCProcessState* State, [out] int* Code); - HRESULT ResizeTty([in] ULONG Rows, [in] ULONG Columns); - - // Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API. -} - -// -// Values discovered from the guest kernel after the VM has booted, forwarded -// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new -// fields here instead of new IDL methods so the interface does not need a new -// IID for each kernel-published value. -// -typedef struct _WSLCGuestCapabilities -{ - // (base, size) of the hv_pci swiotlb pool the kernel reserved and - // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}. - // Both zero means the running kernel does not support hv_pci swiotlb. - UINT64 HvPciSwiotlbBase; - UINT64 HvPciSwiotlbSize; -} WSLCGuestCapabilities; - -// -// IWSLCVirtualMachine - Interface representing a single VM instance. -// Operations are scoped to this VM. The VM ID is stored internally, -// so only the holder of this interface can operate on the VM. -// -[ - uuid(B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A), - pointer_default(unique), - object -] -interface IWSLCVirtualMachine : IUnknown -{ - // Gets the VM ID. - HRESULT GetId([out, retval] GUID* VmId); - - // Accepts a connect from mini_init in the VM. - HRESULT AcceptConnection([out, system_handle(sh_socket)] HANDLE* Socket); - - // Configures networking engine with sockets from the user process. - // GnsSocket is required; DnsSocket is optional (NULL if DNS tunneling is disabled). - // The service duplicates the socket handles. - HRESULT ConfigureNetworking( - [in, system_handle(sh_socket)] HANDLE GnsSocket, - [in, system_handle(sh_socket), unique] HANDLE* DnsSocket); - - // Attaches a VHD or VHDX disk to the VM. - // GrantVmAccess is called by the service before attaching. - // Returns the SCSI LUN assigned to the disk. - HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out, retval] ULONG* Lun); - - // Detaches a previously attached disk from the VM. - HRESULT DetachDisk([in] ULONG Lun); - - // Adds a filesystem share (Plan9 or VirtioFS) accessible to the VM. - // Returns an instance GUID that can be used to remove the share. - HRESULT AddShare([in] LPCWSTR WindowsPath, [in] BOOL ReadOnly, [out, retval] GUID* ShareId); - - // Removes a previously added filesystem share. - HRESULT RemoveShare([in] REFGUID ShareId); - - // Configures the per-VM state discovered from the guest kernel after boot - // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and - // ConfigureNetworking forward these values to wsldevicehost via the - // swiotlb device-options token. A capabilities struct whose fields are - // all zero means the guest kernel does not support the feature; the - // token is then omitted. - HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities); - - // Returns an event that is signaled when the VM exits (graceful or forced). - HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); - - HRESULT MapVirtioNetPort( - [in] USHORT HostPort, - [in] USHORT GuestPort, - [in] int Protocol, - [in] LPCSTR ListenAddress, - [out, retval] USHORT* AllocatedHostPort); - - // Unmaps a port previously mapped via MapVirtioNetPort. - HRESULT UnmapVirtioNetPort( - [in] USHORT HostPort, - [in] USHORT GuestPort, - [in] int Protocol, - [in] LPCSTR ListenAddress); - - // Returns the cached termination reason and details. These are only available after the - // termination event has been signaled; before that the call fails. - HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); -} - -// -// IWSLCVirtualMachineFactory - Creates VMs on demand for a session. -// -// Held by the per-user session process and implemented by the SYSTEM service. -// This lets the session create a fresh VM at any time (e.g. to recreate a VM that -// was idle-terminated when it had no running containers), instead of the service -// eagerly creating a single VM up front. Each successful call returns a new VM whose -// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down. -// -[ - uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B), - pointer_default(unique), - object -] -interface IWSLCVirtualMachineFactory : IUnknown -{ - // Creates a new VM using the settings captured at session creation time. - HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm); -} - -// Settings for IWSLCSessionManager::CreateSession - full session configuration -typedef struct _WSLCSessionSettings { - LPCWSTR DisplayName; - LPCWSTR StoragePath; - ULONGLONG MaximumStorageSizeMb; - ULONG CpuCount; - ULONG MemoryMb; - ULONG BootTimeoutMs; - WSLCNetworkingMode NetworkingMode; - WSLCFeatureFlags FeatureFlags; - WSLCHandle DmesgOutput; - WSLCSessionStorageFlags StorageFlags; - - // Below options are used for debugging purposes only. - [unique] LPCWSTR RootVhdOverride; - [unique] LPCSTR RootVhdTypeOverride; -} WSLCSessionSettings; - - -[ - uuid(7577FE8D-DE85-471E-B870-11669986F332), - pointer_default(unique), - object -] -interface IWSLCContainer : IUnknown -{ - HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr); - HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds); - HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] const WSLCProcessStartOptions* StartOptions, [in, unique] IWarningCallback* WarningCallback); - HRESULT Delete([in] WSLCDeleteFlags Flags); - HRESULT Export([in] WSLCHandle TarHandle); - HRESULT GetState([out] WSLCContainerState* State); - HRESULT GetInitProcess([out] IWSLCProcess** Process); - HRESULT Exec([in, ref] const WSLCProcessOptions* Options, [in, unique] const WSLCProcessStartOptions* StartOptions, [out] IWSLCProcess** Process); - HRESULT Inspect([out] LPSTR* Output); - HRESULT Logs([in] WSLCLogsFlags Flags, [out] WSLCHandle* Stdout, [out] WSLCHandle* Stderr, [in] ULONGLONG Since, [in] ULONGLONG Until, [in] ULONGLONG Tail); - HRESULT GetId([out, string] WSLCContainerId Id); - HRESULT GetName([out, string] LPSTR* Name); - HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count); - HRESULT Kill([in] WSLCSignal Signal); - HRESULT Stats([out] LPSTR* Output); - HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options); - HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName); -} - -typedef struct _WSLCDeletedImageInformation -{ - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; - WSLCDeletedImageType Type; -} WSLCDeletedImageInformation; - -typedef struct _WSLCDeleteImageOptions -{ - LPCSTR Image; // Image can be ID or Repo:Tag. - DWORD Flags; // WSLCDeleteImageFlags - // TODO: Platforms: a json array of OCI platform strings. -} WSLCDeleteImageOptions; - -typedef struct _WSLCBuildImageOptions -{ - LPCWSTR ContextPath; - WSLCHandle DockerfileHandle; - WSLCStringArray Tags; - WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker. - LPCSTR Target; // Target build stage name passed as --target to docker. - WSLCBuildImageFlags Flags; // WSLCBuildImageFlags - WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker. -} WSLCBuildImageOptions; - -typedef struct _WSLCTagImageOptions -{ - LPCSTR Image; // Source image name or ID. - LPCSTR Repo; // Target repository name. - LPCSTR Tag; // Target tag name. -} WSLCTagImageOptions; - -typedef struct _WSLCVolumeOptions -{ - [unique] LPCSTR Name; - [unique] LPCSTR Driver; - [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; - ULONG DriverOptsCount; - [unique, size_is(LabelsCount)] const WSLCLabel* Labels; - ULONG LabelsCount; -} WSLCVolumeOptions; - -typedef char WSLCVolumeName[WSLC_MAX_VOLUME_NAME_LENGTH + 1]; - -typedef struct _WSLCVolumeInformation -{ - WSLCVolumeName Name; - char Driver[WSLC_MAX_VOLUME_DRIVER_LENGTH + 1]; -} WSLCVolumeInformation; - -typedef struct _WSLCNetworkOptions -{ - LPCSTR Name; - [unique] LPCSTR Driver; - [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; - ULONG DriverOptsCount; - [unique, size_is(LabelsCount)] const WSLCLabel* Labels; - ULONG LabelsCount; -} WSLCNetworkOptions; - -typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; - -typedef struct _WSLCNetworkInformation -{ - char Name[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; - char Id[WSLC_CONTAINER_ID_LENGTH + 1]; - char Driver[64]; -} WSLCNetworkInformation; - -typedef struct _WSLCPruneContainersResults -{ - [unique, size_is(ContainersCount)] WSLCContainerId* Containers; - ULONG ContainersCount; - ULONGLONG SpaceReclaimed; -} WSLCPruneContainersResults; - -typedef struct _WSLCListContainersOptions -{ - DWORD Flags; // WSLCListContainersFlags - LONG Limit; - - [unique, size_is(FiltersCount)] const WSLCFilter* Filters; - ULONG FiltersCount; -} WSLCListContainersOptions; - -// Settings for IWSLCSession::Initialize - passed from service to per-user process -typedef struct _WSLCSessionInitSettings -{ - ULONG SessionId; - [unique] LPCWSTR CreatorProcessName; - LPCWSTR DisplayName; - LPCWSTR StoragePath; - WSLCSessionStorageFlags StorageFlags; - ULONGLONG MaximumStorageSizeMb; - ULONG SwapSizeMb; - ULONG BootTimeoutMs; - WSLCNetworkingMode NetworkingMode; - WSLCFeatureFlags FeatureFlags; - [unique] LPCSTR RootVhdTypeOverride; -} WSLCSessionInitSettings; - -[ - uuid(EF0661E4-6364-40EA-B433-E2FDF11F3519), - pointer_default(unique), - object -] -interface IWSLCSession : IUnknown -{ - HRESULT GetId([out] ULONG* Id); - HRESULT GetDisplayName([out] LPWSTR* DisplayName); - HRESULT GetState([out] WSLCSessionState* State); - - // Returns a one-off event that is signaled when the session terminates, whether due to an - // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the - // caller and remains valid (and observes the signaled state) even after the session is released. - HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); - - // Returns the cached termination reason and details. These are only available after the - // termination event has been signaled; before that the call fails. - HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); - - // Image management. - HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); - HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); - HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback); - HRESULT ImportImage([in] WSLCHandle ImageHandle, [in, unique] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback, [out] LPSTR* ImageId); - HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); - HRESULT SaveImages([in] WSLCHandle OutputHandle, [in] const WSLCStringArray* ImageNames, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); - HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count); - HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count); - HRESULT TagImage([in] const WSLCTagImageOptions* Options); - HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output); - HRESULT PruneImages([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *DeletedImagesCount)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* DeletedImagesCount, [out] ULONGLONG* SpaceReclaimed); - - // Container management. - HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container); - HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container); - HRESULT ListContainers([in, unique] const WSLCListContainersOptions* Options,[out, size_is(, *Count)] WSLCContainerEntry** Containers,[out] ULONG* Count, [out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports, [out] ULONG* PortsCount); - HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result); - - // Create a process at the VM level. This is meant for debugging. - HRESULT CreateRootNamespaceProcess([in, ref] LPCSTR Executable, [in, ref] const WSLCProcessOptions* Options, [in] ULONG TtyRows, [in] ULONG TtyColumns, [out] IWSLCProcess** Process, [out] int* Errno); - - // TODO: an OpenProcess() method can be added later if needed. - - // Disk management. - HRESULT FormatVirtualDisk([in, ref] LPCWSTR Path); - - // Terminate the VM and containers. - HRESULT Terminate(); - - // Used only for testing. TODO: Think about moving them to a dedicated testing-only interface. - HRESULT MountWindowsFolder([in, ref] LPCWSTR WindowsPath, [in, ref] LPCSTR LinuxPath, [in] BOOL ReadOnly); - HRESULT UnmountWindowsFolder([in, ref] LPCSTR LinuxPath); - HRESULT MapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); - HRESULT UnmapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); - - // Session initialization - called by SYSTEM service after launching per-user process. - // Returns a handle to this COM server process (used to add to job object). - HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); - - // Initializes the session with a VM factory. VMs are created through the factory. - HRESULT Initialize( - [in] const WSLCSessionInitSettings* Settings, - [in] IWSLCVirtualMachineFactory* VmFactory, - [in] IWSLCPluginNotifier* PluginNotifier, - [in, unique] IWarningCallback* WarningCallback); - - // Volume management. - HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo); - HRESULT DeleteVolume([in] LPCSTR Name); - HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count); - HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output); - - HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken); - HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); - HRESULT PruneVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [in, unique] IWarningCallback* WarningCallback, [out, size_is(, *VolumesCount)] WSLCVolumeName** Volumes, [out] ULONG* VolumesCount, [out] ULONGLONG* SpaceReclaimed); - - // Network management. - HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback); - HRESULT DeleteNetwork([in] LPCSTR Name); - HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count); - HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output); - HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount); - - HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription); -} - -// -// IWSLCSessionReference - Weak reference to a session held by the SYSTEM service. -// Stored in per-user process, allows service to check liveness and terminate sessions. -// Session metadata (ID, name, etc.) is stored service-side in SessionEntry. -// -[ - uuid(B3A72F48-9D15-4E8A-A621-7C3E84F09B52), - pointer_default(unique), - object -] -interface IWSLCSessionReference : IUnknown -{ - // Try to open the session. Fails if session was released or terminated. - // Returns S_OK and a valid session if still alive. - HRESULT OpenSession([out] IWSLCSession** Session); - - // Terminate the session if still alive. - HRESULT Terminate(); -} - -// -// IWSLCSessionFactory - Creates sessions in the per-user COM server process. -// Called by the SYSTEM service via CoCreateInstanceAsUser. -// -[ - uuid(C4E8F291-3B5D-4A7C-9E12-8F6A4D2B7C91), - pointer_default(unique), - object -] -interface IWSLCSessionFactory : IUnknown -{ - // Creates a new session and returns both the session interface and a service reference. - HRESULT CreateSession( - [in] const WSLCSessionInitSettings* Settings, - [in] IWSLCVirtualMachineFactory* VmFactory, - [in] IWSLCPluginNotifier* PluginNotifier, - [in, unique] IWarningCallback* WarningCallback, - [out] IWSLCSession** Session, - [out] IWSLCSessionReference** ServiceRef); - - // Gets the process handle for adding to job object. - HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); -} - -typedef struct _WSLCSessionListEntry -{ - ULONG SessionId; - DWORD CreatorPid; - wchar_t DisplayName[256]; - wchar_t Sid[256 + 1]; // MAX_SID_SIZE = 256 -} WSLCSessionListEntry; - -[ - uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760), - pointer_default(unique), - object -] -interface IWSLCSessionManager : IUnknown -{ - HRESULT GetVersion([out] WSLCVersion* Version); - // Session management. - HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); - HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); - HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount); - HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session); - HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session); -} - -// Ensure wslcsdk.h and wslcsdk.idl are also updated. -cpp_quote("#define WSLC_E_BASE (0x0600)") -cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */") -cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */") -cpp_quote("#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */") -cpp_quote("#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */") -cpp_quote("#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */") -cpp_quote("#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */") -cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */") -cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */") -cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */") -cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */") -cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */") -cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */") -cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */") -cpp_quote("#define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */") -cpp_quote("#define WSLC_E_SESSION_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 15) /* 0x8004060F */") +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + wslc.idl + +Abstract: + + This file contains the WSLC-related COM object definitions. + // N.B. ABI breaking changes in this file are OK, since both client & server always ship together. + // The WSLC SDK must not use this file, and instead use WSLCCompat.idl + +--*/ + +import "unknwn.idl"; +import "wtypes.idl"; + +// Enums and flags shared with the SDK-facing API (WSLCCompat.idl). +import "WSLCShared.idl"; + +cpp_quote("#ifdef __cplusplus") +cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLCSessionManager;") +cpp_quote("class DECLSPEC_UUID(\"9fcd2067-9fc6-4efa-9eb0-698169ebf7d3\") WSLCSessionFactory;") +cpp_quote("#endif") + +#define WSLC_MAX_CONTAINER_NAME_LENGTH 255 +#define WSLC_MAX_IMAGE_NAME_LENGTH 255 +#define WSLC_MAX_VOLUME_NAME_LENGTH 255 +#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255 +#define WSLC_MAX_NETWORK_NAME_LENGTH 255 +#define WSLC_CONTAINER_ID_LENGTH 64 +#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45 +#define WSLC_EPHEMERAL_PORT 0 +#define WSLC_MAX_SAVE_IMAGES_COUNT 256 + +cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255") +cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255") +cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255") +cpp_quote("#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255") +cpp_quote("#define WSLC_MAX_NETWORK_NAME_LENGTH 255") +cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64") +cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45") +cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256") +cpp_quote("#define WSLC_EPHEMERAL_PORT 0") +cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MAX") +cpp_quote("#define WSLC_STOP_TIMEOUT_NONE -1") + +typedef +struct _WSLCVersion { + ULONG Major; + ULONG Minor; + ULONG Revision; +} WSLCVersion; + +[ + uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801), + pointer_default(unique), + object +] +interface ICrashDumpCallback : IUnknown +{ + HRESULT OnCrashDump( + [in, string] LPCWSTR DumpPath, + [in, unique, string] LPCSTR ProcessName, + [in] ULONG Pid, + [in] ULONG Signal, + [in] ULONGLONG Timestamp); +}; + +[ + uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1), + pointer_default(unique), + object +] +interface IProgressCallback : IUnknown +{ + HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total); +}; + +[ + uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3), + pointer_default(unique), + object +] +interface IWarningCallback : IUnknown +{ + HRESULT OnWarning([in, string] LPCWSTR Message); +}; + +[ + uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2), + pointer_default(unique), + object +] +interface IWSLCPluginNotifier : IUnknown +{ + // 'InspectJson' follows the wslc_schema::InspectContainer format. + // Returning failure prevents the container creation. + HRESULT OnContainerStarted([in] LPCSTR InspectJson); + + // Called when a container is about to stop. 'ContainerId' is the container identifier. Errors are logged but ignored. + HRESULT OnContainerStopping([in] LPCSTR ContainerId); + + // 'InspectJson' follows the wslc_schema::InspectImage format. Errors are logged but ignored. + HRESULT OnImageCreated([in] LPCSTR InspectJson); + + // Called when an image is deleted. 'ImageId' is the image identifier. Errors are logged but ignored. + HRESULT OnImageDeleted([in] LPCSTR ImageId); +}; + +typedef struct _WSLCImageInformation +{ + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; + char Hash[256]; + char Digest[256]; + LONGLONG Size; // Matches Docker's int64 image size + LONGLONG Created; // Unix timestamp + char ParentId[256]; +} WSLCImageInformation; + +typedef struct _KeyValuePairInformation +{ + [string] LPSTR Key; + [string] LPSTR Value; +} KeyValuePairInformation; + +typedef struct _KeyValuePair +{ + [string] LPCSTR Key; + [string] LPCSTR Value; +} KeyValuePair; + +typedef KeyValuePair WSLCLabel; +typedef KeyValuePair WSLCDriverOption; +typedef KeyValuePair WSLCFilter; + +typedef KeyValuePairInformation WSLCLabelInformation; +typedef KeyValuePairInformation WSLCDriverOptionInformation; + +typedef struct _WSLCListImagesOptions +{ + DWORD Flags; // WSLCListImagesFlags (can combine with bitwise OR) + [unique, size_is(FiltersCount)] const WSLCFilter* Filters; + ULONG FiltersCount; +} WSLCListImagesOptions; + +typedef struct _WSLCStringArray +{ + [unique, size_is(Count)] LPCSTR const* Values; + ULONG Count; +} WSLCStringArray; + +typedef struct _WSLCProcessOptions +{ + [unique] LPCSTR CurrentDirectory; + [unique] LPCSTR User; + WSLCStringArray CommandLine; + WSLCStringArray Environment; + WSLCProcessFlags Flags; +} WSLCProcessOptions; + +typedef struct _WSLCProcessStartOptions +{ + ULONG TtyRows; // Only needed when tty fd's are passed. + ULONG TtyColumns; + [unique, string] LPCSTR DetachKeys; +} WSLCProcessStartOptions; + +typedef struct _WSLCNamedVolume +{ + LPCSTR Name; + LPCSTR ContainerPath; + BOOL ReadOnly; +} WSLCNamedVolume; + +typedef struct _WSLCVolume +{ + LPCWSTR HostPath; + LPCSTR ContainerPath; + BOOL ReadOnly; +} WSLCVolume; + +typedef struct _WSLCPortMapping +{ + USHORT HostPort; + USHORT ContainerPort; + int Family; + int Protocol; + char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1]; +} WSLCPortMapping; + +typedef struct _WSLCTmpfsMount +{ + LPCSTR Destination; + [unique] LPCSTR Options; +} WSLCTmpfsMount; + +typedef struct _WSLCUlimit +{ + [string] LPCSTR Name; + LONGLONG Soft; + LONGLONG Hard; +} WSLCUlimit; + +typedef struct _WSLCNetworkConnection +{ + [string] LPCSTR NetworkName; + [unique, size_is(SettingsCount)] const KeyValuePair* Settings; + ULONG SettingsCount; +} WSLCNetworkConnection; + +// Options for IWSLCContainer::ConnectToNetwork. +typedef struct _WSLCNetworkConnectionOptions +{ + [unique] LPCSTR NetworkName; + [unique] LPCSTR ContainerIpAddress; // Reserved for future --ip support; must be NULL today. +} WSLCNetworkConnectionOptions; + +typedef struct _WSLCContainerNetwork +{ + [unique, string] LPCSTR NetworkMode; + + [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks; + ULONG NetworksCount; + + // Settings for the primary endpoint (the network identified by NetworkMode). + // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries). + [unique, size_is(SettingsCount)] const KeyValuePair* Settings; + ULONG SettingsCount; +} WSLCContainerNetwork; + +typedef struct _WSLCContainerOptions +{ + LPCSTR Image; + [unique] LPCSTR Name; + WSLCStringArray Entrypoint; + WSLCProcessOptions InitProcessOptions; + [unique, size_is(VolumesCount)] WSLCVolume* Volumes; + ULONG VolumesCount; + [unique, size_is(PortsCount)] WSLCPortMapping* Ports; + ULONG PortsCount; + [unique, size_is(LabelsCount)] const WSLCLabel* Labels; + ULONG LabelsCount; + WSLCContainerFlags Flags; + WSLCSignal StopSignal; + // TODO: List specific GPU devices. + [unique] LPCSTR HostName; + [unique] LPCSTR DomainName; + + WSLCStringArray DnsServers; + WSLCStringArray DnsSearchDomains; + WSLCStringArray DnsOptions; + + LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus + WSLCContainerNetwork ContainerNetwork; + [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs; + ULONG TmpfsCount; + + [unique, size_is(NamedVolumesCount)] WSLCNamedVolume* NamedVolumes; + ULONG NamedVolumesCount; + + LONGLONG MemoryBytes; + LONGLONG NanoCpus; + [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits; + ULONG UlimitsCount; + + // Timeout in seconds to wait for the container to stop before killing it. + // WSLC_STOP_TIMEOUT_DEFAULT indicates the timeout was not specified, so the + // container runtime default is used. WSLC_STOP_TIMEOUT_NONE (-1) means no timeout + // (wait indefinitely). Any other negative value is rejected. + LONG StopTimeout; +} WSLCContainerOptions; + +typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ; + +typedef struct _WSLCContainerEntry +{ + char Name[WSLC_MAX_CONTAINER_NAME_LENGTH + 1]; + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; + WSLCContainerId Id; + ULONGLONG StateChangedAt; + ULONGLONG CreatedAt; + WSLCContainerState State; +} WSLCContainerEntry; + +typedef struct _WSLCContainerPortMapping +{ + WSLCContainerId Id; + WSLCPortMapping PortMapping; +} WSLCContainerPortMapping; + +typedef [system_handle(sh_file)] HANDLE FILE_HANDLE; +typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE; +typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE; + +typedef struct _WSLCHandle +{ + WSLCHandleType Type; + + [switch_type(WSLCHandleType), switch_is(Type)] + union + { + [case(WSLCHandleTypeFile)] + FILE_HANDLE File; + [case(WSLCHandleTypePipe)] + PIPE_HANDLE Pipe; + [case(WSLCHandleTypeSocket)] + SOCKET_HANDLE Socket; + [default]; + } Handle; +} WSLCHandle; + +[ + uuid(1AD163CD-393D-4B33-83A2-8A3F3F23E608), + pointer_default(unique), + object +] +interface IWSLCProcess : IUnknown +{ + HRESULT Signal([in] int Signal); + HRESULT GetExitEvent([out, system_handle(sh_event)] HANDLE* EventHandle); + HRESULT GetStdHandle([in] WSLCFD Fd, [out] WSLCHandle* Handle); + HRESULT GetFlags([out] WSLCProcessFlags* Flags); + HRESULT GetPid([out] int* Pid); + HRESULT GetState([out] WSLCProcessState* State, [out] int* Code); + HRESULT ResizeTty([in] ULONG Rows, [in] ULONG Columns); + + // Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API. +} + +// +// Values discovered from the guest kernel after the VM has booted, forwarded +// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new +// fields here instead of new IDL methods so the interface does not need a new +// IID for each kernel-published value. +// +typedef struct _WSLCGuestCapabilities +{ + // (base, size) of the hv_pci swiotlb pool the kernel reserved and + // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}. + // Both zero means the running kernel does not support hv_pci swiotlb. + UINT64 HvPciSwiotlbBase; + UINT64 HvPciSwiotlbSize; +} WSLCGuestCapabilities; + +// +// IWSLCVirtualMachine - Interface representing a single VM instance. +// Operations are scoped to this VM. The VM ID is stored internally, +// so only the holder of this interface can operate on the VM. +// +[ + uuid(B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A), + pointer_default(unique), + object +] +interface IWSLCVirtualMachine : IUnknown +{ + // Gets the VM ID. + HRESULT GetId([out, retval] GUID* VmId); + + // Accepts a connect from mini_init in the VM. + HRESULT AcceptConnection([out, system_handle(sh_socket)] HANDLE* Socket); + + // Configures networking engine with sockets from the user process. + // GnsSocket is required; DnsSocket is optional (NULL if DNS tunneling is disabled). + // The service duplicates the socket handles. + HRESULT ConfigureNetworking( + [in, system_handle(sh_socket)] HANDLE GnsSocket, + [in, system_handle(sh_socket), unique] HANDLE* DnsSocket); + + // Attaches a VHD or VHDX disk to the VM. + // GrantVmAccess is called by the service before attaching. + // Returns the SCSI LUN assigned to the disk. + HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out, retval] ULONG* Lun); + + // Detaches a previously attached disk from the VM. + HRESULT DetachDisk([in] ULONG Lun); + + // Adds a filesystem share (Plan9 or VirtioFS) accessible to the VM. + // Returns an instance GUID that can be used to remove the share. + HRESULT AddShare([in] LPCWSTR WindowsPath, [in] BOOL ReadOnly, [out, retval] GUID* ShareId); + + // Removes a previously added filesystem share. + HRESULT RemoveShare([in] REFGUID ShareId); + + // Configures the per-VM state discovered from the guest kernel after boot + // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and + // ConfigureNetworking forward these values to wsldevicehost via the + // swiotlb device-options token. A capabilities struct whose fields are + // all zero means the guest kernel does not support the feature; the + // token is then omitted. + HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities); + + // Returns an event that is signaled when the VM exits (graceful or forced). + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); + + HRESULT MapVirtioNetPort( + [in] USHORT HostPort, + [in] USHORT GuestPort, + [in] int Protocol, + [in] LPCSTR ListenAddress, + [out, retval] USHORT* AllocatedHostPort); + + // Unmaps a port previously mapped via MapVirtioNetPort. + HRESULT UnmapVirtioNetPort( + [in] USHORT HostPort, + [in] USHORT GuestPort, + [in] int Protocol, + [in] LPCSTR ListenAddress); + + // Returns the cached termination reason and details. These are only available after the + // termination event has been signaled; before that the call fails. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); +} + +// +// IWSLCVirtualMachineFactory - Creates VMs on demand for a session. +// +// Held by the per-user session process and implemented by the SYSTEM service. +// This lets the session create a fresh VM at any time (e.g. to recreate a VM that +// was idle-terminated when it had no running containers), instead of the service +// eagerly creating a single VM up front. Each successful call returns a new VM whose +// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down. +// +[ + uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B), + pointer_default(unique), + object +] +interface IWSLCVirtualMachineFactory : IUnknown +{ + // Creates a new VM using the settings captured at session creation time. + HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm); +} + +// Settings for IWSLCSessionManager::CreateSession - full session configuration +typedef struct _WSLCSessionSettings { + LPCWSTR DisplayName; + LPCWSTR StoragePath; + ULONGLONG MaximumStorageSizeMb; + ULONG CpuCount; + ULONG MemoryMb; + ULONG BootTimeoutMs; + WSLCNetworkingMode NetworkingMode; + WSLCFeatureFlags FeatureFlags; + WSLCHandle DmesgOutput; + WSLCSessionStorageFlags StorageFlags; + + // Below options are used for debugging purposes only. + [unique] LPCWSTR RootVhdOverride; + [unique] LPCSTR RootVhdTypeOverride; +} WSLCSessionSettings; + + +[ + uuid(7577FE8D-DE85-471E-B870-11669986F332), + pointer_default(unique), + object +] +interface IWSLCContainer : IUnknown +{ + HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr); + HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds); + HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] const WSLCProcessStartOptions* StartOptions, [in, unique] IWarningCallback* WarningCallback); + HRESULT Delete([in] WSLCDeleteFlags Flags); + HRESULT Export([in] WSLCHandle TarHandle); + HRESULT GetState([out] WSLCContainerState* State); + HRESULT GetInitProcess([out] IWSLCProcess** Process); + HRESULT Exec([in, ref] const WSLCProcessOptions* Options, [in, unique] const WSLCProcessStartOptions* StartOptions, [out] IWSLCProcess** Process); + HRESULT Inspect([out] LPSTR* Output); + HRESULT Logs([in] WSLCLogsFlags Flags, [out] WSLCHandle* Stdout, [out] WSLCHandle* Stderr, [in] ULONGLONG Since, [in] ULONGLONG Until, [in] ULONGLONG Tail); + HRESULT GetId([out, string] WSLCContainerId Id); + HRESULT GetName([out, string] LPSTR* Name); + HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count); + HRESULT Kill([in] WSLCSignal Signal); + HRESULT Stats([out] LPSTR* Output); + HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options); + HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName); +} + +typedef struct _WSLCDeletedImageInformation +{ + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; + WSLCDeletedImageType Type; +} WSLCDeletedImageInformation; + +typedef struct _WSLCDeleteImageOptions +{ + LPCSTR Image; // Image can be ID or Repo:Tag. + DWORD Flags; // WSLCDeleteImageFlags + // TODO: Platforms: a json array of OCI platform strings. +} WSLCDeleteImageOptions; + +typedef struct _WSLCBuildImageOptions +{ + LPCWSTR ContextPath; + WSLCHandle DockerfileHandle; + WSLCStringArray Tags; + WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker. + LPCSTR Target; // Target build stage name passed as --target to docker. + WSLCBuildImageFlags Flags; // WSLCBuildImageFlags + WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker. +} WSLCBuildImageOptions; + +typedef struct _WSLCTagImageOptions +{ + LPCSTR Image; // Source image name or ID. + LPCSTR Repo; // Target repository name. + LPCSTR Tag; // Target tag name. +} WSLCTagImageOptions; + +typedef struct _WSLCVolumeOptions +{ + [unique] LPCSTR Name; + [unique] LPCSTR Driver; + [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; + ULONG DriverOptsCount; + [unique, size_is(LabelsCount)] const WSLCLabel* Labels; + ULONG LabelsCount; +} WSLCVolumeOptions; + +typedef char WSLCVolumeName[WSLC_MAX_VOLUME_NAME_LENGTH + 1]; + +typedef struct _WSLCVolumeInformation +{ + WSLCVolumeName Name; + char Driver[WSLC_MAX_VOLUME_DRIVER_LENGTH + 1]; +} WSLCVolumeInformation; + +typedef struct _WSLCNetworkOptions +{ + LPCSTR Name; + [unique] LPCSTR Driver; + [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; + ULONG DriverOptsCount; + [unique, size_is(LabelsCount)] const WSLCLabel* Labels; + ULONG LabelsCount; +} WSLCNetworkOptions; + +typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; + +typedef struct _WSLCNetworkInformation +{ + char Name[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; + char Id[WSLC_CONTAINER_ID_LENGTH + 1]; + char Driver[64]; +} WSLCNetworkInformation; + +typedef struct _WSLCPruneContainersResults +{ + [unique, size_is(ContainersCount)] WSLCContainerId* Containers; + ULONG ContainersCount; + ULONGLONG SpaceReclaimed; +} WSLCPruneContainersResults; + +typedef struct _WSLCListContainersOptions +{ + DWORD Flags; // WSLCListContainersFlags + LONG Limit; + + [unique, size_is(FiltersCount)] const WSLCFilter* Filters; + ULONG FiltersCount; +} WSLCListContainersOptions; + +// Settings for IWSLCSession::Initialize - passed from service to per-user process +typedef struct _WSLCSessionInitSettings +{ + ULONG SessionId; + [unique] LPCWSTR CreatorProcessName; + LPCWSTR DisplayName; + LPCWSTR StoragePath; + WSLCSessionStorageFlags StorageFlags; + ULONGLONG MaximumStorageSizeMb; + ULONG SwapSizeMb; + ULONG BootTimeoutMs; + WSLCNetworkingMode NetworkingMode; + WSLCFeatureFlags FeatureFlags; + [unique] LPCSTR RootVhdTypeOverride; +} WSLCSessionInitSettings; + +[ + uuid(EF0661E4-6364-40EA-B433-E2FDF11F3519), + pointer_default(unique), + object +] +interface IWSLCSession : IUnknown +{ + HRESULT GetId([out] ULONG* Id); + HRESULT GetDisplayName([out] LPWSTR* DisplayName); + HRESULT GetState([out] WSLCSessionState* State); + + // Returns a one-off event that is signaled when the session terminates, whether due to an + // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the + // caller and remains valid (and observes the signaled state) even after the session is released. + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); + + // Returns the cached termination reason and details. These are only available after the + // termination event has been signaled; before that the call fails. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); + + // Image management. + HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); + HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); + HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback); + HRESULT ImportImage([in] WSLCHandle ImageHandle, [in, unique] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback, [out] LPSTR* ImageId); + HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); + HRESULT SaveImages([in] WSLCHandle OutputHandle, [in] const WSLCStringArray* ImageNames, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); + HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count); + HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count); + HRESULT TagImage([in] const WSLCTagImageOptions* Options); + HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output); + HRESULT PruneImages([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *DeletedImagesCount)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* DeletedImagesCount, [out] ULONGLONG* SpaceReclaimed); + + // Container management. + HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container); + HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container); + HRESULT ListContainers([in, unique] const WSLCListContainersOptions* Options,[out, size_is(, *Count)] WSLCContainerEntry** Containers,[out] ULONG* Count, [out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports, [out] ULONG* PortsCount); + HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result); + + // Create a process at the VM level. This is meant for debugging. + HRESULT CreateRootNamespaceProcess([in, ref] LPCSTR Executable, [in, ref] const WSLCProcessOptions* Options, [in] ULONG TtyRows, [in] ULONG TtyColumns, [out] IWSLCProcess** Process, [out] int* Errno); + + // TODO: an OpenProcess() method can be added later if needed. + + // Disk management. + HRESULT FormatVirtualDisk([in, ref] LPCWSTR Path); + + // Terminate the VM and containers. + HRESULT Terminate(); + + // Used only for testing. TODO: Think about moving them to a dedicated testing-only interface. + HRESULT MountWindowsFolder([in, ref] LPCWSTR WindowsPath, [in, ref] LPCSTR LinuxPath, [in] BOOL ReadOnly); + HRESULT UnmountWindowsFolder([in, ref] LPCSTR LinuxPath); + HRESULT MapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); + HRESULT UnmapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); + + // Session initialization - called by SYSTEM service after launching per-user process. + // Returns a handle to this COM server process (used to add to job object). + HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); + + // Initializes the session with a VM factory. VMs are created through the factory. + HRESULT Initialize( + [in] const WSLCSessionInitSettings* Settings, + [in] IWSLCVirtualMachineFactory* VmFactory, + [in] IWSLCPluginNotifier* PluginNotifier, + [in, unique] IWarningCallback* WarningCallback); + + // Volume management. + HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo); + HRESULT DeleteVolume([in] LPCSTR Name); + HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count); + HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output); + + HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken); + HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); + HRESULT PruneVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [in, unique] IWarningCallback* WarningCallback, [out, size_is(, *VolumesCount)] WSLCVolumeName** Volumes, [out] ULONG* VolumesCount, [out] ULONGLONG* SpaceReclaimed); + + // Network management. + HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback); + HRESULT DeleteNetwork([in] LPCSTR Name); + HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count); + HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output); + HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount); + + HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription); +} + +// +// IWSLCSessionReference - Weak reference to a session held by the SYSTEM service. +// Stored in per-user process, allows service to check liveness and terminate sessions. +// Session metadata (ID, name, etc.) is stored service-side in SessionEntry. +// +[ + uuid(B3A72F48-9D15-4E8A-A621-7C3E84F09B52), + pointer_default(unique), + object +] +interface IWSLCSessionReference : IUnknown +{ + // Try to open the session. Fails if session was released or terminated. + // Returns S_OK and a valid session if still alive. + HRESULT OpenSession([out] IWSLCSession** Session); + + // Terminate the session if still alive. + HRESULT Terminate(); +} + +// +// IWSLCSessionFactory - Creates sessions in the per-user COM server process. +// Called by the SYSTEM service via CoCreateInstanceAsUser. +// +[ + uuid(C4E8F291-3B5D-4A7C-9E12-8F6A4D2B7C91), + pointer_default(unique), + object +] +interface IWSLCSessionFactory : IUnknown +{ + // Creates a new session and returns both the session interface and a service reference. + HRESULT CreateSession( + [in] const WSLCSessionInitSettings* Settings, + [in] IWSLCVirtualMachineFactory* VmFactory, + [in] IWSLCPluginNotifier* PluginNotifier, + [in, unique] IWarningCallback* WarningCallback, + [out] IWSLCSession** Session, + [out] IWSLCSessionReference** ServiceRef); + + // Gets the process handle for adding to job object. + HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); +} + +typedef struct _WSLCSessionListEntry +{ + ULONG SessionId; + DWORD CreatorPid; + wchar_t DisplayName[256]; + wchar_t Sid[256 + 1]; // MAX_SID_SIZE = 256 +} WSLCSessionListEntry; + +[ + uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760), + pointer_default(unique), + object +] +interface IWSLCSessionManager : IUnknown +{ + HRESULT GetVersion([out] WSLCVersion* Version); + // Session management. + HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); + HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); + HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount); + HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session); + HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session); +} + +// Ensure wslcsdk.h and wslcsdk.idl are also updated. +cpp_quote("#define WSLC_E_BASE (0x0600)") +cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */") +cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */") +cpp_quote("#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */") +cpp_quote("#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */") +cpp_quote("#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */") +cpp_quote("#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */") +cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */") +cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */") +cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */") +cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */") +cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */") +cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */") +cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */") +cpp_quote("#define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */") +cpp_quote("#define WSLC_E_SESSION_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 15) /* 0x8004060F */") diff --git a/src/windows/wslc/arguments/ArgumentDefinitions.h b/src/windows/wslc/arguments/ArgumentDefinitions.h index 200022bcb6..c43bb3c915 100644 --- a/src/windows/wslc/arguments/ArgumentDefinitions.h +++ b/src/windows/wslc/arguments/ArgumentDefinitions.h @@ -103,6 +103,7 @@ _(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L _(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription()) \ _(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \ _(StopSignal, "stop-signal", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopSignalArgDescription()) \ +_(StopTimeout, "stop-timeout", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopTimeoutArgDescription()) \ _(Tail, "tail", L"n", Kind::Value, Localization::WSLCCLI_TailArgDescription()) \ _(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \ _(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \ diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index 735196f018..cd1341913f 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -48,6 +48,13 @@ void Argument::Validate(const ArgMap& execArgs) const validation::ValidateWSLCSignalFromString(execArgs.GetAll(), m_name); break; + case ArgType::StopTimeout: + // Only validate that the value is an integer here; range validation (rejecting + // negative values) happens in the service so that --stop-timeout and `container + // stop --time` share a single source of truth and error message. + validation::ValidateIntegerFromString(execArgs.GetAll(), m_name); + break; + case ArgType::ShmSize: validation::ValidateMemorySize(execArgs.GetAll(), m_name); break; diff --git a/src/windows/wslc/commands/ContainerCreateCommand.cpp b/src/windows/wslc/commands/ContainerCreateCommand.cpp index 1997e8f275..c21d0ecec7 100644 --- a/src/windows/wslc/commands/ContainerCreateCommand.cpp +++ b/src/windows/wslc/commands/ContainerCreateCommand.cpp @@ -58,6 +58,7 @@ std::vector ContainerCreateCommand::GetArguments() const // Argument::Create(ArgType::Scheme), Argument::Create(ArgType::ShmSize), Argument::Create(ArgType::StopSignal), + Argument::Create(ArgType::StopTimeout), Argument::Create(ArgType::TMPFS, false, NO_LIMIT), Argument::Create(ArgType::TTY), Argument::Create(ArgType::Ulimit, false, NO_LIMIT), diff --git a/src/windows/wslc/commands/ContainerRunCommand.cpp b/src/windows/wslc/commands/ContainerRunCommand.cpp index c754e4c80d..990b5dd8d4 100644 --- a/src/windows/wslc/commands/ContainerRunCommand.cpp +++ b/src/windows/wslc/commands/ContainerRunCommand.cpp @@ -59,6 +59,7 @@ std::vector ContainerRunCommand::GetArguments() const // Argument::Create(ArgType::Scheme), Argument::Create(ArgType::ShmSize), Argument::Create(ArgType::StopSignal), + Argument::Create(ArgType::StopTimeout), Argument::Create(ArgType::TMPFS, false, NO_LIMIT), Argument::Create(ArgType::TTY), Argument::Create(ArgType::Ulimit, false, NO_LIMIT), diff --git a/src/windows/wslc/services/ContainerModel.h b/src/windows/wslc/services/ContainerModel.h index da9d5dbc60..eb7bd2f1a9 100644 --- a/src/windows/wslc/services/ContainerModel.h +++ b/src/windows/wslc/services/ContainerModel.h @@ -38,6 +38,7 @@ struct ContainerOptions bool TTY = false; bool PublishAll = false; WSLCSignal StopSignal = WSLCSignalNone; + std::optional StopTimeout{}; std::optional ShmSize{}; bool Gpu = false; std::vector Ports; @@ -67,10 +68,8 @@ struct CreateContainerResult struct StopContainerOptions { - static constexpr LONG DefaultTimeout = -1; - WSLCSignal Signal = WSLCSignalNone; - LONG Timeout = DefaultTimeout; + LONG Timeout = WSLC_STOP_TIMEOUT_DEFAULT; }; struct PruneContainersResult diff --git a/src/windows/wslc/services/ContainerService.cpp b/src/windows/wslc/services/ContainerService.cpp index 529c1039f8..9a85ebf988 100644 --- a/src/windows/wslc/services/ContainerService.cpp +++ b/src/windows/wslc/services/ContainerService.cpp @@ -136,6 +136,11 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal( containerLauncher.SetDefaultStopSignal(options.StopSignal); } + if (options.StopTimeout.has_value()) + { + containerLauncher.SetStopTimeout(options.StopTimeout.value()); + } + if (options.ShmSize.has_value()) { containerLauncher.SetShmSize(options.ShmSize.value()); diff --git a/src/windows/wslc/tasks/ContainerTasks.cpp b/src/windows/wslc/tasks/ContainerTasks.cpp index bbd61025a6..ad7d112d87 100644 --- a/src/windows/wslc/tasks/ContainerTasks.cpp +++ b/src/windows/wslc/tasks/ContainerTasks.cpp @@ -443,6 +443,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context) options.StopSignal = validation::GetWSLCSignalFromString(context.Args.Get()); } + if (context.Args.Contains(ArgType::StopTimeout)) + { + options.StopTimeout = validation::GetIntegerFromString(context.Args.Get()); + } + if (context.Args.Contains(ArgType::ShmSize)) { options.ShmSize = validation::GetMemorySizeFromString(context.Args.Get()); diff --git a/src/windows/wslcsession/DockerHTTPClient.cpp b/src/windows/wslcsession/DockerHTTPClient.cpp index 9c855a6d63..98b9a3e45e 100644 --- a/src/windows/wslcsession/DockerHTTPClient.cpp +++ b/src/windows/wslcsession/DockerHTTPClient.cpp @@ -302,7 +302,7 @@ void DockerHTTPClient::StartContainer(const std::string& Id, const std::optional Transaction(verb::post, url); } -void DockerHTTPClient::StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds) +void DockerHTTPClient::StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds) { auto url = URL::Create("/containers/{}/stop", Id); if (Signal.has_value()) diff --git a/src/windows/wslcsession/DockerHTTPClient.h b/src/windows/wslcsession/DockerHTTPClient.h index fd24a490bd..3e837be726 100644 --- a/src/windows/wslcsession/DockerHTTPClient.h +++ b/src/windows/wslcsession/DockerHTTPClient.h @@ -126,7 +126,7 @@ class DockerHTTPClient bool all = false, int limit = -1, const std::map>& filters = {}); common::docker_schema::CreatedContainer CreateContainer(const common::docker_schema::CreateContainer& Request, const std::optional& Name); void StartContainer(const std::string& Id, const std::optional& DetachKeys); - void StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds); + void StopContainer(const std::string& Id, std::optional Signal, std::optional TimeoutSeconds); void DeleteContainer(const std::string& Id, bool Force, bool DeleteVolumes = false); void SignalContainer(const std::string& Id, std::optional Signal); common::docker_schema::InspectContainer InspectContainer(const std::string& Id); diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 4da373ed43..7965d92a1d 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -67,6 +67,17 @@ using WslcInspectContainer = wsl::windows::common::wslc_schema::InspectContainer namespace { +// Validates a stop timeout value. WSLC_STOP_TIMEOUT_DEFAULT means the timeout was not +// specified (the container runtime default is used) and WSLC_STOP_TIMEOUT_NONE (-1) means +// no timeout (wait indefinitely). Any other negative value is invalid. +void ValidateStopTimeout(LONG TimeoutSeconds) +{ + THROW_HR_WITH_USER_ERROR_IF( + E_INVALIDARG, + Localization::MessageWslcInvalidStopTimeout(TimeoutSeconds), + TimeoutSeconds < 0 && TimeoutSeconds != WSLC_STOP_TIMEOUT_NONE && TimeoutSeconds != WSLC_STOP_TIMEOUT_DEFAULT); +} + std::vector StringArrayToVector(const WSLCStringArray& array) { if (array.Count == 0) @@ -944,6 +955,8 @@ void WSLCContainerImpl::Stop(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill) SignalArg = Signal; } + ValidateStopTimeout(TimeoutSeconds); + // Don't wait for the container to stop if we're not sending SIGKILL, since it may not stop the container. // N.B. If the signal was SIGTERM for instance, we'll receive the stop notification via OnEvent(). bool waitForStop = !Kill || (SignalArg.value_or(WSLCSignalSIGKILL) == WSLCSignalSIGKILL); @@ -961,10 +974,10 @@ void WSLCContainerImpl::Stop(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill) } else { - std::optional TimeoutArg; - if (TimeoutSeconds >= 0) + std::optional TimeoutArg; + if (TimeoutSeconds != WSLC_STOP_TIMEOUT_DEFAULT) { - TimeoutArg = static_cast(TimeoutSeconds); + TimeoutArg = TimeoutSeconds; } m_dockerClient.StopContainer(m_id, SignalArg, TimeoutArg); @@ -1305,6 +1318,7 @@ WslcInspectContainer WSLCContainerImpl::BuildInspectContainer(const DockerInspec wslcInspect.Config.Entrypoint = dockerInspect.Config.Entrypoint; wslcInspect.Config.User = dockerInspect.Config.User; wslcInspect.Config.WorkingDir = dockerInspect.Config.WorkingDir; + wslcInspect.Config.StopTimeout = dockerInspect.Config.StopTimeout; // Map WSLC port mappings (Windows host ports only). for (const auto& e : m_mappedPorts) @@ -1419,6 +1433,12 @@ std::unique_ptr WSLCContainerImpl::Create( request.StopSignal = std::to_string(containerOptions.StopSignal); } + ValidateStopTimeout(containerOptions.StopTimeout); + if (containerOptions.StopTimeout != WSLC_STOP_TIMEOUT_DEFAULT) + { + request.StopTimeout = static_cast(containerOptions.StopTimeout); + } + if (containerOptions.InitProcessOptions.CurrentDirectory != nullptr) { request.WorkingDir = containerOptions.InitProcessOptions.CurrentDirectory; diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index ca3bb555b8..7fc8a10bfe 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -726,6 +726,75 @@ class WSLCE2EContainerCreateTests VERIFY_ARE_EQUAL(ExpectedExitCode, inspect.State.ExitCode); } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopTimeout) + { + // A positive value is forwarded to the container configuration. + { + constexpr int ExpectedStopTimeout = 30; + auto result = RunWslc(std::format( + L"container create --stop-timeout {} --name {} {}", + ExpectedStopTimeout, + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value()); + VERIFY_ARE_EQUAL(ExpectedStopTimeout, inspect.Config.StopTimeout.value()); + EnsureContainerDoesNotExist(WslcContainerName); + } + + // A value of 0 (stop the container immediately) is a valid, explicit timeout. + { + auto result = + RunWslc(std::format(L"container create --stop-timeout 0 --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value()); + VERIFY_ARE_EQUAL(0, inspect.Config.StopTimeout.value()); + EnsureContainerDoesNotExist(WslcContainerName); + } + + // A value of -1 means "no timeout"; it is a valid, explicit value forwarded to the configuration. + { + auto result = + RunWslc(std::format(L"container create --stop-timeout -1 --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value()); + VERIFY_ARE_EQUAL(-1, inspect.Config.StopTimeout.value()); + EnsureContainerDoesNotExist(WslcContainerName); + } + + // When --stop-timeout is not specified, no timeout is forwarded to the container configuration. + { + auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_FALSE(inspect.Config.StopTimeout.has_value()); + } + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopTimeout_Invalid) + { + { + auto result = + RunWslc(std::format(L"container create --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1}); + VerifyContainerIsNotListed(WslcContainerName); + } + + { + auto result = + RunWslc(std::format(L"container create --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + VerifyContainerIsNotListed(WslcContainerName); + } + } + WSLC_TEST_METHOD(WSLCE2E_Container_Create_ShmSize) { auto result = RunWslc( @@ -1225,6 +1294,7 @@ class WSLCE2EContainerCreateTests << L" --rm Remove the container after it stops\r\n" << L" --shm-size Size of /dev/shm (e.g. 64M, 1G)\r\n" << L" --stop-signal Signal to stop the container\r\n" + << L" --stop-timeout Timeout (in seconds) to stop the container before killing it (-1 for no timeout)\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" --ulimit Ulimit options (format: =[:], use -1 for unlimited)\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 03f19ecda5..126910c21e 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -971,6 +971,76 @@ class WSLCE2EContainerRunTests VERIFY_ARE_EQUAL(ExpectedExitCode, inspect.State.ExitCode); } + WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout) + { + // A positive value is forwarded to the container configuration. + { + constexpr int ExpectedStopTimeout = 25; + auto result = RunWslc(std::format( + L"container run -d --stop-timeout {} --name {} {} sleep infinity", + ExpectedStopTimeout, + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value()); + VERIFY_ARE_EQUAL(ExpectedStopTimeout, inspect.Config.StopTimeout.value()); + EnsureContainerDoesNotExist(WslcContainerName); + } + + // A value of 0 (stop the container immediately) is a valid, explicit timeout. + { + auto result = RunWslc(std::format( + L"container run -d --stop-timeout 0 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value()); + VERIFY_ARE_EQUAL(0, inspect.Config.StopTimeout.value()); + EnsureContainerDoesNotExist(WslcContainerName); + } + + // A value of -1 means "no timeout"; it is a valid, explicit value forwarded to the configuration. + { + auto result = RunWslc(std::format( + L"container run -d --stop-timeout -1 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value()); + VERIFY_ARE_EQUAL(-1, inspect.Config.StopTimeout.value()); + EnsureContainerDoesNotExist(WslcContainerName); + } + + // When --stop-timeout is not specified, no timeout is forwarded to the container configuration. + { + auto result = RunWslc(std::format( + L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + + const auto inspect = InspectContainer(WslcContainerName); + VERIFY_IS_FALSE(inspect.Config.StopTimeout.has_value()); + } + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout_Invalid) + { + { + auto result = RunWslc( + std::format(L"container run --rm --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1}); + EnsureContainerDoesNotExist(WslcContainerName); + } + + { + auto result = RunWslc( + std::format(L"container run --rm --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); + EnsureContainerDoesNotExist(WslcContainerName); + } + } + 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())); @@ -1177,6 +1247,7 @@ class WSLCE2EContainerRunTests << L" --rm Remove the container after it stops\r\n" << L" --shm-size Size of /dev/shm (e.g. 64M, 1G)\r\n" << L" --stop-signal Signal to stop the container\r\n" + << L" --stop-timeout Timeout (in seconds) to stop the container before killing it (-1 for no timeout)\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" --ulimit Ulimit options (format: =[:], use -1 for unlimited)\r\n" diff --git a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp index 0cef70d0d8..839a302204 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp @@ -239,8 +239,13 @@ class WSLCE2EContainerStopTests WSLC_TEST_METHOD(WSLCE2E_Container_Stop_ValidTimeoutNegativeOne) { - // Run a container in the background - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + // Run a container that exits when it receives SIGTERM (the default stop signal). A container's + // PID 1 ignores signals it has no handler for, so an explicit trap is required for the stop to + // complete. + auto result = RunWslc(std::format( + LR"(container run -d --name {} {} bash -c "trap 'exit 0' TERM; while true; do sleep 1; done")", + WslcContainerName, + DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); const auto containerId = result.GetStdoutOneLine(); VERIFY_IS_FALSE(containerId.empty()); @@ -248,11 +253,49 @@ class WSLCE2EContainerStopTests // Verify container is running VerifyContainerIsListed(containerId, L"running"); - // -1 is a valid timeout value + // -1 means "no timeout" (wait indefinitely), which is a valid value. The container honors + // SIGTERM, so the stop completes promptly. result = RunWslc(std::format(L"container stop {} -t -1", containerId)); result.Verify({.Stderr = L"", .ExitCode = 0}); - // Verify the container is no longer running + // The container should be stopped after a successful stop + VerifyContainerIsListed(containerId, L"exited"); + } + + WSLC_TEST_METHOD(WSLCE2E_Container_Stop_NoTimeoutWaitsUntilKilled) + { + // Run a container that ignores SIGTERM (the default stop signal) so that a stop with no + // timeout (-1) waits indefinitely instead of escalating to SIGKILL. The container is created + // with a default stop timeout of 0 to confirm the explicit -t -1 overrides that configured value. + auto result = RunWslc(std::format( + LR"(container run -d --stop-timeout 0 --name {} {} bash -c "trap '' TERM; while true; do sleep 1; done")", + WslcContainerName, + DebianImage.NameAndTag())); + result.Verify({.Stderr = L"", .ExitCode = 0}); + const auto containerId = result.GetStdoutOneLine(); + VERIFY_IS_FALSE(containerId.empty()); + + // Verify container is running + VerifyContainerIsListed(containerId, L"running"); + + // Stop with -t -1 (no timeout). Because the container ignores SIGTERM and there is no timeout, + // the stop request blocks indefinitely waiting for the container to stop, so run it asynchronously. + auto stopSession = RunWslcInteractive(std::format(L"container stop {} -t -1", containerId)); + + // The container must keep running for at least 2 seconds, confirming -1 did not force a kill. + Sleep(2000); + VERIFY_IS_TRUE(stopSession.IsRunning(), L"`stop -t -1` should still be waiting and must not have killed the container"); + VerifyContainerIsListed(containerId, L"running"); + + // Force the container to stop with a kill (SIGKILL cannot be ignored). + result = RunWslc(std::format(L"container kill {}", containerId)); + result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0}); + + // Once the container is killed, the pending stop completes successfully. + const auto stopExitCode = stopSession.Wait(DefaultWaitTimeoutMs); + VERIFY_ARE_EQUAL(0, stopExitCode); + + // The container should be stopped after the kill VerifyContainerIsListed(containerId, L"exited"); } From 074ac71a9456a306c6dc4e50af38b5b1a5390cda Mon Sep 17 00:00:00 2001 From: Blue Date: Thu, 25 Jun 2026 15:30:07 -0700 Subject: [PATCH 2/9] Add service test coverage --- src/windows/common/WSLCContainerLauncher.h | 2 - src/windows/service/inc/wslc.idl | 1508 ++++++++--------- src/windows/wslcsession/WSLCContainer.cpp | 3 - test/windows/WSLCTests.cpp | 29 + .../wslc/e2e/WSLCE2EContainerStopTests.cpp | 51 +- 5 files changed, 787 insertions(+), 806 deletions(-) diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h index 2842f2edc3..8a8bf8d68e 100644 --- a/src/windows/common/WSLCContainerLauncher.h +++ b/src/windows/common/WSLCContainerLauncher.h @@ -104,8 +104,6 @@ class WSLCContainerLauncher : private WSLCProcessLauncher std::string m_networkMode; std::vector m_entrypoint; WSLCSignal m_stopSignal = WSLCSignalNone; - // WSLC_STOP_TIMEOUT_DEFAULT means the stop timeout was not specified, so the - // container runtime default is used. LONG m_stopTimeout = WSLC_STOP_TIMEOUT_DEFAULT; int64_t m_shmSize = 0; WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone; diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 0560810232..0c4b2441fb 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -1,754 +1,754 @@ -/*++ - -Copyright (c) Microsoft Corporation. All rights reserved. - -Module Name: - - wslc.idl - -Abstract: - - This file contains the WSLC-related COM object definitions. - // N.B. ABI breaking changes in this file are OK, since both client & server always ship together. - // The WSLC SDK must not use this file, and instead use WSLCCompat.idl - ---*/ - -import "unknwn.idl"; -import "wtypes.idl"; - -// Enums and flags shared with the SDK-facing API (WSLCCompat.idl). -import "WSLCShared.idl"; - -cpp_quote("#ifdef __cplusplus") -cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLCSessionManager;") -cpp_quote("class DECLSPEC_UUID(\"9fcd2067-9fc6-4efa-9eb0-698169ebf7d3\") WSLCSessionFactory;") -cpp_quote("#endif") - -#define WSLC_MAX_CONTAINER_NAME_LENGTH 255 -#define WSLC_MAX_IMAGE_NAME_LENGTH 255 -#define WSLC_MAX_VOLUME_NAME_LENGTH 255 -#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255 -#define WSLC_MAX_NETWORK_NAME_LENGTH 255 -#define WSLC_CONTAINER_ID_LENGTH 64 -#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45 -#define WSLC_EPHEMERAL_PORT 0 -#define WSLC_MAX_SAVE_IMAGES_COUNT 256 - -cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255") -cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255") -cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255") -cpp_quote("#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255") -cpp_quote("#define WSLC_MAX_NETWORK_NAME_LENGTH 255") -cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64") -cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45") -cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256") -cpp_quote("#define WSLC_EPHEMERAL_PORT 0") -cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MAX") -cpp_quote("#define WSLC_STOP_TIMEOUT_NONE -1") - -typedef -struct _WSLCVersion { - ULONG Major; - ULONG Minor; - ULONG Revision; -} WSLCVersion; - -[ - uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801), - pointer_default(unique), - object -] -interface ICrashDumpCallback : IUnknown -{ - HRESULT OnCrashDump( - [in, string] LPCWSTR DumpPath, - [in, unique, string] LPCSTR ProcessName, - [in] ULONG Pid, - [in] ULONG Signal, - [in] ULONGLONG Timestamp); -}; - -[ - uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1), - pointer_default(unique), - object -] -interface IProgressCallback : IUnknown -{ - HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total); -}; - -[ - uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3), - pointer_default(unique), - object -] -interface IWarningCallback : IUnknown -{ - HRESULT OnWarning([in, string] LPCWSTR Message); -}; - -[ - uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2), - pointer_default(unique), - object -] -interface IWSLCPluginNotifier : IUnknown -{ - // 'InspectJson' follows the wslc_schema::InspectContainer format. - // Returning failure prevents the container creation. - HRESULT OnContainerStarted([in] LPCSTR InspectJson); - - // Called when a container is about to stop. 'ContainerId' is the container identifier. Errors are logged but ignored. - HRESULT OnContainerStopping([in] LPCSTR ContainerId); - - // 'InspectJson' follows the wslc_schema::InspectImage format. Errors are logged but ignored. - HRESULT OnImageCreated([in] LPCSTR InspectJson); - - // Called when an image is deleted. 'ImageId' is the image identifier. Errors are logged but ignored. - HRESULT OnImageDeleted([in] LPCSTR ImageId); -}; - -typedef struct _WSLCImageInformation -{ - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; - char Hash[256]; - char Digest[256]; - LONGLONG Size; // Matches Docker's int64 image size - LONGLONG Created; // Unix timestamp - char ParentId[256]; -} WSLCImageInformation; - -typedef struct _KeyValuePairInformation -{ - [string] LPSTR Key; - [string] LPSTR Value; -} KeyValuePairInformation; - -typedef struct _KeyValuePair -{ - [string] LPCSTR Key; - [string] LPCSTR Value; -} KeyValuePair; - -typedef KeyValuePair WSLCLabel; -typedef KeyValuePair WSLCDriverOption; -typedef KeyValuePair WSLCFilter; - -typedef KeyValuePairInformation WSLCLabelInformation; -typedef KeyValuePairInformation WSLCDriverOptionInformation; - -typedef struct _WSLCListImagesOptions -{ - DWORD Flags; // WSLCListImagesFlags (can combine with bitwise OR) - [unique, size_is(FiltersCount)] const WSLCFilter* Filters; - ULONG FiltersCount; -} WSLCListImagesOptions; - -typedef struct _WSLCStringArray -{ - [unique, size_is(Count)] LPCSTR const* Values; - ULONG Count; -} WSLCStringArray; - -typedef struct _WSLCProcessOptions -{ - [unique] LPCSTR CurrentDirectory; - [unique] LPCSTR User; - WSLCStringArray CommandLine; - WSLCStringArray Environment; - WSLCProcessFlags Flags; -} WSLCProcessOptions; - -typedef struct _WSLCProcessStartOptions -{ - ULONG TtyRows; // Only needed when tty fd's are passed. - ULONG TtyColumns; - [unique, string] LPCSTR DetachKeys; -} WSLCProcessStartOptions; - -typedef struct _WSLCNamedVolume -{ - LPCSTR Name; - LPCSTR ContainerPath; - BOOL ReadOnly; -} WSLCNamedVolume; - -typedef struct _WSLCVolume -{ - LPCWSTR HostPath; - LPCSTR ContainerPath; - BOOL ReadOnly; -} WSLCVolume; - -typedef struct _WSLCPortMapping -{ - USHORT HostPort; - USHORT ContainerPort; - int Family; - int Protocol; - char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1]; -} WSLCPortMapping; - -typedef struct _WSLCTmpfsMount -{ - LPCSTR Destination; - [unique] LPCSTR Options; -} WSLCTmpfsMount; - -typedef struct _WSLCUlimit -{ - [string] LPCSTR Name; - LONGLONG Soft; - LONGLONG Hard; -} WSLCUlimit; - -typedef struct _WSLCNetworkConnection -{ - [string] LPCSTR NetworkName; - [unique, size_is(SettingsCount)] const KeyValuePair* Settings; - ULONG SettingsCount; -} WSLCNetworkConnection; - -// Options for IWSLCContainer::ConnectToNetwork. -typedef struct _WSLCNetworkConnectionOptions -{ - [unique] LPCSTR NetworkName; - [unique] LPCSTR ContainerIpAddress; // Reserved for future --ip support; must be NULL today. -} WSLCNetworkConnectionOptions; - -typedef struct _WSLCContainerNetwork -{ - [unique, string] LPCSTR NetworkMode; - - [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks; - ULONG NetworksCount; - - // Settings for the primary endpoint (the network identified by NetworkMode). - // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries). - [unique, size_is(SettingsCount)] const KeyValuePair* Settings; - ULONG SettingsCount; -} WSLCContainerNetwork; - -typedef struct _WSLCContainerOptions -{ - LPCSTR Image; - [unique] LPCSTR Name; - WSLCStringArray Entrypoint; - WSLCProcessOptions InitProcessOptions; - [unique, size_is(VolumesCount)] WSLCVolume* Volumes; - ULONG VolumesCount; - [unique, size_is(PortsCount)] WSLCPortMapping* Ports; - ULONG PortsCount; - [unique, size_is(LabelsCount)] const WSLCLabel* Labels; - ULONG LabelsCount; - WSLCContainerFlags Flags; - WSLCSignal StopSignal; - // TODO: List specific GPU devices. - [unique] LPCSTR HostName; - [unique] LPCSTR DomainName; - - WSLCStringArray DnsServers; - WSLCStringArray DnsSearchDomains; - WSLCStringArray DnsOptions; - - LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus - WSLCContainerNetwork ContainerNetwork; - [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs; - ULONG TmpfsCount; - - [unique, size_is(NamedVolumesCount)] WSLCNamedVolume* NamedVolumes; - ULONG NamedVolumesCount; - - LONGLONG MemoryBytes; - LONGLONG NanoCpus; - [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits; - ULONG UlimitsCount; - - // Timeout in seconds to wait for the container to stop before killing it. - // WSLC_STOP_TIMEOUT_DEFAULT indicates the timeout was not specified, so the - // container runtime default is used. WSLC_STOP_TIMEOUT_NONE (-1) means no timeout - // (wait indefinitely). Any other negative value is rejected. - LONG StopTimeout; -} WSLCContainerOptions; - -typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ; - -typedef struct _WSLCContainerEntry -{ - char Name[WSLC_MAX_CONTAINER_NAME_LENGTH + 1]; - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; - WSLCContainerId Id; - ULONGLONG StateChangedAt; - ULONGLONG CreatedAt; - WSLCContainerState State; -} WSLCContainerEntry; - -typedef struct _WSLCContainerPortMapping -{ - WSLCContainerId Id; - WSLCPortMapping PortMapping; -} WSLCContainerPortMapping; - -typedef [system_handle(sh_file)] HANDLE FILE_HANDLE; -typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE; -typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE; - -typedef struct _WSLCHandle -{ - WSLCHandleType Type; - - [switch_type(WSLCHandleType), switch_is(Type)] - union - { - [case(WSLCHandleTypeFile)] - FILE_HANDLE File; - [case(WSLCHandleTypePipe)] - PIPE_HANDLE Pipe; - [case(WSLCHandleTypeSocket)] - SOCKET_HANDLE Socket; - [default]; - } Handle; -} WSLCHandle; - -[ - uuid(1AD163CD-393D-4B33-83A2-8A3F3F23E608), - pointer_default(unique), - object -] -interface IWSLCProcess : IUnknown -{ - HRESULT Signal([in] int Signal); - HRESULT GetExitEvent([out, system_handle(sh_event)] HANDLE* EventHandle); - HRESULT GetStdHandle([in] WSLCFD Fd, [out] WSLCHandle* Handle); - HRESULT GetFlags([out] WSLCProcessFlags* Flags); - HRESULT GetPid([out] int* Pid); - HRESULT GetState([out] WSLCProcessState* State, [out] int* Code); - HRESULT ResizeTty([in] ULONG Rows, [in] ULONG Columns); - - // Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API. -} - -// -// Values discovered from the guest kernel after the VM has booted, forwarded -// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new -// fields here instead of new IDL methods so the interface does not need a new -// IID for each kernel-published value. -// -typedef struct _WSLCGuestCapabilities -{ - // (base, size) of the hv_pci swiotlb pool the kernel reserved and - // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}. - // Both zero means the running kernel does not support hv_pci swiotlb. - UINT64 HvPciSwiotlbBase; - UINT64 HvPciSwiotlbSize; -} WSLCGuestCapabilities; - -// -// IWSLCVirtualMachine - Interface representing a single VM instance. -// Operations are scoped to this VM. The VM ID is stored internally, -// so only the holder of this interface can operate on the VM. -// -[ - uuid(B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A), - pointer_default(unique), - object -] -interface IWSLCVirtualMachine : IUnknown -{ - // Gets the VM ID. - HRESULT GetId([out, retval] GUID* VmId); - - // Accepts a connect from mini_init in the VM. - HRESULT AcceptConnection([out, system_handle(sh_socket)] HANDLE* Socket); - - // Configures networking engine with sockets from the user process. - // GnsSocket is required; DnsSocket is optional (NULL if DNS tunneling is disabled). - // The service duplicates the socket handles. - HRESULT ConfigureNetworking( - [in, system_handle(sh_socket)] HANDLE GnsSocket, - [in, system_handle(sh_socket), unique] HANDLE* DnsSocket); - - // Attaches a VHD or VHDX disk to the VM. - // GrantVmAccess is called by the service before attaching. - // Returns the SCSI LUN assigned to the disk. - HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out, retval] ULONG* Lun); - - // Detaches a previously attached disk from the VM. - HRESULT DetachDisk([in] ULONG Lun); - - // Adds a filesystem share (Plan9 or VirtioFS) accessible to the VM. - // Returns an instance GUID that can be used to remove the share. - HRESULT AddShare([in] LPCWSTR WindowsPath, [in] BOOL ReadOnly, [out, retval] GUID* ShareId); - - // Removes a previously added filesystem share. - HRESULT RemoveShare([in] REFGUID ShareId); - - // Configures the per-VM state discovered from the guest kernel after boot - // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and - // ConfigureNetworking forward these values to wsldevicehost via the - // swiotlb device-options token. A capabilities struct whose fields are - // all zero means the guest kernel does not support the feature; the - // token is then omitted. - HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities); - - // Returns an event that is signaled when the VM exits (graceful or forced). - HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); - - HRESULT MapVirtioNetPort( - [in] USHORT HostPort, - [in] USHORT GuestPort, - [in] int Protocol, - [in] LPCSTR ListenAddress, - [out, retval] USHORT* AllocatedHostPort); - - // Unmaps a port previously mapped via MapVirtioNetPort. - HRESULT UnmapVirtioNetPort( - [in] USHORT HostPort, - [in] USHORT GuestPort, - [in] int Protocol, - [in] LPCSTR ListenAddress); - - // Returns the cached termination reason and details. These are only available after the - // termination event has been signaled; before that the call fails. - HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); -} - -// -// IWSLCVirtualMachineFactory - Creates VMs on demand for a session. -// -// Held by the per-user session process and implemented by the SYSTEM service. -// This lets the session create a fresh VM at any time (e.g. to recreate a VM that -// was idle-terminated when it had no running containers), instead of the service -// eagerly creating a single VM up front. Each successful call returns a new VM whose -// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down. -// -[ - uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B), - pointer_default(unique), - object -] -interface IWSLCVirtualMachineFactory : IUnknown -{ - // Creates a new VM using the settings captured at session creation time. - HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm); -} - -// Settings for IWSLCSessionManager::CreateSession - full session configuration -typedef struct _WSLCSessionSettings { - LPCWSTR DisplayName; - LPCWSTR StoragePath; - ULONGLONG MaximumStorageSizeMb; - ULONG CpuCount; - ULONG MemoryMb; - ULONG BootTimeoutMs; - WSLCNetworkingMode NetworkingMode; - WSLCFeatureFlags FeatureFlags; - WSLCHandle DmesgOutput; - WSLCSessionStorageFlags StorageFlags; - - // Below options are used for debugging purposes only. - [unique] LPCWSTR RootVhdOverride; - [unique] LPCSTR RootVhdTypeOverride; -} WSLCSessionSettings; - - -[ - uuid(7577FE8D-DE85-471E-B870-11669986F332), - pointer_default(unique), - object -] -interface IWSLCContainer : IUnknown -{ - HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr); - HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds); - HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] const WSLCProcessStartOptions* StartOptions, [in, unique] IWarningCallback* WarningCallback); - HRESULT Delete([in] WSLCDeleteFlags Flags); - HRESULT Export([in] WSLCHandle TarHandle); - HRESULT GetState([out] WSLCContainerState* State); - HRESULT GetInitProcess([out] IWSLCProcess** Process); - HRESULT Exec([in, ref] const WSLCProcessOptions* Options, [in, unique] const WSLCProcessStartOptions* StartOptions, [out] IWSLCProcess** Process); - HRESULT Inspect([out] LPSTR* Output); - HRESULT Logs([in] WSLCLogsFlags Flags, [out] WSLCHandle* Stdout, [out] WSLCHandle* Stderr, [in] ULONGLONG Since, [in] ULONGLONG Until, [in] ULONGLONG Tail); - HRESULT GetId([out, string] WSLCContainerId Id); - HRESULT GetName([out, string] LPSTR* Name); - HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count); - HRESULT Kill([in] WSLCSignal Signal); - HRESULT Stats([out] LPSTR* Output); - HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options); - HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName); -} - -typedef struct _WSLCDeletedImageInformation -{ - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; - WSLCDeletedImageType Type; -} WSLCDeletedImageInformation; - -typedef struct _WSLCDeleteImageOptions -{ - LPCSTR Image; // Image can be ID or Repo:Tag. - DWORD Flags; // WSLCDeleteImageFlags - // TODO: Platforms: a json array of OCI platform strings. -} WSLCDeleteImageOptions; - -typedef struct _WSLCBuildImageOptions -{ - LPCWSTR ContextPath; - WSLCHandle DockerfileHandle; - WSLCStringArray Tags; - WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker. - LPCSTR Target; // Target build stage name passed as --target to docker. - WSLCBuildImageFlags Flags; // WSLCBuildImageFlags - WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker. -} WSLCBuildImageOptions; - -typedef struct _WSLCTagImageOptions -{ - LPCSTR Image; // Source image name or ID. - LPCSTR Repo; // Target repository name. - LPCSTR Tag; // Target tag name. -} WSLCTagImageOptions; - -typedef struct _WSLCVolumeOptions -{ - [unique] LPCSTR Name; - [unique] LPCSTR Driver; - [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; - ULONG DriverOptsCount; - [unique, size_is(LabelsCount)] const WSLCLabel* Labels; - ULONG LabelsCount; -} WSLCVolumeOptions; - -typedef char WSLCVolumeName[WSLC_MAX_VOLUME_NAME_LENGTH + 1]; - -typedef struct _WSLCVolumeInformation -{ - WSLCVolumeName Name; - char Driver[WSLC_MAX_VOLUME_DRIVER_LENGTH + 1]; -} WSLCVolumeInformation; - -typedef struct _WSLCNetworkOptions -{ - LPCSTR Name; - [unique] LPCSTR Driver; - [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; - ULONG DriverOptsCount; - [unique, size_is(LabelsCount)] const WSLCLabel* Labels; - ULONG LabelsCount; -} WSLCNetworkOptions; - -typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; - -typedef struct _WSLCNetworkInformation -{ - char Name[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; - char Id[WSLC_CONTAINER_ID_LENGTH + 1]; - char Driver[64]; -} WSLCNetworkInformation; - -typedef struct _WSLCPruneContainersResults -{ - [unique, size_is(ContainersCount)] WSLCContainerId* Containers; - ULONG ContainersCount; - ULONGLONG SpaceReclaimed; -} WSLCPruneContainersResults; - -typedef struct _WSLCListContainersOptions -{ - DWORD Flags; // WSLCListContainersFlags - LONG Limit; - - [unique, size_is(FiltersCount)] const WSLCFilter* Filters; - ULONG FiltersCount; -} WSLCListContainersOptions; - -// Settings for IWSLCSession::Initialize - passed from service to per-user process -typedef struct _WSLCSessionInitSettings -{ - ULONG SessionId; - [unique] LPCWSTR CreatorProcessName; - LPCWSTR DisplayName; - LPCWSTR StoragePath; - WSLCSessionStorageFlags StorageFlags; - ULONGLONG MaximumStorageSizeMb; - ULONG SwapSizeMb; - ULONG BootTimeoutMs; - WSLCNetworkingMode NetworkingMode; - WSLCFeatureFlags FeatureFlags; - [unique] LPCSTR RootVhdTypeOverride; -} WSLCSessionInitSettings; - -[ - uuid(EF0661E4-6364-40EA-B433-E2FDF11F3519), - pointer_default(unique), - object -] -interface IWSLCSession : IUnknown -{ - HRESULT GetId([out] ULONG* Id); - HRESULT GetDisplayName([out] LPWSTR* DisplayName); - HRESULT GetState([out] WSLCSessionState* State); - - // Returns a one-off event that is signaled when the session terminates, whether due to an - // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the - // caller and remains valid (and observes the signaled state) even after the session is released. - HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); - - // Returns the cached termination reason and details. These are only available after the - // termination event has been signaled; before that the call fails. - HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); - - // Image management. - HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); - HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); - HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback); - HRESULT ImportImage([in] WSLCHandle ImageHandle, [in, unique] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback, [out] LPSTR* ImageId); - HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); - HRESULT SaveImages([in] WSLCHandle OutputHandle, [in] const WSLCStringArray* ImageNames, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); - HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count); - HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count); - HRESULT TagImage([in] const WSLCTagImageOptions* Options); - HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output); - HRESULT PruneImages([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *DeletedImagesCount)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* DeletedImagesCount, [out] ULONGLONG* SpaceReclaimed); - - // Container management. - HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container); - HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container); - HRESULT ListContainers([in, unique] const WSLCListContainersOptions* Options,[out, size_is(, *Count)] WSLCContainerEntry** Containers,[out] ULONG* Count, [out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports, [out] ULONG* PortsCount); - HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result); - - // Create a process at the VM level. This is meant for debugging. - HRESULT CreateRootNamespaceProcess([in, ref] LPCSTR Executable, [in, ref] const WSLCProcessOptions* Options, [in] ULONG TtyRows, [in] ULONG TtyColumns, [out] IWSLCProcess** Process, [out] int* Errno); - - // TODO: an OpenProcess() method can be added later if needed. - - // Disk management. - HRESULT FormatVirtualDisk([in, ref] LPCWSTR Path); - - // Terminate the VM and containers. - HRESULT Terminate(); - - // Used only for testing. TODO: Think about moving them to a dedicated testing-only interface. - HRESULT MountWindowsFolder([in, ref] LPCWSTR WindowsPath, [in, ref] LPCSTR LinuxPath, [in] BOOL ReadOnly); - HRESULT UnmountWindowsFolder([in, ref] LPCSTR LinuxPath); - HRESULT MapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); - HRESULT UnmapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); - - // Session initialization - called by SYSTEM service after launching per-user process. - // Returns a handle to this COM server process (used to add to job object). - HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); - - // Initializes the session with a VM factory. VMs are created through the factory. - HRESULT Initialize( - [in] const WSLCSessionInitSettings* Settings, - [in] IWSLCVirtualMachineFactory* VmFactory, - [in] IWSLCPluginNotifier* PluginNotifier, - [in, unique] IWarningCallback* WarningCallback); - - // Volume management. - HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo); - HRESULT DeleteVolume([in] LPCSTR Name); - HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count); - HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output); - - HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken); - HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); - HRESULT PruneVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [in, unique] IWarningCallback* WarningCallback, [out, size_is(, *VolumesCount)] WSLCVolumeName** Volumes, [out] ULONG* VolumesCount, [out] ULONGLONG* SpaceReclaimed); - - // Network management. - HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback); - HRESULT DeleteNetwork([in] LPCSTR Name); - HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count); - HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output); - HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount); - - HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription); -} - -// -// IWSLCSessionReference - Weak reference to a session held by the SYSTEM service. -// Stored in per-user process, allows service to check liveness and terminate sessions. -// Session metadata (ID, name, etc.) is stored service-side in SessionEntry. -// -[ - uuid(B3A72F48-9D15-4E8A-A621-7C3E84F09B52), - pointer_default(unique), - object -] -interface IWSLCSessionReference : IUnknown -{ - // Try to open the session. Fails if session was released or terminated. - // Returns S_OK and a valid session if still alive. - HRESULT OpenSession([out] IWSLCSession** Session); - - // Terminate the session if still alive. - HRESULT Terminate(); -} - -// -// IWSLCSessionFactory - Creates sessions in the per-user COM server process. -// Called by the SYSTEM service via CoCreateInstanceAsUser. -// -[ - uuid(C4E8F291-3B5D-4A7C-9E12-8F6A4D2B7C91), - pointer_default(unique), - object -] -interface IWSLCSessionFactory : IUnknown -{ - // Creates a new session and returns both the session interface and a service reference. - HRESULT CreateSession( - [in] const WSLCSessionInitSettings* Settings, - [in] IWSLCVirtualMachineFactory* VmFactory, - [in] IWSLCPluginNotifier* PluginNotifier, - [in, unique] IWarningCallback* WarningCallback, - [out] IWSLCSession** Session, - [out] IWSLCSessionReference** ServiceRef); - - // Gets the process handle for adding to job object. - HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); -} - -typedef struct _WSLCSessionListEntry -{ - ULONG SessionId; - DWORD CreatorPid; - wchar_t DisplayName[256]; - wchar_t Sid[256 + 1]; // MAX_SID_SIZE = 256 -} WSLCSessionListEntry; - -[ - uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760), - pointer_default(unique), - object -] -interface IWSLCSessionManager : IUnknown -{ - HRESULT GetVersion([out] WSLCVersion* Version); - // Session management. - HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); - HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); - HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount); - HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session); - HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session); -} - -// Ensure wslcsdk.h and wslcsdk.idl are also updated. -cpp_quote("#define WSLC_E_BASE (0x0600)") -cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */") -cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */") -cpp_quote("#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */") -cpp_quote("#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */") -cpp_quote("#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */") -cpp_quote("#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */") -cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */") -cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */") -cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */") -cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */") -cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */") -cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */") -cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */") -cpp_quote("#define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */") -cpp_quote("#define WSLC_E_SESSION_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 15) /* 0x8004060F */") +/*++ + +Copyright (c) Microsoft Corporation. All rights reserved. + +Module Name: + + wslc.idl + +Abstract: + + This file contains the WSLC-related COM object definitions. + // N.B. ABI breaking changes in this file are OK, since both client & server always ship together. + // The WSLC SDK must not use this file, and instead use WSLCCompat.idl + +--*/ + +import "unknwn.idl"; +import "wtypes.idl"; + +// Enums and flags shared with the SDK-facing API (WSLCCompat.idl). +import "WSLCShared.idl"; + +cpp_quote("#ifdef __cplusplus") +cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLCSessionManager;") +cpp_quote("class DECLSPEC_UUID(\"9fcd2067-9fc6-4efa-9eb0-698169ebf7d3\") WSLCSessionFactory;") +cpp_quote("#endif") + +#define WSLC_MAX_CONTAINER_NAME_LENGTH 255 +#define WSLC_MAX_IMAGE_NAME_LENGTH 255 +#define WSLC_MAX_VOLUME_NAME_LENGTH 255 +#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255 +#define WSLC_MAX_NETWORK_NAME_LENGTH 255 +#define WSLC_CONTAINER_ID_LENGTH 64 +#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45 +#define WSLC_EPHEMERAL_PORT 0 +#define WSLC_MAX_SAVE_IMAGES_COUNT 256 + +cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255") +cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255") +cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255") +cpp_quote("#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255") +cpp_quote("#define WSLC_MAX_NETWORK_NAME_LENGTH 255") +cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64") +cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45") +cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256") +cpp_quote("#define WSLC_EPHEMERAL_PORT 0") +cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MAX") // use the default container stop timeout (10 seconds). +cpp_quote("#define WSLC_STOP_TIMEOUT_NONE -1") // Wait forever for the container to stop. + +typedef +struct _WSLCVersion { + ULONG Major; + ULONG Minor; + ULONG Revision; +} WSLCVersion; + +[ + uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801), + pointer_default(unique), + object +] +interface ICrashDumpCallback : IUnknown +{ + HRESULT OnCrashDump( + [in, string] LPCWSTR DumpPath, + [in, unique, string] LPCSTR ProcessName, + [in] ULONG Pid, + [in] ULONG Signal, + [in] ULONGLONG Timestamp); +}; + +[ + uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1), + pointer_default(unique), + object +] +interface IProgressCallback : IUnknown +{ + HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total); +}; + +[ + uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3), + pointer_default(unique), + object +] +interface IWarningCallback : IUnknown +{ + HRESULT OnWarning([in, string] LPCWSTR Message); +}; + +[ + uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2), + pointer_default(unique), + object +] +interface IWSLCPluginNotifier : IUnknown +{ + // 'InspectJson' follows the wslc_schema::InspectContainer format. + // Returning failure prevents the container creation. + HRESULT OnContainerStarted([in] LPCSTR InspectJson); + + // Called when a container is about to stop. 'ContainerId' is the container identifier. Errors are logged but ignored. + HRESULT OnContainerStopping([in] LPCSTR ContainerId); + + // 'InspectJson' follows the wslc_schema::InspectImage format. Errors are logged but ignored. + HRESULT OnImageCreated([in] LPCSTR InspectJson); + + // Called when an image is deleted. 'ImageId' is the image identifier. Errors are logged but ignored. + HRESULT OnImageDeleted([in] LPCSTR ImageId); +}; + +typedef struct _WSLCImageInformation +{ + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; + char Hash[256]; + char Digest[256]; + LONGLONG Size; // Matches Docker's int64 image size + LONGLONG Created; // Unix timestamp + char ParentId[256]; +} WSLCImageInformation; + +typedef struct _KeyValuePairInformation +{ + [string] LPSTR Key; + [string] LPSTR Value; +} KeyValuePairInformation; + +typedef struct _KeyValuePair +{ + [string] LPCSTR Key; + [string] LPCSTR Value; +} KeyValuePair; + +typedef KeyValuePair WSLCLabel; +typedef KeyValuePair WSLCDriverOption; +typedef KeyValuePair WSLCFilter; + +typedef KeyValuePairInformation WSLCLabelInformation; +typedef KeyValuePairInformation WSLCDriverOptionInformation; + +typedef struct _WSLCListImagesOptions +{ + DWORD Flags; // WSLCListImagesFlags (can combine with bitwise OR) + [unique, size_is(FiltersCount)] const WSLCFilter* Filters; + ULONG FiltersCount; +} WSLCListImagesOptions; + +typedef struct _WSLCStringArray +{ + [unique, size_is(Count)] LPCSTR const* Values; + ULONG Count; +} WSLCStringArray; + +typedef struct _WSLCProcessOptions +{ + [unique] LPCSTR CurrentDirectory; + [unique] LPCSTR User; + WSLCStringArray CommandLine; + WSLCStringArray Environment; + WSLCProcessFlags Flags; +} WSLCProcessOptions; + +typedef struct _WSLCProcessStartOptions +{ + ULONG TtyRows; // Only needed when tty fd's are passed. + ULONG TtyColumns; + [unique, string] LPCSTR DetachKeys; +} WSLCProcessStartOptions; + +typedef struct _WSLCNamedVolume +{ + LPCSTR Name; + LPCSTR ContainerPath; + BOOL ReadOnly; +} WSLCNamedVolume; + +typedef struct _WSLCVolume +{ + LPCWSTR HostPath; + LPCSTR ContainerPath; + BOOL ReadOnly; +} WSLCVolume; + +typedef struct _WSLCPortMapping +{ + USHORT HostPort; + USHORT ContainerPort; + int Family; + int Protocol; + char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1]; +} WSLCPortMapping; + +typedef struct _WSLCTmpfsMount +{ + LPCSTR Destination; + [unique] LPCSTR Options; +} WSLCTmpfsMount; + +typedef struct _WSLCUlimit +{ + [string] LPCSTR Name; + LONGLONG Soft; + LONGLONG Hard; +} WSLCUlimit; + +typedef struct _WSLCNetworkConnection +{ + [string] LPCSTR NetworkName; + [unique, size_is(SettingsCount)] const KeyValuePair* Settings; + ULONG SettingsCount; +} WSLCNetworkConnection; + +// Options for IWSLCContainer::ConnectToNetwork. +typedef struct _WSLCNetworkConnectionOptions +{ + [unique] LPCSTR NetworkName; + [unique] LPCSTR ContainerIpAddress; // Reserved for future --ip support; must be NULL today. +} WSLCNetworkConnectionOptions; + +typedef struct _WSLCContainerNetwork +{ + [unique, string] LPCSTR NetworkMode; + + [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks; + ULONG NetworksCount; + + // Settings for the primary endpoint (the network identified by NetworkMode). + // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries). + [unique, size_is(SettingsCount)] const KeyValuePair* Settings; + ULONG SettingsCount; +} WSLCContainerNetwork; + +typedef struct _WSLCContainerOptions +{ + LPCSTR Image; + [unique] LPCSTR Name; + WSLCStringArray Entrypoint; + WSLCProcessOptions InitProcessOptions; + [unique, size_is(VolumesCount)] WSLCVolume* Volumes; + ULONG VolumesCount; + [unique, size_is(PortsCount)] WSLCPortMapping* Ports; + ULONG PortsCount; + [unique, size_is(LabelsCount)] const WSLCLabel* Labels; + ULONG LabelsCount; + WSLCContainerFlags Flags; + WSLCSignal StopSignal; + // TODO: List specific GPU devices. + [unique] LPCSTR HostName; + [unique] LPCSTR DomainName; + + WSLCStringArray DnsServers; + WSLCStringArray DnsSearchDomains; + WSLCStringArray DnsOptions; + + LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus + WSLCContainerNetwork ContainerNetwork; + [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs; + ULONG TmpfsCount; + + [unique, size_is(NamedVolumesCount)] WSLCNamedVolume* NamedVolumes; + ULONG NamedVolumesCount; + + LONGLONG MemoryBytes; + LONGLONG NanoCpus; + [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits; + ULONG UlimitsCount; + + // Timeout in seconds to wait for the container to stop before killing it. + // WSLC_STOP_TIMEOUT_DEFAULT indicates the timeout was not specified, so the + // container runtime default is used. WSLC_STOP_TIMEOUT_NONE (-1) means no timeout + // (wait indefinitely). Any other negative value is rejected. + LONG StopTimeout; +} WSLCContainerOptions; + +typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ; + +typedef struct _WSLCContainerEntry +{ + char Name[WSLC_MAX_CONTAINER_NAME_LENGTH + 1]; + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; + WSLCContainerId Id; + ULONGLONG StateChangedAt; + ULONGLONG CreatedAt; + WSLCContainerState State; +} WSLCContainerEntry; + +typedef struct _WSLCContainerPortMapping +{ + WSLCContainerId Id; + WSLCPortMapping PortMapping; +} WSLCContainerPortMapping; + +typedef [system_handle(sh_file)] HANDLE FILE_HANDLE; +typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE; +typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE; + +typedef struct _WSLCHandle +{ + WSLCHandleType Type; + + [switch_type(WSLCHandleType), switch_is(Type)] + union + { + [case(WSLCHandleTypeFile)] + FILE_HANDLE File; + [case(WSLCHandleTypePipe)] + PIPE_HANDLE Pipe; + [case(WSLCHandleTypeSocket)] + SOCKET_HANDLE Socket; + [default]; + } Handle; +} WSLCHandle; + +[ + uuid(1AD163CD-393D-4B33-83A2-8A3F3F23E608), + pointer_default(unique), + object +] +interface IWSLCProcess : IUnknown +{ + HRESULT Signal([in] int Signal); + HRESULT GetExitEvent([out, system_handle(sh_event)] HANDLE* EventHandle); + HRESULT GetStdHandle([in] WSLCFD Fd, [out] WSLCHandle* Handle); + HRESULT GetFlags([out] WSLCProcessFlags* Flags); + HRESULT GetPid([out] int* Pid); + HRESULT GetState([out] WSLCProcessState* State, [out] int* Code); + HRESULT ResizeTty([in] ULONG Rows, [in] ULONG Columns); + + // Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API. +} + +// +// Values discovered from the guest kernel after the VM has booted, forwarded +// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new +// fields here instead of new IDL methods so the interface does not need a new +// IID for each kernel-published value. +// +typedef struct _WSLCGuestCapabilities +{ + // (base, size) of the hv_pci swiotlb pool the kernel reserved and + // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}. + // Both zero means the running kernel does not support hv_pci swiotlb. + UINT64 HvPciSwiotlbBase; + UINT64 HvPciSwiotlbSize; +} WSLCGuestCapabilities; + +// +// IWSLCVirtualMachine - Interface representing a single VM instance. +// Operations are scoped to this VM. The VM ID is stored internally, +// so only the holder of this interface can operate on the VM. +// +[ + uuid(B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A), + pointer_default(unique), + object +] +interface IWSLCVirtualMachine : IUnknown +{ + // Gets the VM ID. + HRESULT GetId([out, retval] GUID* VmId); + + // Accepts a connect from mini_init in the VM. + HRESULT AcceptConnection([out, system_handle(sh_socket)] HANDLE* Socket); + + // Configures networking engine with sockets from the user process. + // GnsSocket is required; DnsSocket is optional (NULL if DNS tunneling is disabled). + // The service duplicates the socket handles. + HRESULT ConfigureNetworking( + [in, system_handle(sh_socket)] HANDLE GnsSocket, + [in, system_handle(sh_socket), unique] HANDLE* DnsSocket); + + // Attaches a VHD or VHDX disk to the VM. + // GrantVmAccess is called by the service before attaching. + // Returns the SCSI LUN assigned to the disk. + HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out, retval] ULONG* Lun); + + // Detaches a previously attached disk from the VM. + HRESULT DetachDisk([in] ULONG Lun); + + // Adds a filesystem share (Plan9 or VirtioFS) accessible to the VM. + // Returns an instance GUID that can be used to remove the share. + HRESULT AddShare([in] LPCWSTR WindowsPath, [in] BOOL ReadOnly, [out, retval] GUID* ShareId); + + // Removes a previously added filesystem share. + HRESULT RemoveShare([in] REFGUID ShareId); + + // Configures the per-VM state discovered from the guest kernel after boot + // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and + // ConfigureNetworking forward these values to wsldevicehost via the + // swiotlb device-options token. A capabilities struct whose fields are + // all zero means the guest kernel does not support the feature; the + // token is then omitted. + HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities); + + // Returns an event that is signaled when the VM exits (graceful or forced). + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); + + HRESULT MapVirtioNetPort( + [in] USHORT HostPort, + [in] USHORT GuestPort, + [in] int Protocol, + [in] LPCSTR ListenAddress, + [out, retval] USHORT* AllocatedHostPort); + + // Unmaps a port previously mapped via MapVirtioNetPort. + HRESULT UnmapVirtioNetPort( + [in] USHORT HostPort, + [in] USHORT GuestPort, + [in] int Protocol, + [in] LPCSTR ListenAddress); + + // Returns the cached termination reason and details. These are only available after the + // termination event has been signaled; before that the call fails. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); +} + +// +// IWSLCVirtualMachineFactory - Creates VMs on demand for a session. +// +// Held by the per-user session process and implemented by the SYSTEM service. +// This lets the session create a fresh VM at any time (e.g. to recreate a VM that +// was idle-terminated when it had no running containers), instead of the service +// eagerly creating a single VM up front. Each successful call returns a new VM whose +// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down. +// +[ + uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B), + pointer_default(unique), + object +] +interface IWSLCVirtualMachineFactory : IUnknown +{ + // Creates a new VM using the settings captured at session creation time. + HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm); +} + +// Settings for IWSLCSessionManager::CreateSession - full session configuration +typedef struct _WSLCSessionSettings { + LPCWSTR DisplayName; + LPCWSTR StoragePath; + ULONGLONG MaximumStorageSizeMb; + ULONG CpuCount; + ULONG MemoryMb; + ULONG BootTimeoutMs; + WSLCNetworkingMode NetworkingMode; + WSLCFeatureFlags FeatureFlags; + WSLCHandle DmesgOutput; + WSLCSessionStorageFlags StorageFlags; + + // Below options are used for debugging purposes only. + [unique] LPCWSTR RootVhdOverride; + [unique] LPCSTR RootVhdTypeOverride; +} WSLCSessionSettings; + + +[ + uuid(7577FE8D-DE85-471E-B870-11669986F332), + pointer_default(unique), + object +] +interface IWSLCContainer : IUnknown +{ + HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr); + HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds); + HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] const WSLCProcessStartOptions* StartOptions, [in, unique] IWarningCallback* WarningCallback); + HRESULT Delete([in] WSLCDeleteFlags Flags); + HRESULT Export([in] WSLCHandle TarHandle); + HRESULT GetState([out] WSLCContainerState* State); + HRESULT GetInitProcess([out] IWSLCProcess** Process); + HRESULT Exec([in, ref] const WSLCProcessOptions* Options, [in, unique] const WSLCProcessStartOptions* StartOptions, [out] IWSLCProcess** Process); + HRESULT Inspect([out] LPSTR* Output); + HRESULT Logs([in] WSLCLogsFlags Flags, [out] WSLCHandle* Stdout, [out] WSLCHandle* Stderr, [in] ULONGLONG Since, [in] ULONGLONG Until, [in] ULONGLONG Tail); + HRESULT GetId([out, string] WSLCContainerId Id); + HRESULT GetName([out, string] LPSTR* Name); + HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count); + HRESULT Kill([in] WSLCSignal Signal); + HRESULT Stats([out] LPSTR* Output); + HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options); + HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName); +} + +typedef struct _WSLCDeletedImageInformation +{ + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1]; + WSLCDeletedImageType Type; +} WSLCDeletedImageInformation; + +typedef struct _WSLCDeleteImageOptions +{ + LPCSTR Image; // Image can be ID or Repo:Tag. + DWORD Flags; // WSLCDeleteImageFlags + // TODO: Platforms: a json array of OCI platform strings. +} WSLCDeleteImageOptions; + +typedef struct _WSLCBuildImageOptions +{ + LPCWSTR ContextPath; + WSLCHandle DockerfileHandle; + WSLCStringArray Tags; + WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker. + LPCSTR Target; // Target build stage name passed as --target to docker. + WSLCBuildImageFlags Flags; // WSLCBuildImageFlags + WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker. +} WSLCBuildImageOptions; + +typedef struct _WSLCTagImageOptions +{ + LPCSTR Image; // Source image name or ID. + LPCSTR Repo; // Target repository name. + LPCSTR Tag; // Target tag name. +} WSLCTagImageOptions; + +typedef struct _WSLCVolumeOptions +{ + [unique] LPCSTR Name; + [unique] LPCSTR Driver; + [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; + ULONG DriverOptsCount; + [unique, size_is(LabelsCount)] const WSLCLabel* Labels; + ULONG LabelsCount; +} WSLCVolumeOptions; + +typedef char WSLCVolumeName[WSLC_MAX_VOLUME_NAME_LENGTH + 1]; + +typedef struct _WSLCVolumeInformation +{ + WSLCVolumeName Name; + char Driver[WSLC_MAX_VOLUME_DRIVER_LENGTH + 1]; +} WSLCVolumeInformation; + +typedef struct _WSLCNetworkOptions +{ + LPCSTR Name; + [unique] LPCSTR Driver; + [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts; + ULONG DriverOptsCount; + [unique, size_is(LabelsCount)] const WSLCLabel* Labels; + ULONG LabelsCount; +} WSLCNetworkOptions; + +typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; + +typedef struct _WSLCNetworkInformation +{ + char Name[WSLC_MAX_NETWORK_NAME_LENGTH + 1]; + char Id[WSLC_CONTAINER_ID_LENGTH + 1]; + char Driver[64]; +} WSLCNetworkInformation; + +typedef struct _WSLCPruneContainersResults +{ + [unique, size_is(ContainersCount)] WSLCContainerId* Containers; + ULONG ContainersCount; + ULONGLONG SpaceReclaimed; +} WSLCPruneContainersResults; + +typedef struct _WSLCListContainersOptions +{ + DWORD Flags; // WSLCListContainersFlags + LONG Limit; + + [unique, size_is(FiltersCount)] const WSLCFilter* Filters; + ULONG FiltersCount; +} WSLCListContainersOptions; + +// Settings for IWSLCSession::Initialize - passed from service to per-user process +typedef struct _WSLCSessionInitSettings +{ + ULONG SessionId; + [unique] LPCWSTR CreatorProcessName; + LPCWSTR DisplayName; + LPCWSTR StoragePath; + WSLCSessionStorageFlags StorageFlags; + ULONGLONG MaximumStorageSizeMb; + ULONG SwapSizeMb; + ULONG BootTimeoutMs; + WSLCNetworkingMode NetworkingMode; + WSLCFeatureFlags FeatureFlags; + [unique] LPCSTR RootVhdTypeOverride; +} WSLCSessionInitSettings; + +[ + uuid(EF0661E4-6364-40EA-B433-E2FDF11F3519), + pointer_default(unique), + object +] +interface IWSLCSession : IUnknown +{ + HRESULT GetId([out] ULONG* Id); + HRESULT GetDisplayName([out] LPWSTR* DisplayName); + HRESULT GetState([out] WSLCSessionState* State); + + // Returns a one-off event that is signaled when the session terminates, whether due to an + // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the + // caller and remains valid (and observes the signaled state) even after the session is released. + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event); + + // Returns the cached termination reason and details. These are only available after the + // termination event has been signaled; before that the call fails. + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details); + + // Image management. + HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); + HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); + HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback); + HRESULT ImportImage([in] WSLCHandle ImageHandle, [in, unique] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback, [out] LPSTR* ImageId); + HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); + HRESULT SaveImages([in] WSLCHandle OutputHandle, [in] const WSLCStringArray* ImageNames, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent); + HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count); + HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count); + HRESULT TagImage([in] const WSLCTagImageOptions* Options); + HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output); + HRESULT PruneImages([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *DeletedImagesCount)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* DeletedImagesCount, [out] ULONGLONG* SpaceReclaimed); + + // Container management. + HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container); + HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container); + HRESULT ListContainers([in, unique] const WSLCListContainersOptions* Options,[out, size_is(, *Count)] WSLCContainerEntry** Containers,[out] ULONG* Count, [out, size_is(, *PortsCount)] WSLCContainerPortMapping** Ports, [out] ULONG* PortsCount); + HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result); + + // Create a process at the VM level. This is meant for debugging. + HRESULT CreateRootNamespaceProcess([in, ref] LPCSTR Executable, [in, ref] const WSLCProcessOptions* Options, [in] ULONG TtyRows, [in] ULONG TtyColumns, [out] IWSLCProcess** Process, [out] int* Errno); + + // TODO: an OpenProcess() method can be added later if needed. + + // Disk management. + HRESULT FormatVirtualDisk([in, ref] LPCWSTR Path); + + // Terminate the VM and containers. + HRESULT Terminate(); + + // Used only for testing. TODO: Think about moving them to a dedicated testing-only interface. + HRESULT MountWindowsFolder([in, ref] LPCWSTR WindowsPath, [in, ref] LPCSTR LinuxPath, [in] BOOL ReadOnly); + HRESULT UnmountWindowsFolder([in, ref] LPCSTR LinuxPath); + HRESULT MapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); + HRESULT UnmapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort); + + // Session initialization - called by SYSTEM service after launching per-user process. + // Returns a handle to this COM server process (used to add to job object). + HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); + + // Initializes the session with a VM factory. VMs are created through the factory. + HRESULT Initialize( + [in] const WSLCSessionInitSettings* Settings, + [in] IWSLCVirtualMachineFactory* VmFactory, + [in] IWSLCPluginNotifier* PluginNotifier, + [in, unique] IWarningCallback* WarningCallback); + + // Volume management. + HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo); + HRESULT DeleteVolume([in] LPCSTR Name); + HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count); + HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output); + + HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken); + HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback); + HRESULT PruneVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [in, unique] IWarningCallback* WarningCallback, [out, size_is(, *VolumesCount)] WSLCVolumeName** Volumes, [out] ULONG* VolumesCount, [out] ULONGLONG* SpaceReclaimed); + + // Network management. + HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback); + HRESULT DeleteNetwork([in] LPCSTR Name); + HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count); + HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output); + HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount); + + HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription); +} + +// +// IWSLCSessionReference - Weak reference to a session held by the SYSTEM service. +// Stored in per-user process, allows service to check liveness and terminate sessions. +// Session metadata (ID, name, etc.) is stored service-side in SessionEntry. +// +[ + uuid(B3A72F48-9D15-4E8A-A621-7C3E84F09B52), + pointer_default(unique), + object +] +interface IWSLCSessionReference : IUnknown +{ + // Try to open the session. Fails if session was released or terminated. + // Returns S_OK and a valid session if still alive. + HRESULT OpenSession([out] IWSLCSession** Session); + + // Terminate the session if still alive. + HRESULT Terminate(); +} + +// +// IWSLCSessionFactory - Creates sessions in the per-user COM server process. +// Called by the SYSTEM service via CoCreateInstanceAsUser. +// +[ + uuid(C4E8F291-3B5D-4A7C-9E12-8F6A4D2B7C91), + pointer_default(unique), + object +] +interface IWSLCSessionFactory : IUnknown +{ + // Creates a new session and returns both the session interface and a service reference. + HRESULT CreateSession( + [in] const WSLCSessionInitSettings* Settings, + [in] IWSLCVirtualMachineFactory* VmFactory, + [in] IWSLCPluginNotifier* PluginNotifier, + [in, unique] IWarningCallback* WarningCallback, + [out] IWSLCSession** Session, + [out] IWSLCSessionReference** ServiceRef); + + // Gets the process handle for adding to job object. + HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle); +} + +typedef struct _WSLCSessionListEntry +{ + ULONG SessionId; + DWORD CreatorPid; + wchar_t DisplayName[256]; + wchar_t Sid[256 + 1]; // MAX_SID_SIZE = 256 +} WSLCSessionListEntry; + +[ + uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760), + pointer_default(unique), + object +] +interface IWSLCSessionManager : IUnknown +{ + HRESULT GetVersion([out] WSLCVersion* Version); + // Session management. + HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); + HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session); + HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount); + HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session); + HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session); +} + +// Ensure wslcsdk.h and wslcsdk.idl are also updated. +cpp_quote("#define WSLC_E_BASE (0x0600)") +cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */") +cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */") +cpp_quote("#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */") +cpp_quote("#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */") +cpp_quote("#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */") +cpp_quote("#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */") +cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */") +cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */") +cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */") +cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */") +cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */") +cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */") +cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */") +cpp_quote("#define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */") +cpp_quote("#define WSLC_E_SESSION_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 15) /* 0x8004060F */") diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 7965d92a1d..9fe3fdbed7 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -67,9 +67,6 @@ using WslcInspectContainer = wsl::windows::common::wslc_schema::InspectContainer namespace { -// Validates a stop timeout value. WSLC_STOP_TIMEOUT_DEFAULT means the timeout was not -// specified (the container runtime default is used) and WSLC_STOP_TIMEOUT_NONE (-1) means -// no timeout (wait indefinitely). Any other negative value is invalid. void ValidateStopTimeout(LONG TimeoutSeconds) { THROW_HR_WITH_USER_ERROR_IF( diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 8baad20ab4..0216788fd4 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -6232,6 +6232,35 @@ class WSLCTests expectContainerList({}); } + // test StopContainer with custom timeouts. + // N.B. We can't validate the actual timeouts since the tests environement will affect container stop times. + { + { + // Create a container with a no stop timeout. + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout", {"sleep", "99999"}); + launcher.SetStopTimeout(WSLC_STOP_TIMEOUT_NONE); + + auto container = launcher.Launch(*m_defaultSession); + + auto inspect = container.Inspect(); + VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(0), WSLC_STOP_TIMEOUT_NONE); + + // Validate that passing '0' as the stop timeout overrides the default + VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalNone, 0)); + } + + { + // Create a container with an instant stop timeout. + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout", {"sleep", "99999"}); + launcher.SetStopTimeout(0); + + auto container = launcher.Create(*m_defaultSession); + + auto inspect = container.Inspect(); + VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(-1), 0); + } + } + // Validate that Kill() works as expected { WSLCContainerLauncher launcher("debian:latest", "test-container-kill", {"sleep", "99999"}, {}); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp index 839a302204..0cef70d0d8 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp @@ -239,13 +239,8 @@ class WSLCE2EContainerStopTests WSLC_TEST_METHOD(WSLCE2E_Container_Stop_ValidTimeoutNegativeOne) { - // Run a container that exits when it receives SIGTERM (the default stop signal). A container's - // PID 1 ignores signals it has no handler for, so an explicit trap is required for the stop to - // complete. - auto result = RunWslc(std::format( - LR"(container run -d --name {} {} bash -c "trap 'exit 0' TERM; while true; do sleep 1; done")", - WslcContainerName, - DebianImage.NameAndTag())); + // Run a container in the background + auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); const auto containerId = result.GetStdoutOneLine(); VERIFY_IS_FALSE(containerId.empty()); @@ -253,49 +248,11 @@ class WSLCE2EContainerStopTests // Verify container is running VerifyContainerIsListed(containerId, L"running"); - // -1 means "no timeout" (wait indefinitely), which is a valid value. The container honors - // SIGTERM, so the stop completes promptly. + // -1 is a valid timeout value result = RunWslc(std::format(L"container stop {} -t -1", containerId)); result.Verify({.Stderr = L"", .ExitCode = 0}); - // The container should be stopped after a successful stop - VerifyContainerIsListed(containerId, L"exited"); - } - - WSLC_TEST_METHOD(WSLCE2E_Container_Stop_NoTimeoutWaitsUntilKilled) - { - // Run a container that ignores SIGTERM (the default stop signal) so that a stop with no - // timeout (-1) waits indefinitely instead of escalating to SIGKILL. The container is created - // with a default stop timeout of 0 to confirm the explicit -t -1 overrides that configured value. - auto result = RunWslc(std::format( - LR"(container run -d --stop-timeout 0 --name {} {} bash -c "trap '' TERM; while true; do sleep 1; done")", - WslcContainerName, - DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = 0}); - const auto containerId = result.GetStdoutOneLine(); - VERIFY_IS_FALSE(containerId.empty()); - - // Verify container is running - VerifyContainerIsListed(containerId, L"running"); - - // Stop with -t -1 (no timeout). Because the container ignores SIGTERM and there is no timeout, - // the stop request blocks indefinitely waiting for the container to stop, so run it asynchronously. - auto stopSession = RunWslcInteractive(std::format(L"container stop {} -t -1", containerId)); - - // The container must keep running for at least 2 seconds, confirming -1 did not force a kill. - Sleep(2000); - VERIFY_IS_TRUE(stopSession.IsRunning(), L"`stop -t -1` should still be waiting and must not have killed the container"); - VerifyContainerIsListed(containerId, L"running"); - - // Force the container to stop with a kill (SIGKILL cannot be ignored). - result = RunWslc(std::format(L"container kill {}", containerId)); - result.Verify({.Stdout = std::format(L"{}\r\n", containerId), .Stderr = L"", .ExitCode = 0}); - - // Once the container is killed, the pending stop completes successfully. - const auto stopExitCode = stopSession.Wait(DefaultWaitTimeoutMs); - VERIFY_ARE_EQUAL(0, stopExitCode); - - // The container should be stopped after the kill + // Verify the container is no longer running VerifyContainerIsListed(containerId, L"exited"); } From aeb3454777c71158fcbe307d1936f1e6f32d8a21 Mon Sep 17 00:00:00 2001 From: Blue Date: Thu, 25 Jun 2026 15:31:11 -0700 Subject: [PATCH 3/9] Format --- .../windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp | 5 +---- test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp index 7fc8a10bfe..1c7bd90073 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp @@ -732,10 +732,7 @@ class WSLCE2EContainerCreateTests { constexpr int ExpectedStopTimeout = 30; auto result = RunWslc(std::format( - L"container create --stop-timeout {} --name {} {}", - ExpectedStopTimeout, - WslcContainerName, - DebianImage.NameAndTag())); + L"container create --stop-timeout {} --name {} {}", ExpectedStopTimeout, WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); const auto inspect = InspectContainer(WslcContainerName); diff --git a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp index 126910c21e..290ed3bc4d 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp @@ -1015,8 +1015,8 @@ class WSLCE2EContainerRunTests // When --stop-timeout is not specified, no timeout is forwarded to the container configuration. { - auto result = RunWslc(std::format( - L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); + auto result = + RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"", .ExitCode = 0}); const auto inspect = InspectContainer(WslcContainerName); @@ -1027,15 +1027,15 @@ class WSLCE2EContainerRunTests WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout_Invalid) { { - auto result = RunWslc( - std::format(L"container run --rm --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + auto result = + RunWslc(std::format(L"container run --rm --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1}); EnsureContainerDoesNotExist(WslcContainerName); } { - auto result = RunWslc( - std::format(L"container run --rm --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag())); + auto result = + RunWslc(std::format(L"container run --rm --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag())); result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1}); EnsureContainerDoesNotExist(WslcContainerName); } From ab89d748f405af6db46f8b36ce0726e552e3d921 Mon Sep 17 00:00:00 2001 From: Blue Date: Thu, 25 Jun 2026 15:31:54 -0700 Subject: [PATCH 4/9] Cleanup diff --- src/windows/service/inc/wslc.idl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 0c4b2441fb..225e26def9 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -266,10 +266,6 @@ typedef struct _WSLCContainerOptions [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits; ULONG UlimitsCount; - // Timeout in seconds to wait for the container to stop before killing it. - // WSLC_STOP_TIMEOUT_DEFAULT indicates the timeout was not specified, so the - // container runtime default is used. WSLC_STOP_TIMEOUT_NONE (-1) means no timeout - // (wait indefinitely). Any other negative value is rejected. LONG StopTimeout; } WSLCContainerOptions; From a3fdfc15db276a4cea12e4d9670cf21b81bb54bf Mon Sep 17 00:00:00 2001 From: Blue Date: Thu, 25 Jun 2026 15:33:13 -0700 Subject: [PATCH 5/9] Cleanup diff --- src/windows/wslc/arguments/ArgumentValidation.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/windows/wslc/arguments/ArgumentValidation.cpp b/src/windows/wslc/arguments/ArgumentValidation.cpp index cd1341913f..fd78795f85 100644 --- a/src/windows/wslc/arguments/ArgumentValidation.cpp +++ b/src/windows/wslc/arguments/ArgumentValidation.cpp @@ -49,9 +49,6 @@ void Argument::Validate(const ArgMap& execArgs) const break; case ArgType::StopTimeout: - // Only validate that the value is an integer here; range validation (rejecting - // negative values) happens in the service so that --stop-timeout and `container - // stop --time` share a single source of truth and error message. validation::ValidateIntegerFromString(execArgs.GetAll(), m_name); break; From c8aab5d24cea2e18e79805c50f55fe2095ed1d0a Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 14:56:34 -0700 Subject: [PATCH 6/9] Remove impossible test --- .../wslc/e2e/WSLCE2EContainerStopTests.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp index 0cef70d0d8..52ac845387 100644 --- a/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp +++ b/test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp @@ -237,25 +237,6 @@ class WSLCE2EContainerStopTests } } - WSLC_TEST_METHOD(WSLCE2E_Container_Stop_ValidTimeoutNegativeOne) - { - // Run a container in the background - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag())); - result.Verify({.Stderr = L"", .ExitCode = 0}); - const auto containerId = result.GetStdoutOneLine(); - VERIFY_IS_FALSE(containerId.empty()); - - // Verify container is running - VerifyContainerIsListed(containerId, L"running"); - - // -1 is a valid timeout value - result = RunWslc(std::format(L"container stop {} -t -1", containerId)); - result.Verify({.Stderr = L"", .ExitCode = 0}); - - // Verify the container is no longer running - VerifyContainerIsListed(containerId, L"exited"); - } - private: const std::wstring WslcContainerName = L"wslc-test-container"; const std::wstring WslcContainerName2 = L"wslc-test-container-2"; From 29bbe0c87a4593c3b2b047d27ffcf141b6e2edf1 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 15:30:14 -0700 Subject: [PATCH 7/9] Rethink API --- src/windows/common/APICompat.cpp | 1 - src/windows/common/WSLCContainerLauncher.cpp | 7 +++- src/windows/common/WSLCContainerLauncher.h | 2 +- src/windows/service/inc/WSLCShared.idl | 3 +- src/windows/service/inc/wslc.idl | 3 +- src/windows/wslcsession/WSLCContainer.cpp | 4 +-- test/windows/WSLCTests.cpp | 38 ++++++++++++++++++-- 7 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/windows/common/APICompat.cpp b/src/windows/common/APICompat.cpp index 48d8433661..8263420465 100644 --- a/src/windows/common/APICompat.cpp +++ b/src/windows/common/APICompat.cpp @@ -294,7 +294,6 @@ ContainerOptionsConversion::ContainerOptionsConversion(const WSLCCompatContainer m_value.Flags = Options.Flags; m_value.StopSignal = Options.StopSignal; - m_value.StopTimeout = WSLC_STOP_TIMEOUT_DEFAULT; m_value.HostName = Options.HostName; m_value.DomainName = Options.DomainName; diff --git a/src/windows/common/WSLCContainerLauncher.cpp b/src/windows/common/WSLCContainerLauncher.cpp index dbaf4ba5af..cdbb50a0c8 100644 --- a/src/windows/common/WSLCContainerLauncher.cpp +++ b/src/windows/common/WSLCContainerLauncher.cpp @@ -300,8 +300,13 @@ std::pair> WSLCContainerLauncher::C options.Ports = m_ports.data(); options.PortsCount = static_cast(m_ports.size()); options.StopSignal = m_stopSignal; - options.StopTimeout = m_stopTimeout; options.Flags = m_containerFlags; + if (m_stopTimeout.has_value()) + { + options.StopTimeout = m_stopTimeout.value(); + WI_SetFlag(options.Flags, WSLCContainerFlagsStopTimeout); + } + options.ShmSize = m_shmSize; if (!entrypointStorage.empty()) diff --git a/src/windows/common/WSLCContainerLauncher.h b/src/windows/common/WSLCContainerLauncher.h index 8a8bf8d68e..0cde0d926f 100644 --- a/src/windows/common/WSLCContainerLauncher.h +++ b/src/windows/common/WSLCContainerLauncher.h @@ -104,7 +104,7 @@ class WSLCContainerLauncher : private WSLCProcessLauncher std::string m_networkMode; std::vector m_entrypoint; WSLCSignal m_stopSignal = WSLCSignalNone; - LONG m_stopTimeout = WSLC_STOP_TIMEOUT_DEFAULT; + std::optional m_stopTimeout; int64_t m_shmSize = 0; WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone; std::string m_hostname; diff --git a/src/windows/service/inc/WSLCShared.idl b/src/windows/service/inc/WSLCShared.idl index d02849aa0e..39e22eb76e 100644 --- a/src/windows/service/inc/WSLCShared.idl +++ b/src/windows/service/inc/WSLCShared.idl @@ -98,9 +98,10 @@ typedef enum _WSLCContainerFlags WSLCContainerFlagsGpu = 2, // Enable GPU access. WSLCContainerFlagsInit = 4, // Run the container under an init process. WSLCContainerFlagsPublishAll = 8, // Publish all exposed ports. + WSLCContainerFlagsStopTimeout = 16, // The StopTimeout field is set and should be honored (otherwise StopTimeout is ignored). } WSLCContainerFlags; -cpp_quote("#define WSLCContainerFlagsValid (WSLCContainerFlagsRm | WSLCContainerFlagsGpu | WSLCContainerFlagsInit | WSLCContainerFlagsPublishAll)") +cpp_quote("#define WSLCContainerFlagsValid (WSLCContainerFlagsRm | WSLCContainerFlagsGpu | WSLCContainerFlagsInit | WSLCContainerFlagsPublishAll | WSLCContainerFlagsStopTimeout)") cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCContainerFlags);") diff --git a/src/windows/service/inc/wslc.idl b/src/windows/service/inc/wslc.idl index 225e26def9..9fc0fa4e53 100644 --- a/src/windows/service/inc/wslc.idl +++ b/src/windows/service/inc/wslc.idl @@ -44,7 +44,7 @@ cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64") cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45") cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256") cpp_quote("#define WSLC_EPHEMERAL_PORT 0") -cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MAX") // use the default container stop timeout (10 seconds). +cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MIN") // Pass to Stop() to use the default container stop timeout cpp_quote("#define WSLC_STOP_TIMEOUT_NONE -1") // Wait forever for the container to stop. typedef @@ -266,6 +266,7 @@ typedef struct _WSLCContainerOptions [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits; ULONG UlimitsCount; + // Ignored unless WSLCContainerFlagsStopTimeout is set in Flags. LONG StopTimeout; } WSLCContainerOptions; diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 9fe3fdbed7..9d59d38ece 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -1430,9 +1430,9 @@ std::unique_ptr WSLCContainerImpl::Create( request.StopSignal = std::to_string(containerOptions.StopSignal); } - ValidateStopTimeout(containerOptions.StopTimeout); - if (containerOptions.StopTimeout != WSLC_STOP_TIMEOUT_DEFAULT) + if (WI_IsFlagSet(containerOptions.Flags, WSLCContainerFlagsStopTimeout)) { + ValidateStopTimeout(containerOptions.StopTimeout); request.StopTimeout = static_cast(containerOptions.StopTimeout); } diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 0216788fd4..6df96bfa7b 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -6233,11 +6233,11 @@ class WSLCTests } // test StopContainer with custom timeouts. - // N.B. We can't validate the actual timeouts since the tests environement will affect container stop times. + // N.B. We can't validate the actual timeouts since the tests environment will affect container stop times. { { // Create a container with a no stop timeout. - WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout", {"sleep", "99999"}); + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-1", {"sleep", "99999"}); launcher.SetStopTimeout(WSLC_STOP_TIMEOUT_NONE); auto container = launcher.Launch(*m_defaultSession); @@ -6251,7 +6251,7 @@ class WSLCTests { // Create a container with an instant stop timeout. - WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout", {"sleep", "99999"}); + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-2", {"sleep", "99999"}); launcher.SetStopTimeout(0); auto container = launcher.Create(*m_defaultSession); @@ -6259,6 +6259,38 @@ class WSLCTests auto inspect = container.Inspect(); VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(-1), 0); } + + { + // Create a container with an short stop timeout. + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-3", {"sleep", "99999"}); + launcher.SetStopTimeout(1); + + auto container = launcher.Launch(*m_defaultSession); + + auto inspect = container.Inspect(); + VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(0), 1); + + auto initProcess = container.GetInitProcess(); + std::thread stopThread([&]() { VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalNone, -1)); }); + + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() { + // TODO: calling Kill() here hangs since Stop() holds the container lock. + // Update this once fixed to: + // LOG_IF_FAILED(container.Get().Kill(WSLCSignalSIGKILL)); + + LOG_IF_FAILED(initProcess.Get().Signal(WSLCSignalSIGKILL)); + + if (stopThread.joinable()) + { + stopThread.join(); + } + }); + + // Wait for at least 2 seconds for the stop to complete to prove that the default 1 second timeout was correctly overriden. + auto waitResult = WaitForSingleObject(stopThread.native_handle(), 2000); + + VERIFY_ARE_EQUAL(waitResult, WAIT_TIMEOUT); + } } // Validate that Kill() works as expected From 1d9ffd63bf4c9b07d33ef338561174dce8ea783e Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 15:52:35 -0700 Subject: [PATCH 8/9] Update test --- test/windows/WSLCTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 6df96bfa7b..2af2e95c83 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -5572,7 +5572,7 @@ class WSLCTests // Invalid container flags are rejected with E_INVALIDARG. options.Image = "debian:latest"; - options.Flags = static_cast(0x10); + options.Flags = static_cast(0x20); VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateContainer(&options, nullptr, &container)); // Invalid init process flags are rejected with E_INVALIDARG. From 427632a07d53552aa0b219100d50c8f0af4ee981 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 16:04:03 -0700 Subject: [PATCH 9/9] Apply PR suggestions --- src/windows/wslcsession/WSLCContainer.cpp | 2 ++ test/windows/WSLCTests.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/windows/wslcsession/WSLCContainer.cpp b/src/windows/wslcsession/WSLCContainer.cpp index 9d59d38ece..6618fceea2 100644 --- a/src/windows/wslcsession/WSLCContainer.cpp +++ b/src/windows/wslcsession/WSLCContainer.cpp @@ -1433,6 +1433,8 @@ std::unique_ptr WSLCContainerImpl::Create( if (WI_IsFlagSet(containerOptions.Flags, WSLCContainerFlagsStopTimeout)) { ValidateStopTimeout(containerOptions.StopTimeout); + + THROW_HR_IF(E_INVALIDARG, containerOptions.StopTimeout == WSLC_STOP_TIMEOUT_DEFAULT); request.StopTimeout = static_cast(containerOptions.StopTimeout); } diff --git a/test/windows/WSLCTests.cpp b/test/windows/WSLCTests.cpp index 2af2e95c83..09baf22a93 100644 --- a/test/windows/WSLCTests.cpp +++ b/test/windows/WSLCTests.cpp @@ -5647,7 +5647,7 @@ class WSLCTests VERIFY_ARE_EQUAL(process.Wait(), WSLCSignalSIGHUP + 128); } - // Validate that the default stop signal can be overriden. + // Validate that the default stop signal can be overridden. { WSLCContainerLauncher launcher("debian:latest", "test-stop-signal-2", {"/bin/cat"}, {}, {}, WSLCProcessFlagsStdin); launcher.SetDefaultStopSignal(WSLCSignalSIGHUP);