Implement --stop-timeout in wslc create|run and support -1 (infinite) timeouts (#40919)

* Save state * Add service test coverage * Format * Cleanup diff * Cleanup diff * Remove impossible test * Rethink API * Update test * Apply PR suggestions * Handle default value properly * Format * Apply PR feedback * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Blue committed Jun 30, 2026 at 15:52 UTC 9f1b8c8aeb538137f584a8f04601860bb4d61c42
21 files changed +1033 -779
localization/strings/en-US/Resources.resw
+7
@@ -2355,6 +2355,10 @@ For privacy information about this product please visit https://aka.ms/privacy.<
2355 <value>Unsupported network mode: '{}'</value>
2356 <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2357 </data>
2358 + <data name="MessageWslcInvalidStopTimeout" xml:space="preserve">
2359 + <value>Invalid stop timeout value: {}</value>
2360 + <comment>{FixedPlaceholder="{}"}Command line arguments, file names and string inserts should not be translated</comment>
2361 + </data>
2362 <data name="MessageWslcAdditionalNetworksRequirePrimary" xml:space="preserve">
2363 <value>Additional networks are not allowed when the primary network mode is 'host' or 'none'.</value>
2364 <comment>{Locked="host"}{Locked="none"}Command line arguments, file names and string inserts should not be translated</comment>
@@ -2920,6 +2924,9 @@ On first run, creates the file with all settings commented out at their defaults
2924 <data name="WSLCCLI_StopSignalArgDescription" xml:space="preserve">
2925 <value>Signal to stop the container</value>
2926 </data>
2927 + <data name="WSLCCLI_StopTimeoutArgDescription" xml:space="preserve">
2928 + <value>Timeout (in seconds) to stop the container before killing it (-1 for no timeout)</value>
2929 + </data>
2930 <data name="WSLCCLI_ShmSizeArgDescription" xml:space="preserve">
2931 <value>Size of /dev/shm (e.g. 64M, 1G)</value>
2932 <comment>{Locked="/dev/shm"}Command line arguments should not be translated</comment>
src/windows/common/WSLCContainerLauncher.cpp
+11
@@ -129,6 +129,11 @@ void WSLCContainerLauncher::SetDefaultStopSignal(WSLCSignal Signal)
129 m_stopSignal = Signal;
130 }
131
132 +void WSLCContainerLauncher::SetStopTimeout(LONG Timeout)
133 +{
134 + m_stopTimeout = Timeout;
135 +}
136 +
137 void WSLCContainerLauncher::SetShmSize(int64_t ShmSize)
138 {
139 m_shmSize = ShmSize;
@@ -296,6 +301,12 @@ std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::C
301 options.PortsCount = static_cast<ULONG>(m_ports.size());
302 options.StopSignal = m_stopSignal;
303 options.Flags = m_containerFlags;
304 + if (m_stopTimeout.has_value())
305 + {
306 + options.StopTimeout = m_stopTimeout.value();
307 + WI_SetFlag(options.Flags, WSLCContainerFlagsStopTimeout);
308 + }
309 +
310 options.ShmSize = m_shmSize;
311
312 if (!entrypointStorage.empty())
src/windows/common/WSLCContainerLauncher.h
+2
@@ -75,6 +75,7 @@ public:
75 void SetName(std::string&& Name);
76 void SetEntrypoint(std::vector<std::string>&& entrypoint);
77 void SetDefaultStopSignal(WSLCSignal Signal);
78 + void SetStopTimeout(LONG Timeout);
79 void SetShmSize(int64_t ShmSize);
80 void SetContainerFlags(WSLCContainerFlags Flags);
81 void SetHostname(std::string&& Hostname);
@@ -103,6 +104,7 @@ private:
104 std::string m_networkMode;
105 std::vector<std::string> m_entrypoint;
106 WSLCSignal m_stopSignal = WSLCSignalNone;
107 + std::optional<LONG> m_stopTimeout;
108 int64_t m_shmSize = 0;
109 WSLCContainerFlags m_containerFlags = WSLCContainerFlagsNone;
110 std::string m_hostname;
src/windows/inc/docker_schema.h
+5 -2
@@ -297,6 +297,7 @@ struct CreateContainer
297 std::string Hostname;
298 std::string Domainname;
299 std::optional<std::string> StopSignal;
300 + std::optional<long> StopTimeout;
301 std::optional<std::string> WorkingDir;
302 std::optional<std::vector<std::string>> Cmd;
303 std::optional<std::vector<std::string>> Entrypoint;
@@ -307,7 +308,7 @@ struct CreateContainer
308 NetworkingConfig NetworkingConfig;
309
310 NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(
310 - CreateContainer, Image, Cmd, Tty, OpenStdin, StdinOnce, Entrypoint, Env, ExposedPorts, HostConfig, StopSignal, WorkingDir, User, Hostname, Domainname, Labels, NetworkingConfig);
311 + CreateContainer, Image, Cmd, Tty, OpenStdin, StdinOnce, Entrypoint, Env, ExposedPorts, HostConfig, StopSignal, StopTimeout, WorkingDir, User, Hostname, Domainname, Labels, NetworkingConfig);
312 };
313
314 struct ContainerInspectState
@@ -329,8 +330,10 @@ struct ContainerConfig
330 std::optional<std::vector<std::string>> Env;
331 std::optional<std::vector<std::string>> Cmd;
332 std::optional<std::vector<std::string>> Entrypoint;
333 + std::optional<std::string> StopSignal;
334 + std::optional<int> StopTimeout;
335
333 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Image, User, WorkingDir, Env, Cmd, Entrypoint);
336 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Image, User, WorkingDir, Env, Cmd, Entrypoint, StopSignal, StopTimeout);
337 };
338
339 struct InspectMount
src/windows/inc/wslc_schema.h
+2 -1
@@ -76,8 +76,9 @@ struct ContainerConfig
76 std::optional<std::vector<std::string>> Entrypoint;
77 std::string User;
78 std::string WorkingDir;
79 + std::optional<int> StopTimeout;
80
80 - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Env, Cmd, Entrypoint, User, WorkingDir);
81 + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ContainerConfig, Env, Cmd, Entrypoint, User, WorkingDir, StopTimeout);
82 };
83
84 struct InspectEndpointSettings
src/windows/service/inc/WSLCShared.idl
+2 -1
@@ -98,9 +98,10 @@ typedef enum _WSLCContainerFlags
98 WSLCContainerFlagsGpu = 2, // Enable GPU access.
99 WSLCContainerFlagsInit = 4, // Run the container under an init process.
100 WSLCContainerFlagsPublishAll = 8, // Publish all exposed ports.
101 + WSLCContainerFlagsStopTimeout = 16, // The StopTimeout field is set and should be honored (otherwise StopTimeout is ignored).
102 } WSLCContainerFlags;
103
103 -cpp_quote("#define WSLCContainerFlagsValid (WSLCContainerFlagsRm | WSLCContainerFlagsGpu | WSLCContainerFlagsInit | WSLCContainerFlagsPublishAll)")
104 +cpp_quote("#define WSLCContainerFlagsValid (WSLCContainerFlagsRm | WSLCContainerFlagsGpu | WSLCContainerFlagsInit | WSLCContainerFlagsPublishAll | WSLCContainerFlagsStopTimeout)")
105
106 cpp_quote("DEFINE_ENUM_FLAG_OPERATORS(WSLCContainerFlags);")
107
src/windows/service/inc/wslc.idl
+751 -746
@@ -1,746 +1,751 @@
1 -/*++
2 -
3 -Copyright (c) Microsoft Corporation. All rights reserved.
4 -
5 -Module Name:
6 -
7 - wslc.idl
8 -
9 -Abstract:
10 -
11 - This file contains the WSLC-related COM object definitions.
12 - // N.B. ABI breaking changes in this file are OK, since both client & server always ship together.
13 - // The WSLC SDK must not use this file, and instead use WSLCCompat.idl
14 -
15 ---*/
16 -
17 -import "unknwn.idl";
18 -import "wtypes.idl";
19 -
20 -// Enums and flags shared with the SDK-facing API (WSLCCompat.idl).
21 -import "WSLCShared.idl";
22 -
23 -cpp_quote("#ifdef __cplusplus")
24 -cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLCSessionManager;")
25 -cpp_quote("class DECLSPEC_UUID(\"9fcd2067-9fc6-4efa-9eb0-698169ebf7d3\") WSLCSessionFactory;")
26 -cpp_quote("#endif")
27 -
28 -#define WSLC_MAX_CONTAINER_NAME_LENGTH 255
29 -#define WSLC_MAX_IMAGE_NAME_LENGTH 255
30 -#define WSLC_MAX_VOLUME_NAME_LENGTH 255
31 -#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255
32 -#define WSLC_MAX_NETWORK_NAME_LENGTH 255
33 -#define WSLC_CONTAINER_ID_LENGTH 64
34 -#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45
35 -#define WSLC_EPHEMERAL_PORT 0
36 -#define WSLC_MAX_SAVE_IMAGES_COUNT 256
37 -
38 -cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255")
39 -cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255")
40 -cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255")
41 -cpp_quote("#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255")
42 -cpp_quote("#define WSLC_MAX_NETWORK_NAME_LENGTH 255")
43 -cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64")
44 -cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45")
45 -cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256")
46 -cpp_quote("#define WSLC_EPHEMERAL_PORT 0")
47 -
48 -typedef
49 -struct _WSLCVersion {
50 - ULONG Major;
51 - ULONG Minor;
52 - ULONG Revision;
53 -} WSLCVersion;
54 -
55 -[
56 - uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801),
57 - pointer_default(unique),
58 - object
59 -]
60 -interface ICrashDumpCallback : IUnknown
61 -{
62 - HRESULT OnCrashDump(
63 - [in, string] LPCWSTR DumpPath,
64 - [in, unique, string] LPCSTR ProcessName,
65 - [in] ULONG Pid,
66 - [in] ULONG Signal,
67 - [in] ULONGLONG Timestamp);
68 -};
69 -
70 -[
71 - uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1),
72 - pointer_default(unique),
73 - object
74 -]
75 -interface IProgressCallback : IUnknown
76 -{
77 - HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total);
78 -};
79 -
80 -[
81 - uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3),
82 - pointer_default(unique),
83 - object
84 -]
85 -interface IWarningCallback : IUnknown
86 -{
87 - HRESULT OnWarning([in, string] LPCWSTR Message);
88 -};
89 -
90 -[
91 - uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2),
92 - pointer_default(unique),
93 - object
94 -]
95 -interface IWSLCPluginNotifier : IUnknown
96 -{
97 - // 'InspectJson' follows the wslc_schema::InspectContainer format.
98 - // Returning failure prevents the container creation.
99 - HRESULT OnContainerStarted([in] LPCSTR InspectJson);
100 -
101 - // Called when a container is about to stop. 'ContainerId' is the container identifier. Errors are logged but ignored.
102 - HRESULT OnContainerStopping([in] LPCSTR ContainerId);
103 -
104 - // 'InspectJson' follows the wslc_schema::InspectImage format. Errors are logged but ignored.
105 - HRESULT OnImageCreated([in] LPCSTR InspectJson);
106 -
107 - // Called when an image is deleted. 'ImageId' is the image identifier. Errors are logged but ignored.
108 - HRESULT OnImageDeleted([in] LPCSTR ImageId);
109 -};
110 -
111 -typedef struct _WSLCImageInformation
112 -{
113 - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
114 - char Hash[256];
115 - char Digest[256];
116 - LONGLONG Size; // Matches Docker's int64 image size
117 - LONGLONG Created; // Unix timestamp
118 - char ParentId[256];
119 -} WSLCImageInformation;
120 -
121 -typedef struct _KeyValuePairInformation
122 -{
123 - [string] LPSTR Key;
124 - [string] LPSTR Value;
125 -} KeyValuePairInformation;
126 -
127 -typedef struct _KeyValuePair
128 -{
129 - [string] LPCSTR Key;
130 - [string] LPCSTR Value;
131 -} KeyValuePair;
132 -
133 -typedef KeyValuePair WSLCLabel;
134 -typedef KeyValuePair WSLCDriverOption;
135 -typedef KeyValuePair WSLCFilter;
136 -
137 -typedef KeyValuePairInformation WSLCLabelInformation;
138 -typedef KeyValuePairInformation WSLCDriverOptionInformation;
139 -
140 -typedef struct _WSLCListImagesOptions
141 -{
142 - DWORD Flags; // WSLCListImagesFlags (can combine with bitwise OR)
143 - [unique, size_is(FiltersCount)] const WSLCFilter* Filters;
144 - ULONG FiltersCount;
145 -} WSLCListImagesOptions;
146 -
147 -typedef struct _WSLCStringArray
148 -{
149 - [unique, size_is(Count)] LPCSTR const* Values;
150 - ULONG Count;
151 -} WSLCStringArray;
152 -
153 -typedef struct _WSLCProcessOptions
154 -{
155 - [unique] LPCSTR CurrentDirectory;
156 - [unique] LPCSTR User;
157 - WSLCStringArray CommandLine;
158 - WSLCStringArray Environment;
159 - WSLCProcessFlags Flags;
160 -} WSLCProcessOptions;
161 -
162 -typedef struct _WSLCProcessStartOptions
163 -{
164 - ULONG TtyRows; // Only needed when tty fd's are passed.
165 - ULONG TtyColumns;
166 - [unique, string] LPCSTR DetachKeys;
167 -} WSLCProcessStartOptions;
168 -
169 -typedef struct _WSLCNamedVolume
170 -{
171 - LPCSTR Name;
172 - LPCSTR ContainerPath;
173 - BOOL ReadOnly;
174 -} WSLCNamedVolume;
175 -
176 -typedef struct _WSLCVolume
177 -{
178 - LPCWSTR HostPath;
179 - LPCSTR ContainerPath;
180 - BOOL ReadOnly;
181 -} WSLCVolume;
182 -
183 -typedef struct _WSLCPortMapping
184 -{
185 - USHORT HostPort;
186 - USHORT ContainerPort;
187 - int Family;
188 - int Protocol;
189 - char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1];
190 -} WSLCPortMapping;
191 -
192 -typedef struct _WSLCTmpfsMount
193 -{
194 - LPCSTR Destination;
195 - [unique] LPCSTR Options;
196 -} WSLCTmpfsMount;
197 -
198 -typedef struct _WSLCUlimit
199 -{
200 - [string] LPCSTR Name;
201 - LONGLONG Soft;
202 - LONGLONG Hard;
203 -} WSLCUlimit;
204 -
205 -typedef struct _WSLCNetworkConnection
206 -{
207 - [string] LPCSTR NetworkName;
208 - [unique, size_is(SettingsCount)] const KeyValuePair* Settings;
209 - ULONG SettingsCount;
210 -} WSLCNetworkConnection;
211 -
212 -// Options for IWSLCContainer::ConnectToNetwork.
213 -typedef struct _WSLCNetworkConnectionOptions
214 -{
215 - [unique] LPCSTR NetworkName;
216 - [unique] LPCSTR ContainerIpAddress; // Reserved for future --ip support; must be NULL today.
217 -} WSLCNetworkConnectionOptions;
218 -
219 -typedef struct _WSLCContainerNetwork
220 -{
221 - [unique, string] LPCSTR NetworkMode;
222 -
223 - [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks;
224 - ULONG NetworksCount;
225 -
226 - // Settings for the primary endpoint (the network identified by NetworkMode).
227 - // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries).
228 - [unique, size_is(SettingsCount)] const KeyValuePair* Settings;
229 - ULONG SettingsCount;
230 -} WSLCContainerNetwork;
231 -
232 -typedef struct _WSLCContainerOptions
233 -{
234 - LPCSTR Image;
235 - [unique] LPCSTR Name;
236 - WSLCStringArray Entrypoint;
237 - WSLCProcessOptions InitProcessOptions;
238 - [unique, size_is(VolumesCount)] WSLCVolume* Volumes;
239 - ULONG VolumesCount;
240 - [unique, size_is(PortsCount)] WSLCPortMapping* Ports;
241 - ULONG PortsCount;
242 - [unique, size_is(LabelsCount)] const WSLCLabel* Labels;
243 - ULONG LabelsCount;
244 - WSLCContainerFlags Flags;
245 - WSLCSignal StopSignal;
246 - // TODO: List specific GPU devices.
247 - [unique] LPCSTR HostName;
248 - [unique] LPCSTR DomainName;
249 -
250 - WSLCStringArray DnsServers;
251 - WSLCStringArray DnsSearchDomains;
252 - WSLCStringArray DnsOptions;
253 -
254 - LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus
255 - WSLCContainerNetwork ContainerNetwork;
256 - [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs;
257 - ULONG TmpfsCount;
258 -
259 - [unique, size_is(NamedVolumesCount)] WSLCNamedVolume* NamedVolumes;
260 - ULONG NamedVolumesCount;
261 -
262 - LONGLONG MemoryBytes;
263 - LONGLONG NanoCpus;
264 - [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits;
265 - ULONG UlimitsCount;
266 -} WSLCContainerOptions;
267 -
268 -typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ;
269 -
270 -typedef struct _WSLCContainerEntry
271 -{
272 - char Name[WSLC_MAX_CONTAINER_NAME_LENGTH + 1];
273 - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
274 - WSLCContainerId Id;
275 - ULONGLONG StateChangedAt;
276 - ULONGLONG CreatedAt;
277 - WSLCContainerState State;
278 -} WSLCContainerEntry;
279 -
280 -typedef struct _WSLCContainerPortMapping
281 -{
282 - WSLCContainerId Id;
283 - WSLCPortMapping PortMapping;
284 -} WSLCContainerPortMapping;
285 -
286 -typedef [system_handle(sh_file)] HANDLE FILE_HANDLE;
287 -typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE;
288 -typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE;
289 -
290 -typedef struct _WSLCHandle
291 -{
292 - WSLCHandleType Type;
293 -
294 - [switch_type(WSLCHandleType), switch_is(Type)]
295 - union
296 - {
297 - [case(WSLCHandleTypeFile)]
298 - FILE_HANDLE File;
299 - [case(WSLCHandleTypePipe)]
300 - PIPE_HANDLE Pipe;
301 - [case(WSLCHandleTypeSocket)]
302 - SOCKET_HANDLE Socket;
303 - [default];
304 - } Handle;
305 -} WSLCHandle;
306 -
307 -[
308 - uuid(1AD163CD-393D-4B33-83A2-8A3F3F23E608),
309 - pointer_default(unique),
310 - object
311 -]
312 -interface IWSLCProcess : IUnknown
313 -{
314 - HRESULT Signal([in] int Signal);
315 - HRESULT GetExitEvent([out, system_handle(sh_event)] HANDLE* EventHandle);
316 - HRESULT GetStdHandle([in] WSLCFD Fd, [out] WSLCHandle* Handle);
317 - HRESULT GetFlags([out] WSLCProcessFlags* Flags);
318 - HRESULT GetPid([out] int* Pid);
319 - HRESULT GetState([out] WSLCProcessState* State, [out] int* Code);
320 - HRESULT ResizeTty([in] ULONG Rows, [in] ULONG Columns);
321 -
322 - // Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API.
323 -}
324 -
325 -//
326 -// Values discovered from the guest kernel after the VM has booted, forwarded
327 -// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new
328 -// fields here instead of new IDL methods so the interface does not need a new
329 -// IID for each kernel-published value.
330 -//
331 -typedef struct _WSLCGuestCapabilities
332 -{
333 - // (base, size) of the hv_pci swiotlb pool the kernel reserved and
334 - // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}.
335 - // Both zero means the running kernel does not support hv_pci swiotlb.
336 - UINT64 HvPciSwiotlbBase;
337 - UINT64 HvPciSwiotlbSize;
338 -} WSLCGuestCapabilities;
339 -
340 -//
341 -// IWSLCVirtualMachine - Interface representing a single VM instance.
342 -// Operations are scoped to this VM. The VM ID is stored internally,
343 -// so only the holder of this interface can operate on the VM.
344 -//
345 -[
346 - uuid(B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A),
347 - pointer_default(unique),
348 - object
349 -]
350 -interface IWSLCVirtualMachine : IUnknown
351 -{
352 - // Gets the VM ID.
353 - HRESULT GetId([out, retval] GUID* VmId);
354 -
355 - // Accepts a connect from mini_init in the VM.
356 - HRESULT AcceptConnection([out, system_handle(sh_socket)] HANDLE* Socket);
357 -
358 - // Configures networking engine with sockets from the user process.
359 - // GnsSocket is required; DnsSocket is optional (NULL if DNS tunneling is disabled).
360 - // The service duplicates the socket handles.
361 - HRESULT ConfigureNetworking(
362 - [in, system_handle(sh_socket)] HANDLE GnsSocket,
363 - [in, system_handle(sh_socket), unique] HANDLE* DnsSocket);
364 -
365 - // Attaches a VHD or VHDX disk to the VM.
366 - // GrantVmAccess is called by the service before attaching.
367 - // Returns the SCSI LUN assigned to the disk.
368 - HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out, retval] ULONG* Lun);
369 -
370 - // Detaches a previously attached disk from the VM.
371 - HRESULT DetachDisk([in] ULONG Lun);
372 -
373 - // Adds a filesystem share (Plan9 or VirtioFS) accessible to the VM.
374 - // Returns an instance GUID that can be used to remove the share.
375 - HRESULT AddShare([in] LPCWSTR WindowsPath, [in] BOOL ReadOnly, [out, retval] GUID* ShareId);
376 -
377 - // Removes a previously added filesystem share.
378 - HRESULT RemoveShare([in] REFGUID ShareId);
379 -
380 - // Configures the per-VM state discovered from the guest kernel after boot
381 - // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and
382 - // ConfigureNetworking forward these values to wsldevicehost via the
383 - // swiotlb device-options token. A capabilities struct whose fields are
384 - // all zero means the guest kernel does not support the feature; the
385 - // token is then omitted.
386 - HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities);
387 -
388 - // Returns an event that is signaled when the VM exits (graceful or forced).
389 - HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
390 -
391 - HRESULT MapVirtioNetPort(
392 - [in] USHORT HostPort,
393 - [in] USHORT GuestPort,
394 - [in] int Protocol,
395 - [in] LPCSTR ListenAddress,
396 - [out, retval] USHORT* AllocatedHostPort);
397 -
398 - // Unmaps a port previously mapped via MapVirtioNetPort.
399 - HRESULT UnmapVirtioNetPort(
400 - [in] USHORT HostPort,
401 - [in] USHORT GuestPort,
402 - [in] int Protocol,
403 - [in] LPCSTR ListenAddress);
404 -
405 - // Returns the cached termination reason and details. These are only available after the
406 - // termination event has been signaled; before that the call fails.
407 - HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details);
408 -}
409 -
410 -//
411 -// IWSLCVirtualMachineFactory - Creates VMs on demand for a session.
412 -//
413 -// Held by the per-user session process and implemented by the SYSTEM service.
414 -// This lets the session create a fresh VM at any time (e.g. to recreate a VM that
415 -// was idle-terminated when it had no running containers), instead of the service
416 -// eagerly creating a single VM up front. Each successful call returns a new VM whose
417 -// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down.
418 -//
419 -[
420 - uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B),
421 - pointer_default(unique),
422 - object
423 -]
424 -interface IWSLCVirtualMachineFactory : IUnknown
425 -{
426 - // Creates a new VM using the settings captured at session creation time.
427 - HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm);
428 -}
429 -
430 -// Settings for IWSLCSessionManager::CreateSession - full session configuration
431 -typedef struct _WSLCSessionSettings {
432 - LPCWSTR DisplayName;
433 - LPCWSTR StoragePath;
434 - ULONGLONG MaximumStorageSizeMb;
435 - ULONG CpuCount;
436 - ULONG MemoryMb;
437 - ULONG BootTimeoutMs;
438 - WSLCNetworkingMode NetworkingMode;
439 - WSLCFeatureFlags FeatureFlags;
440 - WSLCHandle DmesgOutput;
441 - WSLCSessionStorageFlags StorageFlags;
442 -
443 - // Below options are used for debugging purposes only.
444 - [unique] LPCWSTR RootVhdOverride;
445 - [unique] LPCSTR RootVhdTypeOverride;
446 -} WSLCSessionSettings;
447 -
448 -
449 -[
450 - uuid(7577FE8D-DE85-471E-B870-11669986F332),
451 - pointer_default(unique),
452 - object
453 -]
454 -interface IWSLCContainer : IUnknown
455 -{
456 - HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr);
457 - HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds);
458 - HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] const WSLCProcessStartOptions* StartOptions, [in, unique] IWarningCallback* WarningCallback);
459 - HRESULT Delete([in] WSLCDeleteFlags Flags);
460 - HRESULT Export([in] WSLCHandle TarHandle);
461 - HRESULT GetState([out] WSLCContainerState* State);
462 - HRESULT GetInitProcess([out] IWSLCProcess** Process);
463 - HRESULT Exec([in, ref] const WSLCProcessOptions* Options, [in, unique] const WSLCProcessStartOptions* StartOptions, [out] IWSLCProcess** Process);
464 - HRESULT Inspect([out] LPSTR* Output);
465 - HRESULT Logs([in] WSLCLogsFlags Flags, [out] WSLCHandle* Stdout, [out] WSLCHandle* Stderr, [in] ULONGLONG Since, [in] ULONGLONG Until, [in] ULONGLONG Tail);
466 - HRESULT GetId([out, string] WSLCContainerId Id);
467 - HRESULT GetName([out, string] LPSTR* Name);
468 - HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count);
469 - HRESULT Kill([in] WSLCSignal Signal);
470 - HRESULT Stats([out] LPSTR* Output);
471 - HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options);
472 - HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName);
473 -}
474 -
475 -typedef struct _WSLCDeletedImageInformation
476 -{
477 - char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
478 - WSLCDeletedImageType Type;
479 -} WSLCDeletedImageInformation;
480 -
481 -typedef struct _WSLCDeleteImageOptions
482 -{
483 - LPCSTR Image; // Image can be ID or Repo:Tag.
484 - DWORD Flags; // WSLCDeleteImageFlags
485 - // TODO: Platforms: a json array of OCI platform strings.
486 -} WSLCDeleteImageOptions;
487 -
488 -typedef struct _WSLCBuildImageOptions
489 -{
490 - LPCWSTR ContextPath;
491 - WSLCHandle DockerfileHandle;
492 - WSLCStringArray Tags;
493 - WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker.
494 - LPCSTR Target; // Target build stage name passed as --target to docker.
495 - WSLCBuildImageFlags Flags; // WSLCBuildImageFlags
496 - WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker.
497 -} WSLCBuildImageOptions;
498 -
499 -typedef struct _WSLCTagImageOptions
500 -{
501 - LPCSTR Image; // Source image name or ID.
502 - LPCSTR Repo; // Target repository name.
503 - LPCSTR Tag; // Target tag name.
504 -} WSLCTagImageOptions;
505 -
506 -typedef struct _WSLCVolumeOptions
507 -{
508 - [unique] LPCSTR Name;
509 - [unique] LPCSTR Driver;
510 - [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts;
511 - ULONG DriverOptsCount;
512 - [unique, size_is(LabelsCount)] const WSLCLabel* Labels;
513 - ULONG LabelsCount;
514 -} WSLCVolumeOptions;
515 -
516 -typedef char WSLCVolumeName[WSLC_MAX_VOLUME_NAME_LENGTH + 1];
517 -
518 -typedef struct _WSLCVolumeInformation
519 -{
520 - WSLCVolumeName Name;
521 - char Driver[WSLC_MAX_VOLUME_DRIVER_LENGTH + 1];
522 -} WSLCVolumeInformation;
523 -
524 -typedef struct _WSLCNetworkOptions
525 -{
526 - LPCSTR Name;
527 - [unique] LPCSTR Driver;
528 - [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts;
529 - ULONG DriverOptsCount;
530 - [unique, size_is(LabelsCount)] const WSLCLabel* Labels;
531 - ULONG LabelsCount;
532 -} WSLCNetworkOptions;
533 -
534 -typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1];
535 -
536 -typedef struct _WSLCNetworkInformation
537 -{
538 - char Name[WSLC_MAX_NETWORK_NAME_LENGTH + 1];
539 - char Id[WSLC_CONTAINER_ID_LENGTH + 1];
540 - char Driver[64];
541 -} WSLCNetworkInformation;
542 -
543 -typedef struct _WSLCPruneContainersResults
544 -{
545 - [unique, size_is(ContainersCount)] WSLCContainerId* Containers;
546 - ULONG ContainersCount;
547 - ULONGLONG SpaceReclaimed;
548 -} WSLCPruneContainersResults;
549 -
550 -typedef struct _WSLCListContainersOptions
551 -{
552 - DWORD Flags; // WSLCListContainersFlags
553 - LONG Limit;
554 -
555 - [unique, size_is(FiltersCount)] const WSLCFilter* Filters;
556 - ULONG FiltersCount;
557 -} WSLCListContainersOptions;
558 -
559 -// Settings for IWSLCSession::Initialize - passed from service to per-user process
560 -typedef struct _WSLCSessionInitSettings
561 -{
562 - ULONG SessionId;
563 - [unique] LPCWSTR CreatorProcessName;
564 - LPCWSTR DisplayName;
565 - LPCWSTR StoragePath;
566 - WSLCSessionStorageFlags StorageFlags;
567 - ULONGLONG MaximumStorageSizeMb;
568 - ULONG SwapSizeMb;
569 - ULONG BootTimeoutMs;
570 - WSLCNetworkingMode NetworkingMode;
571 - WSLCFeatureFlags FeatureFlags;
572 - [unique] LPCSTR RootVhdTypeOverride;
573 -} WSLCSessionInitSettings;
574 -
575 -[
576 - uuid(EF0661E4-6364-40EA-B433-E2FDF11F3519),
577 - pointer_default(unique),
578 - object
579 -]
580 -interface IWSLCSession : IUnknown
581 -{
582 - HRESULT GetId([out] ULONG* Id);
583 - HRESULT GetDisplayName([out] LPWSTR* DisplayName);
584 - HRESULT GetState([out] WSLCSessionState* State);
585 -
586 - // Returns a one-off event that is signaled when the session terminates, whether due to an
587 - // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the
588 - // caller and remains valid (and observes the signaled state) even after the session is released.
589 - HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
590 -
591 - // Returns the cached termination reason and details. These are only available after the
592 - // termination event has been signaled; before that the call fails.
593 - HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details);
594 -
595 - // Image management.
596 - HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
597 - HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
598 - HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback);
599 - HRESULT ImportImage([in] WSLCHandle ImageHandle, [in, unique] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback, [out] LPSTR* ImageId);
600 - HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
601 - HRESULT SaveImages([in] WSLCHandle OutputHandle, [in] const WSLCStringArray* ImageNames, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
602 - HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count);
603 - HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count);
604 - HRESULT TagImage([in] const WSLCTagImageOptions* Options);
605 - HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output);
606 - 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);
607 -
608 - // Container management.
609 - HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container);
610 - HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container);
611 - 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);
612 - HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result);
613 -
614 - // Create a process at the VM level. This is meant for debugging.
615 - HRESULT CreateRootNamespaceProcess([in, ref] LPCSTR Executable, [in, ref] const WSLCProcessOptions* Options, [in] ULONG TtyRows, [in] ULONG TtyColumns, [out] IWSLCProcess** Process, [out] int* Errno);
616 -
617 - // TODO: an OpenProcess() method can be added later if needed.
618 -
619 - // Disk management.
620 - HRESULT FormatVirtualDisk([in, ref] LPCWSTR Path);
621 -
622 - // Terminate the VM and containers.
623 - HRESULT Terminate();
624 -
625 - // Used only for testing. TODO: Think about moving them to a dedicated testing-only interface.
626 - HRESULT MountWindowsFolder([in, ref] LPCWSTR WindowsPath, [in, ref] LPCSTR LinuxPath, [in] BOOL ReadOnly);
627 - HRESULT UnmountWindowsFolder([in, ref] LPCSTR LinuxPath);
628 - HRESULT MapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort);
629 - HRESULT UnmapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort);
630 -
631 - // Session initialization - called by SYSTEM service after launching per-user process.
632 - // Returns a handle to this COM server process (used to add to job object).
633 - HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle);
634 -
635 - // Initializes the session with a VM factory. VMs are created through the factory.
636 - HRESULT Initialize(
637 - [in] const WSLCSessionInitSettings* Settings,
638 - [in] IWSLCVirtualMachineFactory* VmFactory,
639 - [in] IWSLCPluginNotifier* PluginNotifier,
640 - [in, unique] IWarningCallback* WarningCallback);
641 -
642 - // Volume management.
643 - HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo);
644 - HRESULT DeleteVolume([in] LPCSTR Name);
645 - HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count);
646 - HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output);
647 -
648 - HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken);
649 - HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
650 - 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);
651 -
652 - // Network management.
653 - HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback);
654 - HRESULT DeleteNetwork([in] LPCSTR Name);
655 - HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count);
656 - HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output);
657 - HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount);
658 -
659 - HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription);
660 -}
661 -
662 -//
663 -// IWSLCSessionReference - Weak reference to a session held by the SYSTEM service.
664 -// Stored in per-user process, allows service to check liveness and terminate sessions.
665 -// Session metadata (ID, name, etc.) is stored service-side in SessionEntry.
666 -//
667 -[
668 - uuid(B3A72F48-9D15-4E8A-A621-7C3E84F09B52),
669 - pointer_default(unique),
670 - object
671 -]
672 -interface IWSLCSessionReference : IUnknown
673 -{
674 - // Try to open the session. Fails if session was released or terminated.
675 - // Returns S_OK and a valid session if still alive.
676 - HRESULT OpenSession([out] IWSLCSession** Session);
677 -
678 - // Terminate the session if still alive.
679 - HRESULT Terminate();
680 -}
681 -
682 -//
683 -// IWSLCSessionFactory - Creates sessions in the per-user COM server process.
684 -// Called by the SYSTEM service via CoCreateInstanceAsUser.
685 -//
686 -[
687 - uuid(C4E8F291-3B5D-4A7C-9E12-8F6A4D2B7C91),
688 - pointer_default(unique),
689 - object
690 -]
691 -interface IWSLCSessionFactory : IUnknown
692 -{
693 - // Creates a new session and returns both the session interface and a service reference.
694 - HRESULT CreateSession(
695 - [in] const WSLCSessionInitSettings* Settings,
696 - [in] IWSLCVirtualMachineFactory* VmFactory,
697 - [in] IWSLCPluginNotifier* PluginNotifier,
698 - [in, unique] IWarningCallback* WarningCallback,
699 - [out] IWSLCSession** Session,
700 - [out] IWSLCSessionReference** ServiceRef);
701 -
702 - // Gets the process handle for adding to job object.
703 - HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle);
704 -}
705 -
706 -typedef struct _WSLCSessionListEntry
707 -{
708 - ULONG SessionId;
709 - DWORD CreatorPid;
710 - wchar_t DisplayName[256];
711 - wchar_t Sid[256 + 1]; // MAX_SID_SIZE = 256
712 -} WSLCSessionListEntry;
713 -
714 -[
715 - uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760),
716 - pointer_default(unique),
717 - object
718 -]
719 -interface IWSLCSessionManager : IUnknown
720 -{
721 - HRESULT GetVersion([out] WSLCVersion* Version);
722 - // Session management.
723 - HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session);
724 - HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session);
725 - HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount);
726 - HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session);
727 - HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session);
728 -}
729 -
730 -// Ensure wslcsdk.h and wslcsdk.idl are also updated.
731 -cpp_quote("#define WSLC_E_BASE (0x0600)")
732 -cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */")
733 -cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */")
734 -cpp_quote("#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */")
735 -cpp_quote("#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */")
736 -cpp_quote("#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */")
737 -cpp_quote("#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */")
738 -cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */")
739 -cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */")
740 -cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */")
741 -cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */")
742 -cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */")
743 -cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */")
744 -cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */")
745 -cpp_quote("#define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */")
746 -cpp_quote("#define WSLC_E_SESSION_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 15) /* 0x8004060F */")
1 +/*++
2 +
3 +Copyright (c) Microsoft Corporation. All rights reserved.
4 +
5 +Module Name:
6 +
7 + wslc.idl
8 +
9 +Abstract:
10 +
11 + This file contains the WSLC-related COM object definitions.
12 + // N.B. ABI breaking changes in this file are OK, since both client & server always ship together.
13 + // The WSLC SDK must not use this file, and instead use WSLCCompat.idl
14 +
15 +--*/
16 +
17 +import "unknwn.idl";
18 +import "wtypes.idl";
19 +
20 +// Enums and flags shared with the SDK-facing API (WSLCCompat.idl).
21 +import "WSLCShared.idl";
22 +
23 +cpp_quote("#ifdef __cplusplus")
24 +cpp_quote("class DECLSPEC_UUID(\"a9b7a1b9-0671-405c-95f1-e0612cb4ce8f\") WSLCSessionManager;")
25 +cpp_quote("class DECLSPEC_UUID(\"9fcd2067-9fc6-4efa-9eb0-698169ebf7d3\") WSLCSessionFactory;")
26 +cpp_quote("#endif")
27 +
28 +#define WSLC_MAX_CONTAINER_NAME_LENGTH 255
29 +#define WSLC_MAX_IMAGE_NAME_LENGTH 255
30 +#define WSLC_MAX_VOLUME_NAME_LENGTH 255
31 +#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255
32 +#define WSLC_MAX_NETWORK_NAME_LENGTH 255
33 +#define WSLC_CONTAINER_ID_LENGTH 64
34 +#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45
35 +#define WSLC_EPHEMERAL_PORT 0
36 +#define WSLC_MAX_SAVE_IMAGES_COUNT 256
37 +
38 +cpp_quote("#define WSLC_MAX_CONTAINER_NAME_LENGTH 255")
39 +cpp_quote("#define WSLC_MAX_IMAGE_NAME_LENGTH 255")
40 +cpp_quote("#define WSLC_MAX_VOLUME_NAME_LENGTH 255")
41 +cpp_quote("#define WSLC_MAX_VOLUME_DRIVER_LENGTH 255")
42 +cpp_quote("#define WSLC_MAX_NETWORK_NAME_LENGTH 255")
43 +cpp_quote("#define WSLC_CONTAINER_ID_LENGTH 64")
44 +cpp_quote("#define WSLC_MAX_BINDING_ADDRESS_LENGTH 45")
45 +cpp_quote("#define WSLC_MAX_SAVE_IMAGES_COUNT 256")
46 +cpp_quote("#define WSLC_EPHEMERAL_PORT 0")
47 +cpp_quote("#define WSLC_STOP_TIMEOUT_DEFAULT LONG_MIN") // Pass to Stop() to use the default container stop timeout
48 +cpp_quote("#define WSLC_STOP_TIMEOUT_NONE -1") // Wait forever for the container to stop.
49 +
50 +typedef
51 +struct _WSLCVersion {
52 + ULONG Major;
53 + ULONG Minor;
54 + ULONG Revision;
55 +} WSLCVersion;
56 +
57 +[
58 + uuid(8C5A7B14-9D26-4FAE-AB31-7E5BC23F4801),
59 + pointer_default(unique),
60 + object
61 +]
62 +interface ICrashDumpCallback : IUnknown
63 +{
64 + HRESULT OnCrashDump(
65 + [in, string] LPCWSTR DumpPath,
66 + [in, unique, string] LPCSTR ProcessName,
67 + [in] ULONG Pid,
68 + [in] ULONG Signal,
69 + [in] ULONGLONG Timestamp);
70 +};
71 +
72 +[
73 + uuid(5038842F-53DB-4F30-A6D0-A41B02C94AC1),
74 + pointer_default(unique),
75 + object
76 +]
77 +interface IProgressCallback : IUnknown
78 +{
79 + HRESULT OnProgress(LPCSTR Status, LPCSTR Id, ULONGLONG Current, ULONGLONG Total);
80 +};
81 +
82 +[
83 + uuid(8153ED5D-8ABB-408B-ADBE-C0F3B13E07C3),
84 + pointer_default(unique),
85 + object
86 +]
87 +interface IWarningCallback : IUnknown
88 +{
89 + HRESULT OnWarning([in, string] LPCWSTR Message);
90 +};
91 +
92 +[
93 + uuid(F3E6D5B2-1D40-4E8B-9C39-7A45D1C0F8A2),
94 + pointer_default(unique),
95 + object
96 +]
97 +interface IWSLCPluginNotifier : IUnknown
98 +{
99 + // 'InspectJson' follows the wslc_schema::InspectContainer format.
100 + // Returning failure prevents the container creation.
101 + HRESULT OnContainerStarted([in] LPCSTR InspectJson);
102 +
103 + // Called when a container is about to stop. 'ContainerId' is the container identifier. Errors are logged but ignored.
104 + HRESULT OnContainerStopping([in] LPCSTR ContainerId);
105 +
106 + // 'InspectJson' follows the wslc_schema::InspectImage format. Errors are logged but ignored.
107 + HRESULT OnImageCreated([in] LPCSTR InspectJson);
108 +
109 + // Called when an image is deleted. 'ImageId' is the image identifier. Errors are logged but ignored.
110 + HRESULT OnImageDeleted([in] LPCSTR ImageId);
111 +};
112 +
113 +typedef struct _WSLCImageInformation
114 +{
115 + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
116 + char Hash[256];
117 + char Digest[256];
118 + LONGLONG Size; // Matches Docker's int64 image size
119 + LONGLONG Created; // Unix timestamp
120 + char ParentId[256];
121 +} WSLCImageInformation;
122 +
123 +typedef struct _KeyValuePairInformation
124 +{
125 + [string] LPSTR Key;
126 + [string] LPSTR Value;
127 +} KeyValuePairInformation;
128 +
129 +typedef struct _KeyValuePair
130 +{
131 + [string] LPCSTR Key;
132 + [string] LPCSTR Value;
133 +} KeyValuePair;
134 +
135 +typedef KeyValuePair WSLCLabel;
136 +typedef KeyValuePair WSLCDriverOption;
137 +typedef KeyValuePair WSLCFilter;
138 +
139 +typedef KeyValuePairInformation WSLCLabelInformation;
140 +typedef KeyValuePairInformation WSLCDriverOptionInformation;
141 +
142 +typedef struct _WSLCListImagesOptions
143 +{
144 + DWORD Flags; // WSLCListImagesFlags (can combine with bitwise OR)
145 + [unique, size_is(FiltersCount)] const WSLCFilter* Filters;
146 + ULONG FiltersCount;
147 +} WSLCListImagesOptions;
148 +
149 +typedef struct _WSLCStringArray
150 +{
151 + [unique, size_is(Count)] LPCSTR const* Values;
152 + ULONG Count;
153 +} WSLCStringArray;
154 +
155 +typedef struct _WSLCProcessOptions
156 +{
157 + [unique] LPCSTR CurrentDirectory;
158 + [unique] LPCSTR User;
159 + WSLCStringArray CommandLine;
160 + WSLCStringArray Environment;
161 + WSLCProcessFlags Flags;
162 +} WSLCProcessOptions;
163 +
164 +typedef struct _WSLCProcessStartOptions
165 +{
166 + ULONG TtyRows; // Only needed when tty fd's are passed.
167 + ULONG TtyColumns;
168 + [unique, string] LPCSTR DetachKeys;
169 +} WSLCProcessStartOptions;
170 +
171 +typedef struct _WSLCNamedVolume
172 +{
173 + LPCSTR Name;
174 + LPCSTR ContainerPath;
175 + BOOL ReadOnly;
176 +} WSLCNamedVolume;
177 +
178 +typedef struct _WSLCVolume
179 +{
180 + LPCWSTR HostPath;
181 + LPCSTR ContainerPath;
182 + BOOL ReadOnly;
183 +} WSLCVolume;
184 +
185 +typedef struct _WSLCPortMapping
186 +{
187 + USHORT HostPort;
188 + USHORT ContainerPort;
189 + int Family;
190 + int Protocol;
191 + char BindingAddress[WSLC_MAX_BINDING_ADDRESS_LENGTH + 1];
192 +} WSLCPortMapping;
193 +
194 +typedef struct _WSLCTmpfsMount
195 +{
196 + LPCSTR Destination;
197 + [unique] LPCSTR Options;
198 +} WSLCTmpfsMount;
199 +
200 +typedef struct _WSLCUlimit
201 +{
202 + [string] LPCSTR Name;
203 + LONGLONG Soft;
204 + LONGLONG Hard;
205 +} WSLCUlimit;
206 +
207 +typedef struct _WSLCNetworkConnection
208 +{
209 + [string] LPCSTR NetworkName;
210 + [unique, size_is(SettingsCount)] const KeyValuePair* Settings;
211 + ULONG SettingsCount;
212 +} WSLCNetworkConnection;
213 +
214 +// Options for IWSLCContainer::ConnectToNetwork.
215 +typedef struct _WSLCNetworkConnectionOptions
216 +{
217 + [unique] LPCSTR NetworkName;
218 + [unique] LPCSTR ContainerIpAddress; // Reserved for future --ip support; must be NULL today.
219 +} WSLCNetworkConnectionOptions;
220 +
221 +typedef struct _WSLCContainerNetwork
222 +{
223 + [unique, string] LPCSTR NetworkMode;
224 +
225 + [unique, size_is(NetworksCount)] const WSLCNetworkConnection* Networks;
226 + ULONG NetworksCount;
227 +
228 + // Settings for the primary endpoint (the network identified by NetworkMode).
229 + // KVP-encoded; duplicate keys are allowed (e.g., multiple "Aliases" entries).
230 + [unique, size_is(SettingsCount)] const KeyValuePair* Settings;
231 + ULONG SettingsCount;
232 +} WSLCContainerNetwork;
233 +
234 +typedef struct _WSLCContainerOptions
235 +{
236 + LPCSTR Image;
237 + [unique] LPCSTR Name;
238 + WSLCStringArray Entrypoint;
239 + WSLCProcessOptions InitProcessOptions;
240 + [unique, size_is(VolumesCount)] WSLCVolume* Volumes;
241 + ULONG VolumesCount;
242 + [unique, size_is(PortsCount)] WSLCPortMapping* Ports;
243 + ULONG PortsCount;
244 + [unique, size_is(LabelsCount)] const WSLCLabel* Labels;
245 + ULONG LabelsCount;
246 + WSLCContainerFlags Flags;
247 + WSLCSignal StopSignal;
248 + // TODO: List specific GPU devices.
249 + [unique] LPCSTR HostName;
250 + [unique] LPCSTR DomainName;
251 +
252 + WSLCStringArray DnsServers;
253 + WSLCStringArray DnsSearchDomains;
254 + WSLCStringArray DnsOptions;
255 +
256 + LONGLONG ShmSize; // Matches Docker's int64 ShmSize; consistent with MemoryBytes/NanoCpus
257 + WSLCContainerNetwork ContainerNetwork;
258 + [unique, size_is(TmpfsCount)] const WSLCTmpfsMount* Tmpfs;
259 + ULONG TmpfsCount;
260 +
261 + [unique, size_is(NamedVolumesCount)] WSLCNamedVolume* NamedVolumes;
262 + ULONG NamedVolumesCount;
263 +
264 + LONGLONG MemoryBytes;
265 + LONGLONG NanoCpus;
266 + [unique, size_is(UlimitsCount)] const WSLCUlimit* Ulimits;
267 + ULONG UlimitsCount;
268 +
269 + // Ignored unless WSLCContainerFlagsStopTimeout is set in Flags.
270 + LONG StopTimeout;
271 +} WSLCContainerOptions;
272 +
273 +typedef char WSLCContainerId[WSLC_CONTAINER_ID_LENGTH + 1] ;
274 +
275 +typedef struct _WSLCContainerEntry
276 +{
277 + char Name[WSLC_MAX_CONTAINER_NAME_LENGTH + 1];
278 + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
279 + WSLCContainerId Id;
280 + ULONGLONG StateChangedAt;
281 + ULONGLONG CreatedAt;
282 + WSLCContainerState State;
283 +} WSLCContainerEntry;
284 +
285 +typedef struct _WSLCContainerPortMapping
286 +{
287 + WSLCContainerId Id;
288 + WSLCPortMapping PortMapping;
289 +} WSLCContainerPortMapping;
290 +
291 +typedef [system_handle(sh_file)] HANDLE FILE_HANDLE;
292 +typedef [system_handle(sh_pipe)] HANDLE PIPE_HANDLE;
293 +typedef [system_handle(sh_socket)] HANDLE SOCKET_HANDLE;
294 +
295 +typedef struct _WSLCHandle
296 +{
297 + WSLCHandleType Type;
298 +
299 + [switch_type(WSLCHandleType), switch_is(Type)]
300 + union
301 + {
302 + [case(WSLCHandleTypeFile)]
303 + FILE_HANDLE File;
304 + [case(WSLCHandleTypePipe)]
305 + PIPE_HANDLE Pipe;
306 + [case(WSLCHandleTypeSocket)]
307 + SOCKET_HANDLE Socket;
308 + [default];
309 + } Handle;
310 +} WSLCHandle;
311 +
312 +[
313 + uuid(1AD163CD-393D-4B33-83A2-8A3F3F23E608),
314 + pointer_default(unique),
315 + object
316 +]
317 +interface IWSLCProcess : IUnknown
318 +{
319 + HRESULT Signal([in] int Signal);
320 + HRESULT GetExitEvent([out, system_handle(sh_event)] HANDLE* EventHandle);
321 + HRESULT GetStdHandle([in] WSLCFD Fd, [out] WSLCHandle* Handle);
322 + HRESULT GetFlags([out] WSLCProcessFlags* Flags);
323 + HRESULT GetPid([out] int* Pid);
324 + HRESULT GetState([out] WSLCProcessState* State, [out] int* Code);
325 + HRESULT ResizeTty([in] ULONG Rows, [in] ULONG Columns);
326 +
327 + // Note: the SDK can offer a convenience Wait() method, but that doesn't need to be part of the service API.
328 +}
329 +
330 +//
331 +// Values discovered from the guest kernel after the VM has booted, forwarded
332 +// from wslcsession via IWSLCVirtualMachine::ApplyGuestCapabilities. Add new
333 +// fields here instead of new IDL methods so the interface does not need a new
334 +// IID for each kernel-published value.
335 +//
336 +typedef struct _WSLCGuestCapabilities
337 +{
338 + // (base, size) of the hv_pci swiotlb pool the kernel reserved and
339 + // published under /sys/bus/vmbus/drivers/hv_pci/swiotlb_{base,size}.
340 + // Both zero means the running kernel does not support hv_pci swiotlb.
341 + UINT64 HvPciSwiotlbBase;
342 + UINT64 HvPciSwiotlbSize;
343 +} WSLCGuestCapabilities;
344 +
345 +//
346 +// IWSLCVirtualMachine - Interface representing a single VM instance.
347 +// Operations are scoped to this VM. The VM ID is stored internally,
348 +// so only the holder of this interface can operate on the VM.
349 +//
350 +[
351 + uuid(B5E2D8F1-9A3C-4E6B-8D1F-7C4A2E9B6D3A),
352 + pointer_default(unique),
353 + object
354 +]
355 +interface IWSLCVirtualMachine : IUnknown
356 +{
357 + // Gets the VM ID.
358 + HRESULT GetId([out, retval] GUID* VmId);
359 +
360 + // Accepts a connect from mini_init in the VM.
361 + HRESULT AcceptConnection([out, system_handle(sh_socket)] HANDLE* Socket);
362 +
363 + // Configures networking engine with sockets from the user process.
364 + // GnsSocket is required; DnsSocket is optional (NULL if DNS tunneling is disabled).
365 + // The service duplicates the socket handles.
366 + HRESULT ConfigureNetworking(
367 + [in, system_handle(sh_socket)] HANDLE GnsSocket,
368 + [in, system_handle(sh_socket), unique] HANDLE* DnsSocket);
369 +
370 + // Attaches a VHD or VHDX disk to the VM.
371 + // GrantVmAccess is called by the service before attaching.
372 + // Returns the SCSI LUN assigned to the disk.
373 + HRESULT AttachDisk([in] LPCWSTR Path, [in] BOOL ReadOnly, [out, retval] ULONG* Lun);
374 +
375 + // Detaches a previously attached disk from the VM.
376 + HRESULT DetachDisk([in] ULONG Lun);
377 +
378 + // Adds a filesystem share (Plan9 or VirtioFS) accessible to the VM.
379 + // Returns an instance GUID that can be used to remove the share.
380 + HRESULT AddShare([in] LPCWSTR WindowsPath, [in] BOOL ReadOnly, [out, retval] GUID* ShareId);
381 +
382 + // Removes a previously added filesystem share.
383 + HRESULT RemoveShare([in] REFGUID ShareId);
384 +
385 + // Configures the per-VM state discovered from the guest kernel after boot
386 + // (currently the hv_pci swiotlb pool). Subsequent calls to AddShare and
387 + // ConfigureNetworking forward these values to wsldevicehost via the
388 + // swiotlb device-options token. A capabilities struct whose fields are
389 + // all zero means the guest kernel does not support the feature; the
390 + // token is then omitted.
391 + HRESULT ApplyGuestCapabilities([in] const WSLCGuestCapabilities* Capabilities);
392 +
393 + // Returns an event that is signaled when the VM exits (graceful or forced).
394 + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
395 +
396 + HRESULT MapVirtioNetPort(
397 + [in] USHORT HostPort,
398 + [in] USHORT GuestPort,
399 + [in] int Protocol,
400 + [in] LPCSTR ListenAddress,
401 + [out, retval] USHORT* AllocatedHostPort);
402 +
403 + // Unmaps a port previously mapped via MapVirtioNetPort.
404 + HRESULT UnmapVirtioNetPort(
405 + [in] USHORT HostPort,
406 + [in] USHORT GuestPort,
407 + [in] int Protocol,
408 + [in] LPCSTR ListenAddress);
409 +
410 + // Returns the cached termination reason and details. These are only available after the
411 + // termination event has been signaled; before that the call fails.
412 + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details);
413 +}
414 +
415 +//
416 +// IWSLCVirtualMachineFactory - Creates VMs on demand for a session.
417 +//
418 +// Held by the per-user session process and implemented by the SYSTEM service.
419 +// This lets the session create a fresh VM at any time (e.g. to recreate a VM that
420 +// was idle-terminated when it had no running containers), instead of the service
421 +// eagerly creating a single VM up front. Each successful call returns a new VM whose
422 +// lifetime is owned by the caller: releasing the IWSLCVirtualMachine tears it down.
423 +//
424 +[
425 + uuid(2E3C9A41-7D58-4B6E-9F12-6C4A2E9B6D3B),
426 + pointer_default(unique),
427 + object
428 +]
429 +interface IWSLCVirtualMachineFactory : IUnknown
430 +{
431 + // Creates a new VM using the settings captured at session creation time.
432 + HRESULT CreateVirtualMachine([out] IWSLCVirtualMachine** Vm);
433 +}
434 +
435 +// Settings for IWSLCSessionManager::CreateSession - full session configuration
436 +typedef struct _WSLCSessionSettings {
437 + LPCWSTR DisplayName;
438 + LPCWSTR StoragePath;
439 + ULONGLONG MaximumStorageSizeMb;
440 + ULONG CpuCount;
441 + ULONG MemoryMb;
442 + ULONG BootTimeoutMs;
443 + WSLCNetworkingMode NetworkingMode;
444 + WSLCFeatureFlags FeatureFlags;
445 + WSLCHandle DmesgOutput;
446 + WSLCSessionStorageFlags StorageFlags;
447 +
448 + // Below options are used for debugging purposes only.
449 + [unique] LPCWSTR RootVhdOverride;
450 + [unique] LPCSTR RootVhdTypeOverride;
451 +} WSLCSessionSettings;
452 +
453 +
454 +[
455 + uuid(7577FE8D-DE85-471E-B870-11669986F332),
456 + pointer_default(unique),
457 + object
458 +]
459 +interface IWSLCContainer : IUnknown
460 +{
461 + HRESULT Attach([in, unique] LPCSTR DetachKeys, [out] WSLCHandle* StdIn, [out] WSLCHandle* StdOut, [out] WSLCHandle* StdErr);
462 + HRESULT Stop([in] WSLCSignal Signal, [in] LONG TimeoutSeconds);
463 + HRESULT Start([in] WSLCContainerStartFlags Flags, [in, unique] const WSLCProcessStartOptions* StartOptions, [in, unique] IWarningCallback* WarningCallback);
464 + HRESULT Delete([in] WSLCDeleteFlags Flags);
465 + HRESULT Export([in] WSLCHandle TarHandle);
466 + HRESULT GetState([out] WSLCContainerState* State);
467 + HRESULT GetInitProcess([out] IWSLCProcess** Process);
468 + HRESULT Exec([in, ref] const WSLCProcessOptions* Options, [in, unique] const WSLCProcessStartOptions* StartOptions, [out] IWSLCProcess** Process);
469 + HRESULT Inspect([out] LPSTR* Output);
470 + HRESULT Logs([in] WSLCLogsFlags Flags, [out] WSLCHandle* Stdout, [out] WSLCHandle* Stderr, [in] ULONGLONG Since, [in] ULONGLONG Until, [in] ULONGLONG Tail);
471 + HRESULT GetId([out, string] WSLCContainerId Id);
472 + HRESULT GetName([out, string] LPSTR* Name);
473 + HRESULT GetLabels([out, size_is(, *Count)] WSLCLabelInformation** Labels, [out] ULONG* Count);
474 + HRESULT Kill([in] WSLCSignal Signal);
475 + HRESULT Stats([out] LPSTR* Output);
476 + HRESULT ConnectToNetwork([in] const WSLCNetworkConnectionOptions* Options);
477 + HRESULT DisconnectFromNetwork([in] LPCSTR NetworkName);
478 +}
479 +
480 +typedef struct _WSLCDeletedImageInformation
481 +{
482 + char Image[WSLC_MAX_IMAGE_NAME_LENGTH + 1];
483 + WSLCDeletedImageType Type;
484 +} WSLCDeletedImageInformation;
485 +
486 +typedef struct _WSLCDeleteImageOptions
487 +{
488 + LPCSTR Image; // Image can be ID or Repo:Tag.
489 + DWORD Flags; // WSLCDeleteImageFlags
490 + // TODO: Platforms: a json array of OCI platform strings.
491 +} WSLCDeleteImageOptions;
492 +
493 +typedef struct _WSLCBuildImageOptions
494 +{
495 + LPCWSTR ContextPath;
496 + WSLCHandle DockerfileHandle;
497 + WSLCStringArray Tags;
498 + WSLCStringArray BuildArgs; // KEY=VALUE pairs passed as --build-arg to docker.
499 + LPCSTR Target; // Target build stage name passed as --target to docker.
500 + WSLCBuildImageFlags Flags; // WSLCBuildImageFlags
501 + WSLCStringArray Labels; // KEY=VALUE pairs passed as --label to docker.
502 +} WSLCBuildImageOptions;
503 +
504 +typedef struct _WSLCTagImageOptions
505 +{
506 + LPCSTR Image; // Source image name or ID.
507 + LPCSTR Repo; // Target repository name.
508 + LPCSTR Tag; // Target tag name.
509 +} WSLCTagImageOptions;
510 +
511 +typedef struct _WSLCVolumeOptions
512 +{
513 + [unique] LPCSTR Name;
514 + [unique] LPCSTR Driver;
515 + [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts;
516 + ULONG DriverOptsCount;
517 + [unique, size_is(LabelsCount)] const WSLCLabel* Labels;
518 + ULONG LabelsCount;
519 +} WSLCVolumeOptions;
520 +
521 +typedef char WSLCVolumeName[WSLC_MAX_VOLUME_NAME_LENGTH + 1];
522 +
523 +typedef struct _WSLCVolumeInformation
524 +{
525 + WSLCVolumeName Name;
526 + char Driver[WSLC_MAX_VOLUME_DRIVER_LENGTH + 1];
527 +} WSLCVolumeInformation;
528 +
529 +typedef struct _WSLCNetworkOptions
530 +{
531 + LPCSTR Name;
532 + [unique] LPCSTR Driver;
533 + [unique, size_is(DriverOptsCount)] const WSLCDriverOption* DriverOpts;
534 + ULONG DriverOptsCount;
535 + [unique, size_is(LabelsCount)] const WSLCLabel* Labels;
536 + ULONG LabelsCount;
537 +} WSLCNetworkOptions;
538 +
539 +typedef char WSLCNetworkName[WSLC_MAX_NETWORK_NAME_LENGTH + 1];
540 +
541 +typedef struct _WSLCNetworkInformation
542 +{
543 + char Name[WSLC_MAX_NETWORK_NAME_LENGTH + 1];
544 + char Id[WSLC_CONTAINER_ID_LENGTH + 1];
545 + char Driver[64];
546 +} WSLCNetworkInformation;
547 +
548 +typedef struct _WSLCPruneContainersResults
549 +{
550 + [unique, size_is(ContainersCount)] WSLCContainerId* Containers;
551 + ULONG ContainersCount;
552 + ULONGLONG SpaceReclaimed;
553 +} WSLCPruneContainersResults;
554 +
555 +typedef struct _WSLCListContainersOptions
556 +{
557 + DWORD Flags; // WSLCListContainersFlags
558 + LONG Limit;
559 +
560 + [unique, size_is(FiltersCount)] const WSLCFilter* Filters;
561 + ULONG FiltersCount;
562 +} WSLCListContainersOptions;
563 +
564 +// Settings for IWSLCSession::Initialize - passed from service to per-user process
565 +typedef struct _WSLCSessionInitSettings
566 +{
567 + ULONG SessionId;
568 + [unique] LPCWSTR CreatorProcessName;
569 + LPCWSTR DisplayName;
570 + LPCWSTR StoragePath;
571 + WSLCSessionStorageFlags StorageFlags;
572 + ULONGLONG MaximumStorageSizeMb;
573 + ULONG SwapSizeMb;
574 + ULONG BootTimeoutMs;
575 + WSLCNetworkingMode NetworkingMode;
576 + WSLCFeatureFlags FeatureFlags;
577 + [unique] LPCSTR RootVhdTypeOverride;
578 +} WSLCSessionInitSettings;
579 +
580 +[
581 + uuid(EF0661E4-6364-40EA-B433-E2FDF11F3519),
582 + pointer_default(unique),
583 + object
584 +]
585 +interface IWSLCSession : IUnknown
586 +{
587 + HRESULT GetId([out] ULONG* Id);
588 + HRESULT GetDisplayName([out] LPWSTR* DisplayName);
589 + HRESULT GetState([out] WSLCSessionState* State);
590 +
591 + // Returns a one-off event that is signaled when the session terminates, whether due to an
592 + // explicit Terminate() call or an unexpected VM exit. The returned handle is owned by the
593 + // caller and remains valid (and observes the signaled state) even after the session is released.
594 + HRESULT GetTerminationEvent([out, system_handle(sh_event)] HANDLE* Event);
595 +
596 + // Returns the cached termination reason and details. These are only available after the
597 + // termination event has been signaled; before that the call fails.
598 + HRESULT GetTerminationReason([out] WSLCVirtualMachineTerminationReason* Reason, [out] LPWSTR* Details);
599 +
600 + // Image management.
601 + HRESULT PullImage([in] LPCSTR Image, [in, unique] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
602 + HRESULT BuildImage([in] const WSLCBuildImageOptions* Options, [in, unique] IProgressCallback* ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
603 + HRESULT LoadImage([in] WSLCHandle ImageHandle, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback);
604 + HRESULT ImportImage([in] WSLCHandle ImageHandle, [in, unique] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength, [in, unique] IWarningCallback* WarningCallback, [out] LPSTR* ImageId);
605 + HRESULT SaveImage([in] WSLCHandle OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
606 + HRESULT SaveImages([in] WSLCHandle OutputHandle, [in] const WSLCStringArray* ImageNames, [in, unique] IProgressCallback * ProgressCallback, [in, unique, system_handle(sh_event)] HANDLE CancelEvent);
607 + HRESULT ListImages([in, unique] const WSLCListImagesOptions* Options, [out, size_is(, *Count)] WSLCImageInformation** Images, [out] ULONG* Count);
608 + HRESULT DeleteImage([in] const WSLCDeleteImageOptions* Options, [out, size_is(, *Count)] WSLCDeletedImageInformation** DeletedImages, [out] ULONG* Count);
609 + HRESULT TagImage([in] const WSLCTagImageOptions* Options);
610 + HRESULT InspectImage([in] LPCSTR ImageNameOrId, [out] LPSTR* Output);
611 + 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);
612 +
613 + // Container management.
614 + HRESULT CreateContainer([in] const WSLCContainerOptions* Options, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCContainer** Container);
615 + HRESULT OpenContainer([in, ref] LPCSTR Id, [out] IWSLCContainer** Container);
616 + 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);
617 + HRESULT PruneContainers([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out] WSLCPruneContainersResults* Result);
618 +
619 + // Create a process at the VM level. This is meant for debugging.
620 + HRESULT CreateRootNamespaceProcess([in, ref] LPCSTR Executable, [in, ref] const WSLCProcessOptions* Options, [in] ULONG TtyRows, [in] ULONG TtyColumns, [out] IWSLCProcess** Process, [out] int* Errno);
621 +
622 + // TODO: an OpenProcess() method can be added later if needed.
623 +
624 + // Disk management.
625 + HRESULT FormatVirtualDisk([in, ref] LPCWSTR Path);
626 +
627 + // Terminate the VM and containers.
628 + HRESULT Terminate();
629 +
630 + // Used only for testing. TODO: Think about moving them to a dedicated testing-only interface.
631 + HRESULT MountWindowsFolder([in, ref] LPCWSTR WindowsPath, [in, ref] LPCSTR LinuxPath, [in] BOOL ReadOnly);
632 + HRESULT UnmountWindowsFolder([in, ref] LPCSTR LinuxPath);
633 + HRESULT MapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort);
634 + HRESULT UnmapVmPort([in] int Family, [in] unsigned short WindowsPort, [in] unsigned short LinuxPort);
635 +
636 + // Session initialization - called by SYSTEM service after launching per-user process.
637 + // Returns a handle to this COM server process (used to add to job object).
638 + HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle);
639 +
640 + // Initializes the session with a VM factory. VMs are created through the factory.
641 + HRESULT Initialize(
642 + [in] const WSLCSessionInitSettings* Settings,
643 + [in] IWSLCVirtualMachineFactory* VmFactory,
644 + [in] IWSLCPluginNotifier* PluginNotifier,
645 + [in, unique] IWarningCallback* WarningCallback);
646 +
647 + // Volume management.
648 + HRESULT CreateVolume([in] const WSLCVolumeOptions* Options, [out] WSLCVolumeInformation* VolumeInfo);
649 + HRESULT DeleteVolume([in] LPCSTR Name);
650 + HRESULT ListVolumes([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *Count)] WSLCVolumeInformation** Volumes, [out] ULONG* Count);
651 + HRESULT InspectVolume([in] LPCSTR Name, [out] LPSTR* Output);
652 +
653 + HRESULT Authenticate([in] LPCSTR ServerAddress, [in] LPCSTR Username, [in] LPCSTR Password, [out] LPSTR* IdentityToken);
654 + HRESULT PushImage([in] LPCSTR Image, [in] LPCSTR RegistryAuthenticationInformation, [in, unique] IProgressCallback* ProgressCallback, [in, unique] IWarningCallback* WarningCallback);
655 + 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);
656 +
657 + // Network management.
658 + HRESULT CreateNetwork([in] const WSLCNetworkOptions* Options, [in, unique] IWarningCallback* WarningCallback);
659 + HRESULT DeleteNetwork([in] LPCSTR Name);
660 + HRESULT ListNetworks([out, size_is(, *Count)] WSLCNetworkInformation** Networks, [out] ULONG* Count);
661 + HRESULT InspectNetwork([in] LPCSTR Name, [out] LPSTR* Output);
662 + HRESULT PruneNetworks([in, unique, size_is(FiltersCount)] const WSLCFilter* Filters, [in] ULONG FiltersCount, [out, size_is(, *NetworksCount)] WSLCNetworkName** Networks, [out] ULONG* NetworksCount);
663 +
664 + HRESULT RegisterCrashDumpCallback([in] ICrashDumpCallback* Callback, [out] IUnknown** Subscription);
665 +}
666 +
667 +//
668 +// IWSLCSessionReference - Weak reference to a session held by the SYSTEM service.
669 +// Stored in per-user process, allows service to check liveness and terminate sessions.
670 +// Session metadata (ID, name, etc.) is stored service-side in SessionEntry.
671 +//
672 +[
673 + uuid(B3A72F48-9D15-4E8A-A621-7C3E84F09B52),
674 + pointer_default(unique),
675 + object
676 +]
677 +interface IWSLCSessionReference : IUnknown
678 +{
679 + // Try to open the session. Fails if session was released or terminated.
680 + // Returns S_OK and a valid session if still alive.
681 + HRESULT OpenSession([out] IWSLCSession** Session);
682 +
683 + // Terminate the session if still alive.
684 + HRESULT Terminate();
685 +}
686 +
687 +//
688 +// IWSLCSessionFactory - Creates sessions in the per-user COM server process.
689 +// Called by the SYSTEM service via CoCreateInstanceAsUser.
690 +//
691 +[
692 + uuid(C4E8F291-3B5D-4A7C-9E12-8F6A4D2B7C91),
693 + pointer_default(unique),
694 + object
695 +]
696 +interface IWSLCSessionFactory : IUnknown
697 +{
698 + // Creates a new session and returns both the session interface and a service reference.
699 + HRESULT CreateSession(
700 + [in] const WSLCSessionInitSettings* Settings,
701 + [in] IWSLCVirtualMachineFactory* VmFactory,
702 + [in] IWSLCPluginNotifier* PluginNotifier,
703 + [in, unique] IWarningCallback* WarningCallback,
704 + [out] IWSLCSession** Session,
705 + [out] IWSLCSessionReference** ServiceRef);
706 +
707 + // Gets the process handle for adding to job object.
708 + HRESULT GetProcessHandle([out, system_handle(sh_process)] HANDLE* ProcessHandle);
709 +}
710 +
711 +typedef struct _WSLCSessionListEntry
712 +{
713 + ULONG SessionId;
714 + DWORD CreatorPid;
715 + wchar_t DisplayName[256];
716 + wchar_t Sid[256 + 1]; // MAX_SID_SIZE = 256
717 +} WSLCSessionListEntry;
718 +
719 +[
720 + uuid(82A7ABC8-6B50-43FC-AB96-15FBBE7E8760),
721 + pointer_default(unique),
722 + object
723 +]
724 +interface IWSLCSessionManager : IUnknown
725 +{
726 + HRESULT GetVersion([out] WSLCVersion* Version);
727 + // Session management.
728 + HRESULT CreateSession([in, unique] const WSLCSessionSettings* Settings, WSLCSessionFlags Flags, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session);
729 + HRESULT EnterSession([in, ref] LPCWSTR DisplayName, [in, ref] LPCWSTR StoragePath, [in, unique] IWarningCallback* WarningCallback, [out] IWSLCSession** Session);
730 + HRESULT ListSessions([out, size_is(, *SessionsCount)] WSLCSessionListEntry** Sessions, [out] ULONG* SessionsCount);
731 + HRESULT OpenSession([in] ULONG Id, [out] IWSLCSession** Session);
732 + HRESULT OpenSessionByName([in, unique] LPCWSTR DisplayName, [out] IWSLCSession** Session);
733 +}
734 +
735 +// Ensure wslcsdk.h and wslcsdk.idl are also updated.
736 +cpp_quote("#define WSLC_E_BASE (0x0600)")
737 +cpp_quote("#define WSLC_E_IMAGE_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 1) /* 0x80040601 */")
738 +cpp_quote("#define WSLC_E_CONTAINER_PREFIX_AMBIGUOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 2) /* 0x80040602 */")
739 +cpp_quote("#define WSLC_E_CONTAINER_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 3) /* 0x80040603 */")
740 +cpp_quote("#define WSLC_E_VOLUME_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 4) /* 0x80040604 */")
741 +cpp_quote("#define WSLC_E_CONTAINER_NOT_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 5) /* 0x80040605 */")
742 +cpp_quote("#define WSLC_E_CONTAINER_IS_RUNNING MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 6) /* 0x80040606 */")
743 +cpp_quote("#define WSLC_E_SESSION_RESERVED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 7) /* 0x80040607 */")
744 +cpp_quote("#define WSLC_E_INVALID_SESSION_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 8) /* 0x80040608 */")
745 +cpp_quote("#define WSLC_E_NETWORK_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 9) /* 0x80040609 */")
746 +cpp_quote("#define WSLC_E_WU_SEARCH_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 10) /* 0x8004060A */")
747 +cpp_quote("#define WSLC_E_SDK_UPDATE_NEEDED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 11) /* 0x8004060B */")
748 +cpp_quote("#define WSLC_E_CONTAINER_DISABLED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 12) /* 0x8004060C */")
749 +cpp_quote("#define WSLC_E_REGISTRY_BLOCKED_BY_POLICY MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 13) /* 0x8004060D */")
750 +cpp_quote("#define WSLC_E_VOLUME_NOT_AVAILABLE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 14) /* 0x8004060E */")
751 +cpp_quote("#define WSLC_E_SESSION_NOT_FOUND MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, WSLC_E_BASE + 15) /* 0x8004060F */")
src/windows/wslc/arguments/ArgumentDefinitions.h
+1
@@ -103,6 +103,7 @@ _(StoragePath, "storage-path", NO_ALIAS, Kind::Positional, L
103 _(Signal, "signal", L"s", Kind::Value, Localization::WSLCCLI_SignalArgDescription()) \
104 _(Source, "source", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_SourceArgDescription()) \
105 _(StopSignal, "stop-signal", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopSignalArgDescription()) \
106 +_(StopTimeout, "stop-timeout", NO_ALIAS, Kind::Value, Localization::WSLCCLI_StopTimeoutArgDescription()) \
107 _(Tail, "tail", L"n", Kind::Value, Localization::WSLCCLI_TailArgDescription()) \
108 _(Tag, "tag", L"t", Kind::Value, Localization::WSLCCLI_TagArgDescription()) \
109 _(Target, "target", NO_ALIAS, Kind::Positional, Localization::WSLCCLI_TargetArgDescription()) \
src/windows/wslc/arguments/ArgumentValidation.cpp
+4
@@ -48,6 +48,10 @@ void Argument::Validate(const ArgMap& execArgs) const
48 validation::ValidateWSLCSignalFromString(execArgs.GetAll<ArgType::StopSignal>(), m_name);
49 break;
50
51 + case ArgType::StopTimeout:
52 + validation::ValidateIntegerFromString<long>(execArgs.GetAll<ArgType::StopTimeout>(), m_name);
53 + break;
54 +
55 case ArgType::ShmSize:
56 validation::ValidateMemorySize(execArgs.GetAll<ArgType::ShmSize>(), m_name);
57 break;
src/windows/wslc/commands/ContainerCreateCommand.cpp
+1
@@ -58,6 +58,7 @@ std::vector<Argument> ContainerCreateCommand::GetArguments() const
58 // Argument::Create(ArgType::Scheme),
59 Argument::Create(ArgType::ShmSize),
60 Argument::Create(ArgType::StopSignal),
61 + Argument::Create(ArgType::StopTimeout),
62 Argument::Create(ArgType::TMPFS, false, NO_LIMIT),
63 Argument::Create(ArgType::TTY),
64 Argument::Create(ArgType::Ulimit, false, NO_LIMIT),
src/windows/wslc/commands/ContainerRunCommand.cpp
+1
@@ -59,6 +59,7 @@ std::vector<Argument> ContainerRunCommand::GetArguments() const
59 // Argument::Create(ArgType::Scheme),
60 Argument::Create(ArgType::ShmSize),
61 Argument::Create(ArgType::StopSignal),
62 + Argument::Create(ArgType::StopTimeout),
63 Argument::Create(ArgType::TMPFS, false, NO_LIMIT),
64 Argument::Create(ArgType::TTY),
65 Argument::Create(ArgType::Ulimit, false, NO_LIMIT),
src/windows/wslc/services/ContainerModel.h
+2 -3
@@ -38,6 +38,7 @@ struct ContainerOptions
38 bool TTY = false;
39 bool PublishAll = false;
40 WSLCSignal StopSignal = WSLCSignalNone;
41 + std::optional<int> StopTimeout{};
42 std::optional<int64_t> ShmSize{};
43 bool Gpu = false;
44 std::vector<std::string> Ports;
@@ -67,10 +68,8 @@ struct CreateContainerResult
68
69 struct StopContainerOptions
70 {
70 - static constexpr LONG DefaultTimeout = -1;
71 -
71 WSLCSignal Signal = WSLCSignalNone;
73 - LONG Timeout = DefaultTimeout;
72 + LONG Timeout = WSLC_STOP_TIMEOUT_DEFAULT;
73 };
74
75 struct PruneContainersResult
src/windows/wslc/services/ContainerService.cpp
+5
@@ -136,6 +136,11 @@ static wsl::windows::common::RunningWSLCContainer CreateInternal(
136 containerLauncher.SetDefaultStopSignal(options.StopSignal);
137 }
138
139 + if (options.StopTimeout.has_value())
140 + {
141 + containerLauncher.SetStopTimeout(options.StopTimeout.value());
142 + }
143 +
144 if (options.ShmSize.has_value())
145 {
146 containerLauncher.SetShmSize(options.ShmSize.value());
src/windows/wslc/tasks/ContainerTasks.cpp
+5
@@ -443,6 +443,11 @@ void SetContainerOptionsFromArgs(CLIExecutionContext& context)
443 options.StopSignal = validation::GetWSLCSignalFromString(context.Args.Get<ArgType::StopSignal>());
444 }
445
446 + if (context.Args.Contains(ArgType::StopTimeout))
447 + {
448 + options.StopTimeout = validation::GetIntegerFromString<int>(context.Args.Get<ArgType::StopTimeout>());
449 + }
450 +
451 if (context.Args.Contains(ArgType::ShmSize))
452 {
453 options.ShmSize = validation::GetMemorySizeFromString(context.Args.Get<ArgType::ShmSize>());
src/windows/wslcsession/DockerHTTPClient.cpp
+1 -1
@@ -302,7 +302,7 @@ void DockerHTTPClient::StartContainer(const std::string& Id, const std::optional
302 Transaction(verb::post, url);
303 }
304
305 -void DockerHTTPClient::StopContainer(const std::string& Id, std::optional<WSLCSignal> Signal, std::optional<ULONG> TimeoutSeconds)
305 +void DockerHTTPClient::StopContainer(const std::string& Id, std::optional<WSLCSignal> Signal, std::optional<LONG> TimeoutSeconds)
306 {
307 auto url = URL::Create("/containers/{}/stop", Id);
308 if (Signal.has_value())
src/windows/wslcsession/DockerHTTPClient.h
+1 -1
@@ -127,7 +127,7 @@ public:
127 bool all = false, int limit = -1, const std::map<std::string, std::vector<std::string>>& filters = {});
128 common::docker_schema::CreatedContainer CreateContainer(const common::docker_schema::CreateContainer& Request, const std::optional<std::string>& Name);
129 void StartContainer(const std::string& Id, const std::optional<std::string>& DetachKeys);
130 - void StopContainer(const std::string& Id, std::optional<WSLCSignal> Signal, std::optional<ULONG> TimeoutSeconds);
130 + void StopContainer(const std::string& Id, std::optional<WSLCSignal> Signal, std::optional<LONG> TimeoutSeconds);
131 void DeleteContainer(const std::string& Id, bool Force, bool DeleteVolumes = false);
132 void SignalContainer(const std::string& Id, std::optional<WSLCSignal> Signal);
133 common::docker_schema::InspectContainer InspectContainer(const std::string& Id);
src/windows/wslcsession/WSLCContainer.cpp
+21 -3
@@ -67,6 +67,14 @@ using WslcInspectContainer = wsl::windows::common::wslc_schema::InspectContainer
67
68 namespace {
69
70 +void ValidateStopTimeout(LONG TimeoutSeconds, bool allowDefault)
71 +{
72 + THROW_HR_WITH_USER_ERROR_IF(
73 + E_INVALIDARG,
74 + Localization::MessageWslcInvalidStopTimeout(TimeoutSeconds),
75 + TimeoutSeconds < 0 && TimeoutSeconds != WSLC_STOP_TIMEOUT_NONE && (!allowDefault || TimeoutSeconds != WSLC_STOP_TIMEOUT_DEFAULT));
76 +}
77 +
78 std::vector<std::string> StringArrayToVector(const WSLCStringArray& array)
79 {
80 if (array.Count == 0)
@@ -944,6 +952,8 @@ void WSLCContainerImpl::Stop(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill)
952 SignalArg = Signal;
953 }
954
955 + ValidateStopTimeout(TimeoutSeconds, true);
956 +
957 // Don't wait for the container to stop if we're not sending SIGKILL, since it may not stop the container.
958 // N.B. If the signal was SIGTERM for instance, we'll receive the stop notification via OnEvent().
959 bool waitForStop = !Kill || (SignalArg.value_or(WSLCSignalSIGKILL) == WSLCSignalSIGKILL);
@@ -961,10 +971,10 @@ void WSLCContainerImpl::Stop(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill)
971 }
972 else
973 {
964 - std::optional<ULONG> TimeoutArg;
965 - if (TimeoutSeconds >= 0)
974 + std::optional<LONG> TimeoutArg;
975 + if (TimeoutSeconds != WSLC_STOP_TIMEOUT_DEFAULT)
976 {
967 - TimeoutArg = static_cast<ULONG>(TimeoutSeconds);
977 + TimeoutArg = TimeoutSeconds;
978 }
979
980 m_dockerClient.StopContainer(m_id, SignalArg, TimeoutArg);
@@ -1305,6 +1315,7 @@ WslcInspectContainer WSLCContainerImpl::BuildInspectContainer(const DockerInspec
1315 wslcInspect.Config.Entrypoint = dockerInspect.Config.Entrypoint;
1316 wslcInspect.Config.User = dockerInspect.Config.User;
1317 wslcInspect.Config.WorkingDir = dockerInspect.Config.WorkingDir;
1318 + wslcInspect.Config.StopTimeout = dockerInspect.Config.StopTimeout;
1319
1320 // Map WSLC port mappings (Windows host ports only).
1321 for (const auto& e : m_mappedPorts)
@@ -1419,6 +1430,13 @@ std::unique_ptr<WSLCContainerImpl> WSLCContainerImpl::Create(
1430 request.StopSignal = std::to_string(containerOptions.StopSignal);
1431 }
1432
1433 + if (WI_IsFlagSet(containerOptions.Flags, WSLCContainerFlagsStopTimeout))
1434 + {
1435 + ValidateStopTimeout(containerOptions.StopTimeout, false);
1436 +
1437 + request.StopTimeout = static_cast<int>(containerOptions.StopTimeout);
1438 + }
1439 +
1440 if (containerOptions.InitProcessOptions.CurrentDirectory != nullptr)
1441 {
1442 request.WorkingDir = containerOptions.InitProcessOptions.CurrentDirectory;
test/windows/WSLCTests.cpp
+63 -2
@@ -5573,7 +5573,7 @@ class WSLCTests
5573
5574 // Invalid container flags are rejected with E_INVALIDARG.
5575 options.Image = "debian:latest";
5576 - options.Flags = static_cast<WSLCContainerFlags>(0x10);
5576 + options.Flags = static_cast<WSLCContainerFlags>(0x20);
5577 VERIFY_ARE_EQUAL(E_INVALIDARG, m_defaultSession->CreateContainer(&options, nullptr, &container));
5578
5579 // Invalid init process flags are rejected with E_INVALIDARG.
@@ -5648,7 +5648,7 @@ class WSLCTests
5648 VERIFY_ARE_EQUAL(process.Wait(), WSLCSignalSIGHUP + 128);
5649 }
5650
5651 - // Validate that the default stop signal can be overriden.
5651 + // Validate that the default stop signal can be overridden.
5652 {
5653 WSLCContainerLauncher launcher("debian:latest", "test-stop-signal-2", {"/bin/cat"}, {}, {}, WSLCProcessFlagsStdin);
5654 launcher.SetDefaultStopSignal(WSLCSignalSIGHUP);
@@ -6233,6 +6233,67 @@ class WSLCTests
6233 expectContainerList({});
6234 }
6235
6236 + // test StopContainer with custom timeouts.
6237 + // N.B. We can't validate the actual timeouts since the tests environment will affect container stop times.
6238 + {
6239 + {
6240 + // Create a container with a no stop timeout.
6241 + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-1", {"sleep", "99999"});
6242 + launcher.SetStopTimeout(WSLC_STOP_TIMEOUT_NONE);
6243 +
6244 + auto container = launcher.Launch(*m_defaultSession);
6245 +
6246 + auto inspect = container.Inspect();
6247 + VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(0), WSLC_STOP_TIMEOUT_NONE);
6248 +
6249 + // Validate that passing '0' as the stop timeout overrides the default
6250 + VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalNone, 0));
6251 + }
6252 +
6253 + {
6254 + // Create a container with an instant stop timeout.
6255 + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-2", {"sleep", "99999"});
6256 + launcher.SetStopTimeout(0);
6257 +
6258 + auto container = launcher.Create(*m_defaultSession);
6259 +
6260 + auto inspect = container.Inspect();
6261 + VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(-1), 0);
6262 + }
6263 +
6264 + {
6265 + // Create a container with an short stop timeout.
6266 + WSLCContainerLauncher launcher("debian:latest", "test-container-stop-timeout-3", {"sleep", "99999"});
6267 + launcher.SetStopTimeout(1);
6268 +
6269 + auto container = launcher.Launch(*m_defaultSession);
6270 +
6271 + auto inspect = container.Inspect();
6272 + VERIFY_ARE_EQUAL(inspect.Config.StopTimeout.value_or(0), 1);
6273 +
6274 + auto initProcess = container.GetInitProcess();
6275 + std::thread stopThread([&]() { VERIFY_SUCCEEDED(container.Get().Stop(WSLCSignalNone, -1)); });
6276 +
6277 + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
6278 + // TODO: calling Kill() here hangs since Stop() holds the container lock.
6279 + // Update this once fixed to:
6280 + // LOG_IF_FAILED(container.Get().Kill(WSLCSignalSIGKILL));
6281 +
6282 + LOG_IF_FAILED(initProcess.Get().Signal(WSLCSignalSIGKILL));
6283 +
6284 + if (stopThread.joinable())
6285 + {
6286 + stopThread.join();
6287 + }
6288 + });
6289 +
6290 + // Wait for at least 2 seconds for the stop to complete to prove that the default 1 second timeout was correctly overridden.
6291 + auto waitResult = WaitForSingleObject(stopThread.native_handle(), 2000);
6292 +
6293 + VERIFY_ARE_EQUAL(waitResult, WAIT_TIMEOUT);
6294 + }
6295 + }
6296 +
6297 // Validate that Kill() works as expected
6298 {
6299 WSLCContainerLauncher launcher("debian:latest", "test-container-kill", {"sleep", "99999"}, {});
test/windows/wslc/e2e/WSLCE2EContainerCreateTests.cpp
+68
@@ -726,6 +726,73 @@ class WSLCE2EContainerCreateTests
726 VERIFY_ARE_EQUAL(ExpectedExitCode, inspect.State.ExitCode);
727 }
728
729 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopTimeout)
730 + {
731 + // A positive value is forwarded to the container configuration.
732 + {
733 + constexpr int ExpectedStopTimeout = 30;
734 + auto result = RunWslc(std::format(
735 + L"container create --stop-timeout {} --name {} {}", ExpectedStopTimeout, WslcContainerName, DebianImage.NameAndTag()));
736 + result.Verify({.Stderr = L"", .ExitCode = 0});
737 +
738 + const auto inspect = InspectContainer(WslcContainerName);
739 + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
740 + VERIFY_ARE_EQUAL(ExpectedStopTimeout, inspect.Config.StopTimeout.value());
741 + EnsureContainerDoesNotExist(WslcContainerName);
742 + }
743 +
744 + // A value of 0 (stop the container immediately) is a valid, explicit timeout.
745 + {
746 + auto result =
747 + RunWslc(std::format(L"container create --stop-timeout 0 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
748 + result.Verify({.Stderr = L"", .ExitCode = 0});
749 +
750 + const auto inspect = InspectContainer(WslcContainerName);
751 + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
752 + VERIFY_ARE_EQUAL(0, inspect.Config.StopTimeout.value());
753 + EnsureContainerDoesNotExist(WslcContainerName);
754 + }
755 +
756 + // A value of -1 means "no timeout"; it is a valid, explicit value forwarded to the configuration.
757 + {
758 + auto result =
759 + RunWslc(std::format(L"container create --stop-timeout -1 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
760 + result.Verify({.Stderr = L"", .ExitCode = 0});
761 +
762 + const auto inspect = InspectContainer(WslcContainerName);
763 + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
764 + VERIFY_ARE_EQUAL(-1, inspect.Config.StopTimeout.value());
765 + EnsureContainerDoesNotExist(WslcContainerName);
766 + }
767 +
768 + // When --stop-timeout is not specified, no timeout is forwarded to the container configuration.
769 + {
770 + auto result = RunWslc(std::format(L"container create --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
771 + result.Verify({.Stderr = L"", .ExitCode = 0});
772 +
773 + const auto inspect = InspectContainer(WslcContainerName);
774 + VERIFY_IS_FALSE(inspect.Config.StopTimeout.has_value());
775 + EnsureContainerDoesNotExist(WslcContainerName);
776 + }
777 + }
778 +
779 + WSLC_TEST_METHOD(WSLCE2E_Container_Create_StopTimeout_Invalid)
780 + {
781 + {
782 + auto result =
783 + RunWslc(std::format(L"container create --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
784 + result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1});
785 + VerifyContainerIsNotListed(WslcContainerName);
786 + }
787 +
788 + {
789 + auto result =
790 + RunWslc(std::format(L"container create --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
791 + result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
792 + VerifyContainerIsNotListed(WslcContainerName);
793 + }
794 + }
795 +
796 WSLC_TEST_METHOD(WSLCE2E_Container_Create_ShmSize)
797 {
798 auto result = RunWslc(
@@ -1225,6 +1292,7 @@ private:
1292 << L" --rm Remove the container after it stops\r\n"
1293 << L" --shm-size Size of /dev/shm (e.g. 64M, 1G)\r\n"
1294 << L" --stop-signal Signal to stop the container\r\n"
1295 + << L" --stop-timeout Timeout (in seconds) to stop the container before killing it (-1 for no timeout)\r\n"
1296 << L" --tmpfs Mount tmpfs to the container at the given path\r\n"
1297 << L" -t,--tty Open a TTY with the container process.\r\n"
1298 << L" --ulimit Ulimit options (format: <name>=<soft>[:<hard>], use -1 for unlimited)\r\n"
test/windows/wslc/e2e/WSLCE2EContainerRunTests.cpp
+80
@@ -1013,6 +1013,85 @@ class WSLCE2EContainerRunTests
1013 VERIFY_ARE_EQUAL(ExpectedExitCode, inspect.State.ExitCode);
1014 }
1015
1016 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout)
1017 + {
1018 + // A positive value is forwarded to the container configuration.
1019 + {
1020 + constexpr int ExpectedStopTimeout = 25;
1021 + auto result = RunWslc(std::format(
1022 + L"container run -d --stop-timeout {} --name {} {} sleep infinity",
1023 + ExpectedStopTimeout,
1024 + WslcContainerName,
1025 + DebianImage.NameAndTag()));
1026 + result.Verify({.Stderr = L"", .ExitCode = 0});
1027 +
1028 + const auto inspect = InspectContainer(WslcContainerName);
1029 + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
1030 + VERIFY_ARE_EQUAL(ExpectedStopTimeout, inspect.Config.StopTimeout.value());
1031 + EnsureContainerDoesNotExist(WslcContainerName);
1032 + }
1033 +
1034 + // A value of 0 (stop the container immediately) is a valid, explicit timeout.
1035 + {
1036 + auto result = RunWslc(std::format(
1037 + L"container run -d --stop-timeout 0 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
1038 + result.Verify({.Stderr = L"", .ExitCode = 0});
1039 +
1040 + const auto inspect = InspectContainer(WslcContainerName);
1041 + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
1042 + VERIFY_ARE_EQUAL(0, inspect.Config.StopTimeout.value());
1043 + EnsureContainerDoesNotExist(WslcContainerName);
1044 + }
1045 +
1046 + // A value of -1 means "no timeout"; it is a valid, explicit value forwarded to the configuration.
1047 + {
1048 + auto result = RunWslc(std::format(
1049 + L"container run -d --stop-timeout -1 --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
1050 + result.Verify({.Stderr = L"", .ExitCode = 0});
1051 +
1052 + const auto inspect = InspectContainer(WslcContainerName);
1053 + VERIFY_IS_TRUE(inspect.Config.StopTimeout.has_value());
1054 + VERIFY_ARE_EQUAL(-1, inspect.Config.StopTimeout.value());
1055 + EnsureContainerDoesNotExist(WslcContainerName);
1056 + }
1057 +
1058 + // When --stop-timeout is not specified, no timeout is forwarded to the container configuration.
1059 + {
1060 + auto result =
1061 + RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
1062 + result.Verify({.Stderr = L"", .ExitCode = 0});
1063 +
1064 + const auto inspect = InspectContainer(WslcContainerName);
1065 + VERIFY_IS_FALSE(inspect.Config.StopTimeout.has_value());
1066 + EnsureContainerDoesNotExist(WslcContainerName);
1067 + }
1068 + }
1069 +
1070 + WSLC_TEST_METHOD(WSLCE2E_Container_Run_StopTimeout_Invalid)
1071 + {
1072 + {
1073 + auto result =
1074 + RunWslc(std::format(L"container run --rm --stop-timeout abc --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
1075 + result.Verify({.Stderr = L"Invalid stop-timeout argument value: abc\r\n", .ExitCode = 1});
1076 + EnsureContainerDoesNotExist(WslcContainerName);
1077 + }
1078 +
1079 + {
1080 + auto result =
1081 + RunWslc(std::format(L"container run --rm --stop-timeout -2 --name {} {}", WslcContainerName, DebianImage.NameAndTag()));
1082 + result.Verify({.Stderr = L"Invalid stop timeout value: -2\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
1083 + EnsureContainerDoesNotExist(WslcContainerName);
1084 + }
1085 +
1086 + // Validate that the correct error is displayed if the user passes the exact 'WSLC_STOP_TIMEOUT_DEFAULT' value.
1087 + {
1088 + auto result = RunWslc(std::format(
1089 + L"container run --rm --stop-timeout {} --name {} {}", WSLC_STOP_TIMEOUT_DEFAULT, WslcContainerName, DebianImage.NameAndTag()));
1090 + result.Verify({.Stderr = L"Invalid stop timeout value: -2147483648\r\nError code: E_INVALIDARG\r\n", .ExitCode = 1});
1091 + EnsureContainerDoesNotExist(WslcContainerName);
1092 + }
1093 + }
1094 +
1095 WSLC_TEST_METHOD(WSLCE2E_Container_Run_ShmSize)
1096 {
1097 auto result = RunWslc(std::format(L"container run --rm --shm-size 128M {} df -h /dev/shm", DebianImage.NameAndTag()));
@@ -1219,6 +1298,7 @@ private:
1298 << L" --rm Remove the container after it stops\r\n"
1299 << L" --shm-size Size of /dev/shm (e.g. 64M, 1G)\r\n"
1300 << L" --stop-signal Signal to stop the container\r\n"
1301 + << L" --stop-timeout Timeout (in seconds) to stop the container before killing it (-1 for no timeout)\r\n"
1302 << L" --tmpfs Mount tmpfs to the container at the given path\r\n"
1303 << L" -t,--tty Open a TTY with the container process.\r\n"
1304 << L" --ulimit Ulimit options (format: <name>=<soft>[:<hard>], use -1 for unlimited)\r\n"
test/windows/wslc/e2e/WSLCE2EContainerStopTests.cpp
-19
@@ -237,25 +237,6 @@ class WSLCE2EContainerStopTests
237 }
238 }
239
240 - WSLC_TEST_METHOD(WSLCE2E_Container_Stop_ValidTimeoutNegativeOne)
241 - {
242 - // Run a container in the background
243 - auto result = RunWslc(std::format(L"container run -d --name {} {} sleep infinity", WslcContainerName, DebianImage.NameAndTag()));
244 - result.Verify({.Stderr = L"", .ExitCode = 0});
245 - const auto containerId = result.GetStdoutOneLine();
246 - VERIFY_IS_FALSE(containerId.empty());
247 -
248 - // Verify container is running
249 - VerifyContainerIsListed(containerId, L"running");
250 -
251 - // -1 is a valid timeout value
252 - result = RunWslc(std::format(L"container stop {} -t -1", containerId));
253 - result.Verify({.Stderr = L"", .ExitCode = 0});
254 -
255 - // Verify the container is no longer running
256 - VerifyContainerIsListed(containerId, L"exited");
257 - }
258 -
240 private:
241 const std::wstring WslcContainerName = L"wslc-test-container";
242 const std::wstring WslcContainerName2 = L"wslc-test-container-2";