master
cpp 549 lines 16.2 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 WSLCContainerLauncher.cpp
8
9 Abstract:
10
11 This file contains the implementation for WSLCContainerLauncher.
12
13 --*/
14
15 #include "precomp.h"
16 #include "WSLCContainerLauncher.h"
17
18 using wsl::windows::common::ClientRunningWSLCProcess;
19 using wsl::windows::common::RunningWSLCContainer;
20 using wsl::windows::common::WSLCContainerLauncher;
21
22 RunningWSLCContainer::RunningWSLCContainer(wil::com_ptr<IWSLCContainer>&& Container, WSLCProcessFlags Flags) :
23 m_container(std::move(Container)), m_flags(Flags)
24 {
25 }
26
27 RunningWSLCContainer::~RunningWSLCContainer()
28 {
29 Reset();
30 }
31
32 IWSLCContainer& RunningWSLCContainer::Get()
33 {
34 return *m_container;
35 }
36
37 void RunningWSLCContainer::Reset()
38 {
39 if (m_container && m_deleteOnClose)
40 {
41 // Attempt to stop and delete the container.
42 LOG_IF_FAILED(m_container->Delete(WSLCDeleteFlagsForce | WSLCDeleteFlagsDeleteVolumes));
43 }
44
45 m_container.reset();
46 }
47
48 WSLCContainerState RunningWSLCContainer::State()
49 {
50 WSLCContainerState state{};
51 THROW_IF_FAILED(m_container->GetState(&state));
52 return state;
53 }
54
55 ClientRunningWSLCProcess RunningWSLCContainer::GetInitProcess()
56 {
57 wil::com_ptr<IWSLCProcess> process;
58 THROW_IF_FAILED(m_container->GetInitProcess(&process));
59
60 return ClientRunningWSLCProcess{std::move(process), m_flags};
61 }
62
63 void RunningWSLCContainer::SetDeleteOnClose(bool deleteOnClose)
64 {
65 m_deleteOnClose = deleteOnClose;
66 }
67
68 std::string RunningWSLCContainer::Id()
69 {
70 WSLCContainerId id{};
71 THROW_IF_FAILED(m_container->GetId(id));
72
73 return id;
74 }
75
76 std::string RunningWSLCContainer::Name()
77 {
78 wil::unique_cotaskmem_ansistring name;
79 THROW_IF_FAILED(m_container->GetName(&name));
80
81 return name.get();
82 }
83
84 WSLCContainerLauncher::WSLCContainerLauncher(
85 const std::string& Image,
86 const std::string& Name,
87 const std::vector<std::string>& Arguments,
88 const std::vector<std::string>& Environment,
89 std::string networkMode,
90 WSLCProcessFlags Flags) :
91 WSLCProcessLauncher({}, Arguments, Environment, Flags), m_image(Image), m_name(Name), m_networkMode(std::move(networkMode))
92 {
93 }
94
95 void WSLCContainerLauncher::AddPort(uint16_t WindowsPort, uint16_t ContainerPort, int Family, int Protocol, const std::optional<std::string>& BindingAddress)
96 {
97 THROW_HR_IF(E_INVALIDARG, Family != AF_INET && Family != AF_INET6);
98
99 WSLCPortMapping port{
100 .HostPort = WindowsPort,
101 .ContainerPort = ContainerPort,
102 .Family = Family,
103 .Protocol = Protocol,
104 };
105
106 if (BindingAddress.has_value())
107 {
108 THROW_HR_IF(E_INVALIDARG, BindingAddress->size() > WSLC_MAX_BINDING_ADDRESS_LENGTH);
109 THROW_HR_IF_MSG(
110 E_INVALIDARG, strcpy_s(port.BindingAddress, BindingAddress->c_str()) != 0, "Invalid address: %hs", BindingAddress->c_str());
111 }
112 else
113 {
114 static_assert(sizeof("127.0.0.1") <= WSLC_MAX_BINDING_ADDRESS_LENGTH + 1, "Default IPv4 binding address too long");
115 static_assert(sizeof("::1") <= WSLC_MAX_BINDING_ADDRESS_LENGTH + 1, "Default IPv6 binding address too long");
116 THROW_HR_IF(E_INVALIDARG, strcpy_s(port.BindingAddress, Family == AF_INET ? "127.0.0.1" : "::1") != 0);
117 }
118
119 m_ports.push_back(port);
120 }
121
122 void WSLCContainerLauncher::SetName(std::string&& Name)
123 {
124 m_name = std::move(Name);
125 }
126
127 void WSLCContainerLauncher::SetDefaultStopSignal(WSLCSignal Signal)
128 {
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;
140 }
141
142 void WSLCContainerLauncher::SetHealthCmd(std::string&& HealthCmd)
143 {
144 m_healthCmd = std::move(HealthCmd);
145 }
146
147 void WSLCContainerLauncher::SetHealthInterval(int64_t Nanoseconds)
148 {
149 m_healthInterval = Nanoseconds;
150 }
151
152 void WSLCContainerLauncher::SetHealthTimeout(int64_t Nanoseconds)
153 {
154 m_healthTimeout = Nanoseconds;
155 }
156
157 void WSLCContainerLauncher::SetHealthStartPeriod(int64_t Nanoseconds)
158 {
159 m_healthStartPeriod = Nanoseconds;
160 }
161
162 void WSLCContainerLauncher::SetHealthRetries(LONG Retries)
163 {
164 m_healthRetries = Retries;
165 }
166
167 void WSLCContainerLauncher::SetNoHealthcheck()
168 {
169 WI_SetFlag(m_containerFlags, WSLCContainerFlagsNoHealthCheck);
170 }
171
172 void WSLCContainerLauncher::SetEntrypoint(std::vector<std::string>&& entrypoint)
173 {
174 m_entrypoint = std::move(entrypoint);
175 }
176
177 void WSLCContainerLauncher::SetContainerFlags(WSLCContainerFlags Flags)
178 {
179 m_containerFlags = Flags;
180 }
181
182 void WSLCContainerLauncher::SetHostname(std::string&& Hostname)
183 {
184 m_hostname = std::move(Hostname);
185 }
186
187 void WSLCContainerLauncher::SetDomainname(std::string&& Domainame)
188 {
189 m_domainname = std::move(Domainame);
190 }
191
192 void WSLCContainerLauncher::SetDnsServers(std::vector<std::string>&& DnsServers)
193 {
194 m_dnsServers = std::move(DnsServers);
195 }
196
197 void WSLCContainerLauncher::SetDnsSearchDomains(std::vector<std::string>&& DnsSearchDomains)
198 {
199 m_dnsSearchDomains = std::move(DnsSearchDomains);
200 }
201
202 void WSLCContainerLauncher::SetDnsOptions(std::vector<std::string>&& DnsOptions)
203 {
204 m_dnsOptions = std::move(DnsOptions);
205 }
206
207 void WSLCContainerLauncher::SetMemoryLimit(std::int64_t Bytes)
208 {
209 m_memoryBytes = Bytes;
210 }
211
212 void WSLCContainerLauncher::SetNanoCpus(std::int64_t NanoCpus)
213 {
214 m_nanoCpus = NanoCpus;
215 }
216
217 void WSLCContainerLauncher::AddUlimit(const std::string& Name, std::int64_t Soft, std::int64_t Hard)
218 {
219 // Store a copy of the name string to keep the WSLCUlimit pointer valid.
220 const auto& name = m_ulimitNames.emplace_back(Name);
221
222 WSLCUlimit ulimit{};
223 ulimit.Name = name.c_str();
224 ulimit.Soft = Soft;
225 ulimit.Hard = Hard;
226
227 m_ulimits.push_back(ulimit);
228 }
229
230 void wsl::windows::common::WSLCContainerLauncher::AddVolume(const std::wstring& HostPath, const std::string& ContainerPath, bool ReadOnly)
231 {
232 AddMount({
233 .MountType = WSLCMountTypeBind,
234 .Source = HostPath,
235 .Target = ContainerPath,
236 .ReadOnly = ReadOnly,
237 .BindSource = mount::BindSourcePolicy::CreateIfMissing,
238 });
239 }
240
241 void wsl::windows::common::WSLCContainerLauncher::AddNamedVolume(const std::string& Name, const std::string& ContainerPath, bool ReadOnly)
242 {
243 AddMount({
244 .MountType = WSLCMountTypeVolume,
245 .Source = wsl::shared::string::MultiByteToWide(Name),
246 .Target = ContainerPath,
247 .ReadOnly = ReadOnly,
248 });
249 }
250
251 void wsl::windows::common::WSLCContainerLauncher::AddMount(const mount::Spec& Mount)
252 {
253 WSLCMountSpec mount{};
254 mount.Type = Mount.MountType;
255
256 if (!Mount.Source.empty())
257 {
258 mount.Source = m_mountSources.emplace_back(Mount.Source).c_str();
259 }
260
261 mount.Target = m_mountTargets.emplace_back(Mount.Target).c_str();
262 mount.ReadOnly = Mount.ReadOnly ? TRUE : FALSE;
263 if (Mount.MountType == WSLCMountTypeBind && Mount.BindSource == mount::BindSourcePolicy::CreateIfMissing)
264 {
265 WI_SetFlag(mount.Flags, WSLCMountSpecFlagsCreateSourceIfMissing);
266 }
267
268 if (Mount.TmpfsSizeBytes.has_value())
269 {
270 WI_SetFlag(mount.Flags, WSLCMountSpecFlagsTmpfsSize);
271 mount.TmpfsSizeBytes = Mount.TmpfsSizeBytes.value();
272 }
273
274 if (Mount.TmpfsMode.has_value())
275 {
276 WI_SetFlag(mount.Flags, WSLCMountSpecFlagsTmpfsMode);
277 mount.TmpfsMode = Mount.TmpfsMode.value();
278 }
279
280 if (Mount.TmpfsOptions.has_value())
281 {
282 mount.TmpfsOptions = m_mountTmpfsOptions.emplace_back(Mount.TmpfsOptions.value()).c_str();
283 }
284
285 m_mounts.push_back(mount);
286 }
287
288 void wsl::windows::common::WSLCContainerLauncher::AddLabel(const std::string& Key, const std::string& Value)
289 {
290 // Store a copy of the key/value strings to the launcher to ensure the pointers in WSLCLabel remain valid.
291 const auto& key = m_labelKeys.emplace_back(Key);
292 const auto& value = m_labelValues.emplace_back(Value);
293
294 WSLCLabel label{};
295 label.Key = key.c_str();
296 label.Value = value.c_str();
297
298 m_labels.push_back(label);
299 }
300
301 void wsl::windows::common::WSLCContainerLauncher::AddTmpfs(const std::string& ContainerPath, const std::string& Options)
302 {
303 AddMount({
304 .MountType = WSLCMountTypeTmpfs,
305 .Target = ContainerPath,
306 .TmpfsOptions = Options,
307 });
308 }
309
310 void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std::string& Name)
311 {
312 AddAdditionalNetwork(Name, {});
313 }
314
315 void wsl::windows::common::WSLCContainerLauncher::AddAdditionalNetwork(const std::string& Name, const std::vector<std::string>& Aliases)
316 {
317 m_additionalNetworks.push_back({.Name = Name, .Aliases = Aliases});
318 }
319
320 void wsl::windows::common::WSLCContainerLauncher::AddPrimaryNetworkAlias(const std::string& Alias)
321 {
322 m_primaryNetworkAliases.push_back(Alias);
323 }
324
325 void wsl::windows::common::WSLCContainerLauncher::SetPrimaryNetworkIpAddress(std::string&& Address)
326 {
327 m_primaryNetworkIpAddress = std::move(Address);
328 }
329
330 std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::LaunchNoThrow(
331 IWSLCSession& Session, WSLCContainerStartFlags Flags, IWarningCallback* WarningCallback)
332 {
333 auto [result, container] = CreateNoThrow(Session, WarningCallback);
334 if (FAILED(result))
335 {
336 return std::make_pair(result, std::optional<RunningWSLCContainer>{});
337 }
338
339 WSLCProcessStartOptions startOptions{};
340 startOptions.TtyRows = m_rows;
341 startOptions.TtyColumns = m_columns;
342
343 result = container.value().Get().Start(Flags, &startOptions, WarningCallback);
344
345 return std::make_pair(result, std::move(container));
346 }
347
348 std::pair<HRESULT, std::optional<RunningWSLCContainer>> WSLCContainerLauncher::CreateNoThrow(IWSLCSession& Session, IWarningCallback* WarningCallback)
349 {
350 WSLCContainerOptions options{};
351 options.Image = m_image.c_str();
352
353 if (!m_name.empty())
354 {
355 options.Name = m_name.c_str();
356 }
357
358 std::vector<const char*> entrypointStorage;
359
360 for (const auto& e : m_entrypoint)
361 {
362 entrypointStorage.push_back(e.c_str());
363 }
364
365 auto [processOptions, commandLinePtrs, environmentPtrs] = CreateProcessOptions();
366 options.InitProcessOptions = processOptions;
367 options.Ports = m_ports.data();
368 options.PortsCount = static_cast<ULONG>(m_ports.size());
369 options.StopSignal = m_stopSignal;
370 options.Flags = m_containerFlags;
371 if (m_stopTimeout.has_value())
372 {
373 options.StopTimeout = m_stopTimeout.value();
374 WI_SetFlag(options.Flags, WSLCContainerFlagsStopTimeout);
375 }
376
377 options.ShmSize = m_shmSize;
378
379 if (m_healthCmd.has_value() || m_healthInterval.has_value() || m_healthTimeout.has_value() ||
380 m_healthStartPeriod.has_value() || m_healthRetries.has_value())
381 {
382 if (m_healthCmd.has_value())
383 {
384 options.HealthCmd = m_healthCmd->c_str();
385 }
386
387 options.HealthIntervalNs = m_healthInterval.value_or(0);
388 options.HealthTimeoutNs = m_healthTimeout.value_or(0);
389 options.HealthStartPeriodNs = m_healthStartPeriod.value_or(0);
390 options.HealthRetries = m_healthRetries.value_or(0);
391 WI_SetFlag(options.Flags, WSLCContainerFlagsHealthCheck);
392 }
393
394 if (!entrypointStorage.empty())
395 {
396 options.Entrypoint = {entrypointStorage.data(), static_cast<ULONG>(entrypointStorage.size())};
397 }
398
399 if (!m_hostname.empty())
400 {
401 options.HostName = m_hostname.c_str();
402 }
403
404 if (!m_domainname.empty())
405 {
406 options.DomainName = m_domainname.c_str();
407 }
408
409 std::vector<const char*> dnsServersStorage;
410 for (const auto& e : m_dnsServers)
411 {
412 dnsServersStorage.push_back(e.c_str());
413 }
414
415 if (!dnsServersStorage.empty())
416 {
417 options.DnsServers = {dnsServersStorage.data(), static_cast<ULONG>(dnsServersStorage.size())};
418 }
419
420 std::vector<const char*> dnsSearchDomainsStorage;
421 for (const auto& e : m_dnsSearchDomains)
422 {
423 dnsSearchDomainsStorage.push_back(e.c_str());
424 }
425
426 if (!dnsSearchDomainsStorage.empty())
427 {
428 options.DnsSearchDomains = {dnsSearchDomainsStorage.data(), static_cast<ULONG>(dnsSearchDomainsStorage.size())};
429 }
430
431 std::vector<const char*> dnsOptionsStorage;
432 for (const auto& e : m_dnsOptions)
433 {
434 dnsOptionsStorage.push_back(e.c_str());
435 }
436
437 if (!dnsOptionsStorage.empty())
438 {
439 options.DnsOptions = {dnsOptionsStorage.data(), static_cast<ULONG>(dnsOptionsStorage.size())};
440 }
441
442 if (!m_workingDirectory.empty())
443 {
444 options.InitProcessOptions.CurrentDirectory = m_workingDirectory.c_str();
445 }
446
447 options.MountsCount = static_cast<ULONG>(m_mounts.size());
448 options.Mounts = m_mounts.size() > 0 ? m_mounts.data() : nullptr;
449
450 options.LabelsCount = static_cast<ULONG>(m_labels.size());
451 options.Labels = m_labels.size() > 0 ? m_labels.data() : nullptr;
452
453 options.ContainerNetwork.NetworkMode = m_networkMode.c_str();
454
455 // Each additional network becomes an entry in NetworkingConfig.EndpointsConfig.
456 std::vector<WSLCNetworkConnection> connections;
457 connections.reserve(m_additionalNetworks.size());
458 std::vector<std::vector<KeyValuePair>> connectionSettings;
459 connectionSettings.reserve(m_additionalNetworks.size());
460 for (const auto& e : m_additionalNetworks)
461 {
462 auto& settings = connectionSettings.emplace_back();
463 settings.reserve(e.Aliases.size());
464 for (const auto& alias : e.Aliases)
465 {
466 settings.push_back({.Key = "Aliases", .Value = alias.c_str()});
467 }
468
469 connections.push_back({
470 .NetworkName = e.Name.c_str(),
471 .Settings = settings.empty() ? nullptr : settings.data(),
472 .SettingsCount = static_cast<ULONG>(settings.size()),
473 });
474 }
475
476 options.ContainerNetwork.Networks = connections.empty() ? nullptr : connections.data();
477 options.ContainerNetwork.NetworksCount = static_cast<ULONG>(connections.size());
478
479 // Settings for the primary endpoint.
480 std::vector<KeyValuePair> primarySettings;
481 primarySettings.reserve(m_primaryNetworkAliases.size() + (m_primaryNetworkIpAddress.has_value() ? 1 : 0));
482 for (const auto& alias : m_primaryNetworkAliases)
483 {
484 primarySettings.push_back({.Key = "Aliases", .Value = alias.c_str()});
485 }
486
487 if (m_primaryNetworkIpAddress.has_value())
488 {
489 primarySettings.push_back({.Key = "IPAddress", .Value = m_primaryNetworkIpAddress->c_str()});
490 }
491
492 options.ContainerNetwork.Settings = primarySettings.empty() ? nullptr : primarySettings.data();
493 options.ContainerNetwork.SettingsCount = static_cast<ULONG>(primarySettings.size());
494
495 options.MemoryBytes = m_memoryBytes;
496 options.NanoCpus = m_nanoCpus;
497 options.UlimitsCount = static_cast<ULONG>(m_ulimits.size());
498 options.Ulimits = m_ulimits.size() > 0 ? m_ulimits.data() : nullptr;
499
500 // TODO: Support volumes, ports, flags, container networking mode, etc.
501 wil::com_ptr<IWSLCContainer> container;
502 auto result = Session.CreateContainer(&options, WarningCallback, &container);
503 if (FAILED(result))
504 {
505 return std::pair<HRESULT, std::optional<RunningWSLCContainer>>(result, std::optional<RunningWSLCContainer>{});
506 }
507
508 return std::make_pair(S_OK, std::move(RunningWSLCContainer{std::move(container), m_flags}));
509 }
510
511 RunningWSLCContainer WSLCContainerLauncher::Create(IWSLCSession& Session, IWarningCallback* WarningCallback)
512 {
513 auto [result, container] = CreateNoThrow(Session, WarningCallback);
514 THROW_IF_FAILED(result);
515
516 return std::move(container.value());
517 }
518
519 RunningWSLCContainer WSLCContainerLauncher::Launch(IWSLCSession& Session, WSLCContainerStartFlags Flags, IWarningCallback* WarningCallback)
520 {
521 auto [result, container] = LaunchNoThrow(Session, Flags, WarningCallback);
522 THROW_IF_FAILED(result);
523
524 return std::move(container.value());
525 }
526
527 wsl::windows::common::wslc_schema::InspectContainer RunningWSLCContainer::Inspect()
528 {
529 wil::unique_cotaskmem_ansistring output;
530 THROW_IF_FAILED(m_container->Inspect(FALSE, &output));
531
532 return wsl::shared::FromJson<wslc_schema::InspectContainer>(output.get());
533 }
534
535 std::map<std::string, std::string> RunningWSLCContainer::Labels()
536 {
537 wil::unique_cotaskmem_array_ptr<WSLCLabelInformation> labels;
538 THROW_IF_FAILED(m_container->GetLabels(&labels, labels.size_address<ULONG>()));
539
540 std::map<std::string, std::string> result;
541 for (size_t i = 0; i < labels.size(); i++)
542 {
543 result[labels[i].Key] = labels[i].Value;
544 CoTaskMemFree(labels[i].Key);
545 CoTaskMemFree(labels[i].Value);
546 }
547
548 return result;
549 }