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";