@samitouri / QOSAMI-WSL / commits / a8ff3108

Implement WinRT / C# projection for the WSLC SDK API surface (#40360)

Flor Chacón committed May 19, 2026 at 08:25 UTC a8ff310869bb5be776c81103a44e2370b8646c20
47 files changed +3836 -323
src/windows/WslcSDK/csharp/WinRTActivation.cs
+4 -1
@@ -54,7 +54,10 @@ internal static class WinRTActivation
54 private static IntPtr GetActivationFactory(string typeName, Guid iid)
55 {
56 // Convert the type name to HSTRING
57 - WindowsCreateString(typeName, (uint)typeName.Length, out var hstring);
57 + if (WindowsCreateString(typeName, (uint)typeName.Length, out var hstring) < 0)
58 + {
59 + return IntPtr.Zero;
60 + }
61 try
62 {
63 if (s_getDllFactory(hstring, out var factory) < 0)
src/windows/WslcSDK/winrt/CMakeLists.txt
+2
@@ -17,6 +17,7 @@ set(SOURCES
17 ServiceVersion.cpp
18 Session.cpp
19 SessionSettings.cpp
20 + Streams.cpp
21 TagImageOptions.cpp
22 VhdOptions.cpp
23 WslcService.cpp
@@ -39,6 +40,7 @@ set(HEADERS
40 ServiceVersion.h
41 Session.h
42 SessionSettings.h
43 + Streams.h
44 TagImageOptions.h
45 VhdOptions.h
46 WslcService.h
src/windows/WslcSDK/winrt/Container.cpp
+84 -10
@@ -15,38 +15,112 @@ Abstract:
15 #include "precomp.h"
16 #include "Container.h"
17 #include "Microsoft.WSL.Containers.Container.g.cpp"
18 +#include "ContainerSettings.h"
19 +#include "Process.h"
20 +#include "ProcessSettings.h"
21 +
22 +using namespace winrt::Windows::Foundation;
23
24 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -void Container::Start(winrt::Microsoft::WSL::Containers::ContainerStartFlags const& flags)
25 +Container::Container(WslcSession session, winrt::Microsoft::WSL::Containers::ContainerSettings const& settings)
26 {
22 - throw hresult_not_implemented();
27 + if (settings.InitProcess())
28 + {
29 + m_initProcess = winrt::make_self<implementation::Process>(settings.InitProcess());
30 + }
31 +
32 + wil::unique_cotaskmem_string errorMessage;
33 + auto hr = WslcCreateContainer(session, GetStructPointer(settings), m_container.put(), errorMessage.put());
34 + THROW_MSG_IF_FAILED(hr, errorMessage);
35 }
24 -void Container::Stop(winrt::Microsoft::WSL::Containers::Signal const& signal, uint32_t timeoutSeconds)
36 +
37 +void Container::Start()
38 {
26 - throw hresult_not_implemented();
39 + auto startFlags = WSLC_CONTAINER_START_FLAG_NONE;
40 + if (m_initProcess)
41 + {
42 + WI_SetFlagIf(
43 + startFlags,
44 + WSLC_CONTAINER_START_FLAG_ATTACH,
45 + m_initProcess->OutputMode() == ProcessOutputMode::Event || m_initProcess->OutputMode() == ProcessOutputMode::Stream);
46 + }
47 +
48 + wil::unique_cotaskmem_string errorMessage;
49 + auto hr = WslcStartContainer(m_container.get(), startFlags, errorMessage.put());
50 + THROW_MSG_IF_FAILED(hr, errorMessage);
51 +
52 + if (m_initProcess)
53 + {
54 + WslcProcess initHandle;
55 + winrt::check_hresult(WslcGetContainerInitProcess(m_container.get(), &initHandle));
56 + m_initProcess->AttachHandle(initHandle);
57 + }
58 }
59 +
60 +void Container::Stop(winrt::Microsoft::WSL::Containers::Signal const& signal, TimeSpan timeout)
61 +{
62 + wil::unique_cotaskmem_string errorMessage;
63 + auto timeoutSeconds = std::chrono::duration_cast<std::chrono::seconds>(timeout).count();
64 + if (timeoutSeconds > std::numeric_limits<uint32_t>::max())
65 + {
66 + throw winrt::hresult_invalid_argument(L"Timeout is too large");
67 + }
68 +
69 + if (timeoutSeconds < 0)
70 + {
71 + throw winrt::hresult_invalid_argument(L"Timeout must be non-negative");
72 + }
73 +
74 + auto hr = WslcStopContainer(ToHandle(), static_cast<WslcSignal>(signal), static_cast<uint32_t>(timeoutSeconds), errorMessage.put());
75 + THROW_MSG_IF_FAILED(hr, errorMessage);
76 +}
77 +
78 void Container::Delete(winrt::Microsoft::WSL::Containers::DeleteContainerFlags const& flags)
79 {
30 - throw hresult_not_implemented();
80 + wil::unique_cotaskmem_string errorMessage;
81 + auto hr = WslcDeleteContainer(ToHandle(), static_cast<WslcDeleteContainerFlags>(flags), errorMessage.put());
82 + THROW_MSG_IF_FAILED(hr, errorMessage);
83 }
84 +
85 winrt::Microsoft::WSL::Containers::Process Container::CreateProcess(winrt::Microsoft::WSL::Containers::ProcessSettings const& newProcessSettings)
86 {
34 - throw hresult_not_implemented();
87 + return *winrt::make_self<implementation::Process>(*this, newProcessSettings);
88 }
89 +
90 hstring Container::Inspect()
91 {
38 - throw hresult_not_implemented();
92 + wil::unique_cotaskmem_ansistring inspectData;
93 + winrt::check_hresult(WslcInspectContainer(ToHandle(), inspectData.put()));
94 + return winrt::to_hstring(inspectData.get());
95 }
96 +
97 hstring Container::Id()
98 {
42 - throw hresult_not_implemented();
99 + CHAR id[WSLC_CONTAINER_ID_BUFFER_SIZE];
100 + winrt::check_hresult(WslcGetContainerID(ToHandle(), id));
101 + return winrt::to_hstring(id);
102 }
103 +
104 winrt::Microsoft::WSL::Containers::Process Container::InitProcess()
105 {
46 - throw hresult_not_implemented();
106 + if (!m_initProcess)
107 + {
108 + throw winrt::hresult_illegal_method_call(L"This container was not configured with an init process");
109 + }
110 +
111 + return *m_initProcess;
112 }
113 +
114 winrt::Microsoft::WSL::Containers::ContainerState Container::State()
115 {
50 - throw hresult_not_implemented();
116 + WslcContainerState state;
117 + winrt::check_hresult(WslcGetContainerState(ToHandle(), &state));
118 + return static_cast<winrt::Microsoft::WSL::Containers::ContainerState>(state);
119 }
120 +
121 +WslcContainer Container::ToHandle()
122 +{
123 + return m_container.get();
124 +}
125 +
126 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/Container.h
+17 -2
@@ -14,19 +14,34 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.Container.g.h"
17 +#include "Helpers.h"
18 +#include "Process.h"
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 struct Container : ContainerT<Container>
22 {
23 Container() = default;
24 + Container(WslcSession session, winrt::Microsoft::WSL::Containers::ContainerSettings const& settings);
25
23 - void Start(winrt::Microsoft::WSL::Containers::ContainerStartFlags const& flags);
24 - void Stop(winrt::Microsoft::WSL::Containers::Signal const& signal, uint32_t timeoutSeconds);
26 + void Start();
27 + void Stop(winrt::Microsoft::WSL::Containers::Signal const& signal, winrt::Windows::Foundation::TimeSpan timeout);
28 void Delete(winrt::Microsoft::WSL::Containers::DeleteContainerFlags const& flags);
29 winrt::Microsoft::WSL::Containers::Process CreateProcess(winrt::Microsoft::WSL::Containers::ProcessSettings const& newProcessSettings);
30 hstring Inspect();
31 hstring Id();
32 winrt::Microsoft::WSL::Containers::Process InitProcess();
33 winrt::Microsoft::WSL::Containers::ContainerState State();
34 +
35 + WslcContainer ToHandle();
36 +
37 +private:
38 + winrt::com_ptr<implementation::Process> m_initProcess;
39 +
40 + // Releasing the container handle will end the processes and disconnect the callbacks.
41 + // Keep this at the end so that it is released first, ensuring the init process' events aren't destroyed while they may still be signaled.
42 + wil::unique_any<WslcContainer, decltype(&WslcReleaseContainer), &WslcReleaseContainer> m_container;
43 };
44 +
45 } // namespace winrt::Microsoft::WSL::Containers::implementation
46 +
47 +DEFINE_TYPE_HELPERS(Container);
src/windows/WslcSDK/winrt/ContainerNamedVolume.cpp
+63 -8
@@ -17,32 +17,87 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ContainerNamedVolume.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -ContainerNamedVolume::ContainerNamedVolume(hstring const& name, hstring const& containerPath, bool readOnly)
20 +
21 +ContainerNamedVolume::ContainerNamedVolume(hstring const& name, hstring const& containerPath, bool readOnly) :
22 + m_name(winrt::to_string(name)), m_containerPath(winrt::to_string(containerPath)), m_readOnly(readOnly)
23 {
22 - throw hresult_not_implemented();
24 + if (name.empty())
25 + {
26 + throw hresult_invalid_argument(L"Volume name cannot be empty");
27 + }
28 +
29 + if (containerPath.empty())
30 + {
31 + throw hresult_invalid_argument(L"Container path cannot be empty");
32 + }
33 }
34 +
35 hstring ContainerNamedVolume::Name()
36 {
26 - throw hresult_not_implemented();
37 + return winrt::to_hstring(m_name);
38 }
39 +
40 void ContainerNamedVolume::Name(hstring const& value)
41 {
30 - throw hresult_not_implemented();
42 + if (m_containerNamedVolume)
43 + {
44 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
45 + }
46 +
47 + if (value.empty())
48 + {
49 + throw hresult_invalid_argument(L"Volume name cannot be empty");
50 + }
51 +
52 + m_name = winrt::to_string(value);
53 }
54 +
55 hstring ContainerNamedVolume::ContainerPath()
56 {
34 - throw hresult_not_implemented();
57 + return winrt::to_hstring(m_containerPath);
58 }
59 +
60 void ContainerNamedVolume::ContainerPath(hstring const& value)
61 {
38 - throw hresult_not_implemented();
62 + if (m_containerNamedVolume)
63 + {
64 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
65 + }
66 +
67 + if (value.empty())
68 + {
69 + throw hresult_invalid_argument(L"Container path cannot be empty");
70 + }
71 +
72 + m_containerPath = winrt::to_string(value);
73 }
74 +
75 bool ContainerNamedVolume::ReadOnly()
76 {
42 - throw hresult_not_implemented();
77 + return m_readOnly;
78 }
79 +
80 void ContainerNamedVolume::ReadOnly(bool value)
81 {
46 - throw hresult_not_implemented();
82 + if (m_containerNamedVolume)
83 + {
84 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
85 + }
86 +
87 + m_readOnly = value;
88 }
89 +
90 +WslcContainerNamedVolume ContainerNamedVolume::ToStruct()
91 +{
92 + if (!m_containerNamedVolume)
93 + {
94 + m_containerNamedVolume = std::make_unique<WslcContainerNamedVolume>();
95 + m_containerNamedVolume->name = m_name.c_str();
96 + m_containerNamedVolume->containerPath = m_containerPath.c_str();
97 + m_containerNamedVolume->readOnly = m_readOnly;
98 + }
99 +
100 + return *m_containerNamedVolume;
101 +}
102 +
103 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ContainerNamedVolume.h
+13
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ContainerNamedVolume.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct ContainerNamedVolume : ContainerNamedVolumeT<ContainerNamedVolume>
@@ -27,10 +28,22 @@ struct ContainerNamedVolume : ContainerNamedVolumeT<ContainerNamedVolume>
28 void ContainerPath(hstring const& value);
29 bool ReadOnly();
30 void ReadOnly(bool value);
31 +
32 + WslcContainerNamedVolume ToStruct();
33 +
34 +private:
35 + std::string m_name;
36 + std::string m_containerPath;
37 + bool m_readOnly{};
38 +
39 + std::unique_ptr<WslcContainerNamedVolume> m_containerNamedVolume;
40 };
41 } // namespace winrt::Microsoft::WSL::Containers::implementation
42 +
43 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
44 struct ContainerNamedVolume : ContainerNamedVolumeT<ContainerNamedVolume, implementation::ContainerNamedVolume>
45 {
46 };
47 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
48 +
49 +DEFINE_TYPE_HELPERS(ContainerNamedVolume);
src/windows/WslcSDK/winrt/ContainerPortMapping.cpp
+95 -10
@@ -17,40 +17,125 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ContainerPortMapping.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -ContainerPortMapping::ContainerPortMapping(uint16_t windowsPort, uint16_t containerPort, winrt::Microsoft::WSL::Containers::PortProtocol const& protocol)
20 +
21 +ContainerPortMapping::ContainerPortMapping(uint16_t windowsPort, uint16_t containerPort, winrt::Microsoft::WSL::Containers::PortProtocol const& protocol) :
22 + m_windowsPort(windowsPort), m_containerPort(containerPort), m_protocol(protocol)
23 {
22 - throw hresult_not_implemented();
24 }
25 +
26 uint16_t ContainerPortMapping::WindowsPort()
27 {
26 - throw hresult_not_implemented();
28 + return m_windowsPort;
29 }
30 +
31 void ContainerPortMapping::WindowsPort(uint16_t value)
32 {
30 - throw hresult_not_implemented();
33 + if (m_containerPortMapping)
34 + {
35 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
36 + }
37 +
38 + m_windowsPort = value;
39 }
40 +
41 uint16_t ContainerPortMapping::ContainerPort()
42 {
34 - throw hresult_not_implemented();
43 + return m_containerPort;
44 }
45 +
46 void ContainerPortMapping::ContainerPort(uint16_t value)
47 {
38 - throw hresult_not_implemented();
48 + if (m_containerPortMapping)
49 + {
50 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
51 + }
52 +
53 + m_containerPort = value;
54 }
55 +
56 winrt::Microsoft::WSL::Containers::PortProtocol ContainerPortMapping::Protocol()
57 {
42 - throw hresult_not_implemented();
58 + return m_protocol;
59 }
60 +
61 void ContainerPortMapping::Protocol(winrt::Microsoft::WSL::Containers::PortProtocol const& value)
62 {
46 - throw hresult_not_implemented();
63 + if (m_containerPortMapping)
64 + {
65 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
66 + }
67 +
68 + m_protocol = value;
69 }
70 +
71 winrt::Windows::Networking::HostName ContainerPortMapping::WindowsAddress()
72 {
50 - throw hresult_not_implemented();
73 + return m_windowsAddress;
74 }
75 +
76 void ContainerPortMapping::WindowsAddress(winrt::Windows::Networking::HostName const& value)
77 {
54 - throw hresult_not_implemented();
78 + if (m_containerPortMapping)
79 + {
80 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
81 + }
82 +
83 + if (value && value.Type() != winrt::Windows::Networking::HostNameType::Ipv4 && value.Type() != winrt::Windows::Networking::HostNameType::Ipv6)
84 + {
85 + throw hresult_invalid_argument(L"Only IP addresses are supported for port mapping");
86 + }
87 +
88 + m_windowsAddress = value;
89 }
90 +
91 +WslcContainerPortMapping ContainerPortMapping::ToStruct()
92 +{
93 + if (!m_containerPortMapping)
94 + {
95 + m_containerPortMapping = std::make_unique<WslcContainerPortMapping>();
96 + m_containerPortMapping->windowsPort = m_windowsPort;
97 + m_containerPortMapping->containerPort = m_containerPort;
98 + m_containerPortMapping->protocol = static_cast<WslcPortProtocol>(m_protocol);
99 +
100 + if (m_windowsAddress)
101 + {
102 + m_windowsAddressStorage = sockaddr_storage{};
103 + void* addrPtr = &m_windowsAddressStorage.value();
104 +
105 + auto rawName = winrt::to_string(m_windowsAddress.RawName());
106 +
107 + if (m_windowsAddress.Type() == winrt::Windows::Networking::HostNameType::Ipv4)
108 + {
109 + auto addr = static_cast<sockaddr_in*>(addrPtr);
110 + addr->sin_family = AF_INET;
111 + if (inet_pton(AF_INET, rawName.c_str(), &addr->sin_addr) != 1)
112 + {
113 + throw winrt::hresult_invalid_argument(L"Invalid IPv4 address format");
114 + }
115 + }
116 + else if (m_windowsAddress.Type() == winrt::Windows::Networking::HostNameType::Ipv6)
117 + {
118 + auto addr = static_cast<sockaddr_in6*>(addrPtr);
119 + addr->sin6_family = AF_INET6;
120 + if (inet_pton(AF_INET6, rawName.c_str(), &addr->sin6_addr) != 1)
121 + {
122 + throw winrt::hresult_invalid_argument(L"Invalid IPv6 address format");
123 + }
124 + }
125 + else
126 + {
127 + throw winrt::hresult_invalid_argument(L"Only IP addresses are supported for port mapping");
128 + }
129 +
130 + m_containerPortMapping->windowsAddress = &m_windowsAddressStorage.value();
131 + }
132 + else
133 + {
134 + m_containerPortMapping->windowsAddress = nullptr;
135 + }
136 + }
137 +
138 + return *m_containerPortMapping;
139 +}
140 +
141 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ContainerPortMapping.h
+16
@@ -14,6 +14,8 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ContainerPortMapping.g.h"
17 +#include "Helpers.h"
18 +#include <winrt/Windows.Networking.h>
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 struct ContainerPortMapping : ContainerPortMappingT<ContainerPortMapping>
@@ -29,10 +31,24 @@ struct ContainerPortMapping : ContainerPortMappingT<ContainerPortMapping>
31 void Protocol(winrt::Microsoft::WSL::Containers::PortProtocol const& value);
32 winrt::Windows::Networking::HostName WindowsAddress();
33 void WindowsAddress(winrt::Windows::Networking::HostName const& value);
34 +
35 + WslcContainerPortMapping ToStruct();
36 +
37 +private:
38 + uint16_t m_windowsPort{};
39 + uint16_t m_containerPort{};
40 + winrt::Microsoft::WSL::Containers::PortProtocol m_protocol{winrt::Microsoft::WSL::Containers::PortProtocol::TCP};
41 + winrt::Windows::Networking::HostName m_windowsAddress{nullptr};
42 +
43 + std::unique_ptr<WslcContainerPortMapping> m_containerPortMapping;
44 + std::optional<sockaddr_storage> m_windowsAddressStorage;
45 };
46 } // namespace winrt::Microsoft::WSL::Containers::implementation
47 +
48 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
49 struct ContainerPortMapping : ContainerPortMappingT<ContainerPortMapping, implementation::ContainerPortMapping>
50 {
51 };
52 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
53 +
54 +DEFINE_TYPE_HELPERS(ContainerPortMapping);
src/windows/WslcSDK/winrt/ContainerSettings.cpp
+214 -24
@@ -17,88 +17,278 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ContainerSettings.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -ContainerSettings::ContainerSettings(hstring const& imageName)
20 +
21 +ContainerSettings::ContainerSettings(hstring const& imageName) : m_imageName(winrt::to_string(imageName))
22 {
22 - throw hresult_not_implemented();
23 + if (imageName.empty())
24 + {
25 + throw winrt::hresult_invalid_argument(L"Image name cannot be empty");
26 + }
27 }
28 +
29 hstring ContainerSettings::ImageName()
30 {
26 - throw hresult_not_implemented();
31 + return winrt::to_hstring(m_imageName);
32 }
33 +
34 void ContainerSettings::ImageName(hstring const& value)
35 {
30 - throw hresult_not_implemented();
36 + if (m_containerSettings)
37 + {
38 + throw winrt::hresult_illegal_state_change(L"Cannot change image name after container has been initialized");
39 + }
40 +
41 + if (value.empty())
42 + {
43 + throw winrt::hresult_invalid_argument(L"Image name cannot be empty");
44 + }
45 +
46 + m_imageName = winrt::to_string(value);
47 }
48 +
49 hstring ContainerSettings::Name()
50 {
34 - throw hresult_not_implemented();
51 + return winrt::to_hstring(m_name);
52 }
53 +
54 void ContainerSettings::Name(hstring const& value)
55 {
38 - throw hresult_not_implemented();
56 + if (m_containerSettings)
57 + {
58 + throw winrt::hresult_illegal_state_change(L"Cannot change container name after container has been initialized");
59 + }
60 +
61 + m_name = winrt::to_string(value);
62 }
63 +
64 winrt::Microsoft::WSL::Containers::ProcessSettings ContainerSettings::InitProcess()
65 {
42 - throw hresult_not_implemented();
66 + return m_initProcess;
67 }
68 +
69 void ContainerSettings::InitProcess(winrt::Microsoft::WSL::Containers::ProcessSettings const& value)
70 {
46 - throw hresult_not_implemented();
71 + if (m_containerSettings)
72 + {
73 + throw winrt::hresult_illegal_state_change(L"Cannot change init process after container has been initialized");
74 + }
75 +
76 + m_initProcess = value;
77 }
78 +
79 winrt::Windows::Foundation::IReference<winrt::Microsoft::WSL::Containers::ContainerNetworkingMode> ContainerSettings::NetworkingMode()
80 {
50 - throw hresult_not_implemented();
81 + return m_networkingMode;
82 }
83 +
84 void ContainerSettings::NetworkingMode(winrt::Windows::Foundation::IReference<winrt::Microsoft::WSL::Containers::ContainerNetworkingMode> const& value)
85 {
54 - throw hresult_not_implemented();
86 + if (m_containerSettings)
87 + {
88 + throw winrt::hresult_illegal_state_change(L"Cannot change networking mode after container has been initialized");
89 + }
90 +
91 + if (value && value.Value() != ContainerNetworkingMode::None && value.Value() != ContainerNetworkingMode::Bridged)
92 + {
93 + throw winrt::hresult_invalid_argument(L"Invalid networking mode");
94 + }
95 +
96 + m_networkingMode = value;
97 }
98 +
99 hstring ContainerSettings::HostName()
100 {
58 - throw hresult_not_implemented();
101 + return winrt::to_hstring(m_hostName);
102 }
103 +
104 void ContainerSettings::HostName(hstring const& value)
105 {
62 - throw hresult_not_implemented();
106 + if (m_containerSettings)
107 + {
108 + throw winrt::hresult_illegal_state_change(L"Cannot change host name after container has been initialized");
109 + }
110 +
111 + m_hostName = winrt::to_string(value);
112 }
113 +
114 hstring ContainerSettings::DomainName()
115 {
66 - throw hresult_not_implemented();
116 + return winrt::to_hstring(m_domainName);
117 }
118 +
119 void ContainerSettings::DomainName(hstring const& value)
120 {
70 - throw hresult_not_implemented();
121 + if (m_containerSettings)
122 + {
123 + throw winrt::hresult_illegal_state_change(L"Cannot change domain name after container has been initialized");
124 + }
125 +
126 + m_domainName = winrt::to_string(value);
127 }
72 -winrt::Microsoft::WSL::Containers::ContainerFlags ContainerSettings::Flags()
128 +
129 +ContainerFlags ContainerSettings::Flags()
130 {
74 - throw hresult_not_implemented();
131 + return m_flags;
132 }
76 -void ContainerSettings::Flags(winrt::Microsoft::WSL::Containers::ContainerFlags const& value)
133 +
134 +void ContainerSettings::Flags(ContainerFlags const& value)
135 {
78 - throw hresult_not_implemented();
136 + if (m_containerSettings)
137 + {
138 + throw winrt::hresult_illegal_state_change(L"Cannot change container flags after container has been initialized");
139 + }
140 +
141 + m_flags = value;
142 }
143 +
144 winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerPortMapping> ContainerSettings::PortMappings()
145 {
82 - throw hresult_not_implemented();
146 + return m_portMappings;
147 }
148 +
149 void ContainerSettings::PortMappings(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerPortMapping> const& value)
150 {
86 - throw hresult_not_implemented();
151 + if (m_containerSettings)
152 + {
153 + throw winrt::hresult_illegal_state_change(L"Cannot change port mappings after container has been initialized");
154 + }
155 +
156 + if (!value)
157 + {
158 + throw winrt::hresult_error(E_POINTER, L"Value cannot be null");
159 + }
160 +
161 + m_portMappings = value;
162 }
163 +
164 winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerVolume> ContainerSettings::Volumes()
165 {
90 - throw hresult_not_implemented();
166 + return m_volumes;
167 }
168 +
169 void ContainerSettings::Volumes(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerVolume> const& value)
170 {
94 - throw hresult_not_implemented();
171 + if (m_containerSettings)
172 + {
173 + throw winrt::hresult_illegal_state_change(L"Cannot change volumes after container has been initialized");
174 + }
175 +
176 + if (!value)
177 + {
178 + throw winrt::hresult_error(E_POINTER, L"Value cannot be null");
179 + }
180 +
181 + m_volumes = value;
182 }
183 +
184 winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerNamedVolume> ContainerSettings::NamedVolumes()
185 {
98 - throw hresult_not_implemented();
186 + return m_namedVolumes;
187 }
188 +
189 void ContainerSettings::NamedVolumes(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerNamedVolume> const& value)
190 {
102 - throw hresult_not_implemented();
191 + if (m_containerSettings)
192 + {
193 + throw winrt::hresult_illegal_state_change(L"Cannot change named volumes after container has been initialized");
194 + }
195 +
196 + if (!value)
197 + {
198 + throw winrt::hresult_error(E_POINTER, L"Value cannot be null");
199 + }
200 +
201 + m_namedVolumes = value;
202 +}
203 +
204 +WslcContainerSettings* ContainerSettings::ToStructPointer()
205 +{
206 + if (!m_containerSettings)
207 + {
208 + m_containerSettings = std::make_unique<WslcContainerSettings>();
209 + winrt::check_hresult(WslcInitContainerSettings(m_imageName.c_str(), m_containerSettings.get()));
210 +
211 + if (!m_name.empty())
212 + {
213 + winrt::check_hresult(WslcSetContainerSettingsName(m_containerSettings.get(), m_name.c_str()));
214 + }
215 +
216 + if (m_initProcess)
217 + {
218 + winrt::check_hresult(WslcSetContainerSettingsInitProcess(m_containerSettings.get(), GetStructPointer(m_initProcess)));
219 + }
220 +
221 + if (m_networkingMode)
222 + {
223 + winrt::check_hresult(WslcSetContainerSettingsNetworkingMode(
224 + m_containerSettings.get(), static_cast<WslcContainerNetworkingMode>(m_networkingMode.Value())));
225 + }
226 +
227 + if (!m_hostName.empty())
228 + {
229 + winrt::check_hresult(WslcSetContainerSettingsHostName(m_containerSettings.get(), m_hostName.c_str()));
230 + }
231 +
232 + if (!m_domainName.empty())
233 + {
234 + winrt::check_hresult(WslcSetContainerSettingsDomainName(m_containerSettings.get(), m_domainName.c_str()));
235 + }
236 +
237 + winrt::check_hresult(WslcSetContainerSettingsFlags(m_containerSettings.get(), static_cast<WslcContainerFlags>(m_flags)));
238 +
239 + if (m_portMappings.Size() > 0)
240 + {
241 + m_portMappingsStructs.clear();
242 + m_portMappingsStructs.reserve(m_portMappings.Size());
243 + for (auto const& portMapping : m_portMappings)
244 + {
245 + if (!portMapping)
246 + {
247 + throw winrt::hresult_error(E_POINTER, L"Port mappings collection contains a null element");
248 + }
249 + m_portMappingsStructs.push_back(GetStruct(portMapping));
250 + }
251 +
252 + winrt::check_hresult(WslcSetContainerSettingsPortMappings(
253 + m_containerSettings.get(), m_portMappingsStructs.data(), static_cast<uint32_t>(m_portMappingsStructs.size())));
254 + }
255 +
256 + if (m_volumes.Size() > 0)
257 + {
258 + m_volumesStructs.clear();
259 + m_volumesStructs.reserve(m_volumes.Size());
260 + for (auto const& volume : m_volumes)
261 + {
262 + if (!volume)
263 + {
264 + throw winrt::hresult_error(E_POINTER, L"Volumes collection contains a null element");
265 + }
266 + m_volumesStructs.push_back(GetStruct(volume));
267 + }
268 +
269 + winrt::check_hresult(WslcSetContainerSettingsVolumes(
270 + m_containerSettings.get(), m_volumesStructs.data(), static_cast<uint32_t>(m_volumesStructs.size())));
271 + }
272 +
273 + if (m_namedVolumes.Size() > 0)
274 + {
275 + m_namedVolumesStructs.clear();
276 + m_namedVolumesStructs.reserve(m_namedVolumes.Size());
277 + for (auto const& namedVolume : m_namedVolumes)
278 + {
279 + if (!namedVolume)
280 + {
281 + throw winrt::hresult_error(E_POINTER, L"Named volumes collection contains a null element");
282 + }
283 + m_namedVolumesStructs.push_back(GetStruct(namedVolume));
284 + }
285 +
286 + winrt::check_hresult(WslcSetContainerSettingsNamedVolumes(
287 + m_containerSettings.get(), m_namedVolumesStructs.data(), static_cast<uint32_t>(m_namedVolumesStructs.size())));
288 + }
289 + }
290 +
291 + return m_containerSettings.get();
292 }
293 +
294 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ContainerSettings.h
+27
@@ -14,6 +14,8 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ContainerSettings.g.h"
17 +#include "ProcessSettings.h"
18 +#include "Helpers.h"
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 struct ContainerSettings : ContainerSettingsT<ContainerSettings>
@@ -41,10 +43,35 @@ struct ContainerSettings : ContainerSettingsT<ContainerSettings>
43 void Volumes(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerVolume> const& value);
44 winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerNamedVolume> NamedVolumes();
45 void NamedVolumes(winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerNamedVolume> const& value);
46 +
47 + WslcContainerSettings* ToStructPointer();
48 +
49 +private:
50 + std::string m_imageName;
51 + std::string m_name;
52 + winrt::Microsoft::WSL::Containers::ProcessSettings m_initProcess{nullptr};
53 + winrt::Windows::Foundation::IReference<winrt::Microsoft::WSL::Containers::ContainerNetworkingMode> m_networkingMode{nullptr};
54 + std::string m_hostName;
55 + std::string m_domainName;
56 + winrt::Microsoft::WSL::Containers::ContainerFlags m_flags{winrt::Microsoft::WSL::Containers::ContainerFlags::None};
57 + winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerPortMapping> m_portMappings{
58 + winrt::single_threaded_vector<winrt::Microsoft::WSL::Containers::ContainerPortMapping>()};
59 + winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerVolume> m_volumes{
60 + winrt::single_threaded_vector<winrt::Microsoft::WSL::Containers::ContainerVolume>()};
61 + winrt::Windows::Foundation::Collections::IVector<winrt::Microsoft::WSL::Containers::ContainerNamedVolume> m_namedVolumes{
62 + winrt::single_threaded_vector<winrt::Microsoft::WSL::Containers::ContainerNamedVolume>()};
63 +
64 + std::unique_ptr<WslcContainerSettings> m_containerSettings;
65 + std::vector<WslcContainerPortMapping> m_portMappingsStructs;
66 + std::vector<WslcContainerVolume> m_volumesStructs;
67 + std::vector<WslcContainerNamedVolume> m_namedVolumesStructs;
68 };
69 } // namespace winrt::Microsoft::WSL::Containers::implementation
70 +
71 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
72 struct ContainerSettings : ContainerSettingsT<ContainerSettings, implementation::ContainerSettings>
73 {
74 };
75 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
76 +
77 +DEFINE_TYPE_HELPERS(ContainerSettings);
src/windows/WslcSDK/winrt/ContainerVolume.cpp
+63 -8
@@ -17,32 +17,87 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ContainerVolume.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -ContainerVolume::ContainerVolume(hstring const& windowsPath, hstring const& containerPath, bool readOnly)
20 +
21 +ContainerVolume::ContainerVolume(hstring const& windowsPath, hstring const& containerPath, bool readOnly) :
22 + m_windowsPath(windowsPath), m_containerPath(winrt::to_string(containerPath)), m_readOnly(readOnly)
23 {
22 - throw hresult_not_implemented();
24 + if (windowsPath.empty())
25 + {
26 + throw hresult_invalid_argument(L"Windows path cannot be empty");
27 + }
28 +
29 + if (containerPath.empty())
30 + {
31 + throw hresult_invalid_argument(L"Container path cannot be empty");
32 + }
33 }
34 +
35 hstring ContainerVolume::WindowsPath()
36 {
26 - throw hresult_not_implemented();
37 + return hstring(m_windowsPath);
38 }
39 +
40 void ContainerVolume::WindowsPath(hstring const& value)
41 {
30 - throw hresult_not_implemented();
42 + if (m_containerVolume)
43 + {
44 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
45 + }
46 +
47 + if (value.empty())
48 + {
49 + throw hresult_invalid_argument(L"Windows path cannot be empty");
50 + }
51 +
52 + m_windowsPath = value;
53 }
54 +
55 hstring ContainerVolume::ContainerPath()
56 {
34 - throw hresult_not_implemented();
57 + return winrt::to_hstring(m_containerPath);
58 }
59 +
60 void ContainerVolume::ContainerPath(hstring const& value)
61 {
38 - throw hresult_not_implemented();
62 + if (m_containerVolume)
63 + {
64 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
65 + }
66 +
67 + if (value.empty())
68 + {
69 + throw hresult_invalid_argument(L"Container path cannot be empty");
70 + }
71 +
72 + m_containerPath = winrt::to_string(value);
73 }
74 +
75 bool ContainerVolume::ReadOnly()
76 {
42 - throw hresult_not_implemented();
77 + return m_readOnly;
78 }
79 +
80 void ContainerVolume::ReadOnly(bool value)
81 {
46 - throw hresult_not_implemented();
82 + if (m_containerVolume)
83 + {
84 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
85 + }
86 +
87 + m_readOnly = value;
88 }
89 +
90 +WslcContainerVolume ContainerVolume::ToStruct()
91 +{
92 + if (!m_containerVolume)
93 + {
94 + m_containerVolume = std::make_unique<WslcContainerVolume>();
95 + m_containerVolume->windowsPath = m_windowsPath.c_str();
96 + m_containerVolume->containerPath = m_containerPath.c_str();
97 + m_containerVolume->readOnly = m_readOnly;
98 + }
99 +
100 + return *m_containerVolume;
101 +}
102 +
103 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ContainerVolume.h
+13
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ContainerVolume.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct ContainerVolume : ContainerVolumeT<ContainerVolume>
@@ -27,10 +28,22 @@ struct ContainerVolume : ContainerVolumeT<ContainerVolume>
28 void ContainerPath(hstring const& value);
29 bool ReadOnly();
30 void ReadOnly(bool value);
31 +
32 + WslcContainerVolume ToStruct();
33 +
34 +private:
35 + std::wstring m_windowsPath;
36 + std::string m_containerPath;
37 + bool m_readOnly{};
38 +
39 + std::unique_ptr<WslcContainerVolume> m_containerVolume;
40 };
41 } // namespace winrt::Microsoft::WSL::Containers::implementation
42 +
43 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
44 struct ContainerVolume : ContainerVolumeT<ContainerVolume, implementation::ContainerVolume>
45 {
46 };
47 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
48 +
49 +DEFINE_TYPE_HELPERS(ContainerVolume);
src/windows/WslcSDK/winrt/Helpers.h
+40 -21
@@ -18,14 +18,17 @@ Abstract:
18 do \
19 { \
20 const auto _hr = (hr); \
21 - auto _msg = (msg).get(); \
22 - if (_msg) \
21 + if (FAILED(_hr)) \
22 { \
24 - THROW_HR_IF_MSG(_hr, FAILED(_hr), "%ls", _msg); \
25 - } \
26 - else \
27 - { \
28 - THROW_IF_FAILED(_hr); \
23 + auto _msg = (msg).get(); \
24 + if (_msg) \
25 + { \
26 + throw winrt::hresult_error(_hr, winrt::to_hstring(_msg)); \
27 + } \
28 + else \
29 + { \
30 + winrt::throw_hresult(_hr); \
31 + } \
32 } \
33 } while (0)
34
@@ -41,38 +44,54 @@ struct implementation_type;
44
45 namespace winrt::Microsoft::WSL::Containers::implementation {
46 template <typename T>
44 -auto* GetStructPointer(const T& obj)
47 +auto GetImplementation(const T& obj)
48 {
46 - return obj ? winrt::get_self<typename implementation_type<T>::type>(obj)->ToStructPointer() : nullptr;
49 + return winrt::get_self<typename implementation_type<T>::type>(obj);
50 }
51
52 template <typename T>
50 -auto* GetHandlePointer(const T& obj)
53 +auto* GetStructPointer(const T& obj)
54 {
52 - return obj ? winrt::get_self<typename implementation_type<T>::type>(obj)->ToHandlePointer() : nullptr;
55 + return obj ? GetImplementation(obj)->ToStructPointer() : nullptr;
56 }
57
58 template <typename T>
56 -auto GetHandle(const T& obj)
59 +auto GetStruct(const T& obj)
60 {
58 - return obj ? winrt::get_self<typename implementation_type<T>::type>(obj)->ToHandle() : nullptr;
61 + return GetImplementation(obj)->ToStruct();
62 }
63
64 template <typename T>
62 -auto* GetStructPointer(const winrt::com_ptr<T>& obj)
65 +auto GetHandle(const T& obj)
66 {
64 - return obj ? obj->ToStructPointer() : nullptr;
67 + return obj ? GetImplementation(obj)->ToHandle() : nullptr;
68 }
69
70 template <typename T>
68 -auto* GetHandlePointer(const winrt::com_ptr<T>& obj)
71 +auto* GetStructPointer(const winrt::com_ptr<T>& obj)
72 {
70 - return obj ? obj->ToHandlePointer() : nullptr;
73 + return obj ? obj->ToStructPointer() : nullptr;
74 }
75
76 +// Helper for forwarding C progress callbacks to a WinRT progress token.
77 +// An instance of this struct is used as the context for the C callback.
78 +// The callback is invoked synchronously during the blocking C call, so the context
79 +// lives on the coroutine stack and is always valid for the duration of the call.
80 template <typename T>
74 -auto GetHandle(const winrt::com_ptr<T>& obj)
81 +struct ProgressCallbackHelper
82 {
76 - return obj ? obj->ToHandle() : nullptr;
77 -}
78 -} // namespace winrt::Microsoft::WSL::Containers::implementation
\ No newline at end of file
83 + template <typename ProgressTokenT>
84 + ProgressCallbackHelper(ProgressTokenT progressToken) :
85 + m_reportProgress([progressToken](T progress) { progressToken(progress); })
86 + {
87 + }
88 +
89 + static void ReportProgress(PVOID context, T progress)
90 + {
91 + auto callbackContext = static_cast<ProgressCallbackHelper*>(context);
92 + callbackContext->m_reportProgress(progress);
93 + }
94 +
95 + const std::function<void(T)> m_reportProgress;
96 +};
97 +} // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ImageInfo.cpp
+18 -4
@@ -17,20 +17,34 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ImageInfo.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 +ImageInfo::ImageInfo(WslcImageInfo const& info)
21 +{
22 + m_name = winrt::to_hstring(info.name);
23 + m_sizeBytes = info.sizeBytes;
24 + m_createdTimestamp = winrt::clock::from_time_t(static_cast<time_t>(info.createdUnixTime));
25 +
26 + winrt::Windows::Storage::Streams::DataWriter writer;
27 + writer.WriteBytes(info.sha256);
28 + m_sha256 = writer.DetachBuffer();
29 +}
30 +
31 hstring ImageInfo::Name()
32 {
22 - throw hresult_not_implemented();
33 + return m_name;
34 }
35 +
36 winrt::Windows::Storage::Streams::IBuffer ImageInfo::Sha256()
37 {
26 - throw hresult_not_implemented();
38 + return m_sha256;
39 }
40 +
41 uint64_t ImageInfo::SizeBytes()
42 {
30 - throw hresult_not_implemented();
43 + return m_sizeBytes;
44 }
45 +
46 winrt::Windows::Foundation::DateTime ImageInfo::CreatedTimestamp()
47 {
34 - throw hresult_not_implemented();
48 + return m_createdTimestamp;
49 }
50 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ImageInfo.h
+10
@@ -14,15 +14,25 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ImageInfo.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct ImageInfo : ImageInfoT<ImageInfo>
21 {
22 ImageInfo() = default;
23 + ImageInfo(WslcImageInfo const& info);
24
25 hstring Name();
26 winrt::Windows::Storage::Streams::IBuffer Sha256();
27 uint64_t SizeBytes();
28 winrt::Windows::Foundation::DateTime CreatedTimestamp();
29 +
30 +private:
31 + hstring m_name;
32 + winrt::Windows::Storage::Streams::IBuffer m_sha256;
33 + uint64_t m_sizeBytes;
34 + winrt::Windows::Foundation::DateTime m_createdTimestamp;
35 };
36 } // namespace winrt::Microsoft::WSL::Containers::implementation
37 +
38 +DEFINE_TYPE_HELPERS(ImageInfo);
src/windows/WslcSDK/winrt/ImageProgress.cpp
+35 -4
@@ -17,20 +17,51 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ImageProgress.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 +ImageProgress::ImageProgress(const WslcImageProgressMessage* progress) :
21 + m_id(winrt::to_hstring(progress->id)),
22 + m_status(static_cast<ImageProgressStatus>(progress->status)),
23 + m_currentBytes(progress->detail.currentBytes),
24 + m_totalBytes(progress->detail.totalBytes)
25 +{
26 +}
27 +
28 hstring ImageProgress::Id()
29 {
22 - throw hresult_not_implemented();
30 + return m_id;
31 }
32 +
33 +void ImageProgress::Id(hstring const& value)
34 +{
35 + m_id = value;
36 +}
37 +
38 winrt::Microsoft::WSL::Containers::ImageProgressStatus ImageProgress::Status()
39 {
26 - throw hresult_not_implemented();
40 + return m_status;
41 +}
42 +
43 +void ImageProgress::Status(winrt::Microsoft::WSL::Containers::ImageProgressStatus const& value)
44 +{
45 + m_status = value;
46 }
47 +
48 uint64_t ImageProgress::CurrentBytes()
49 {
30 - throw hresult_not_implemented();
50 + return m_currentBytes;
51 +}
52 +
53 +void ImageProgress::CurrentBytes(uint64_t value)
54 +{
55 + m_currentBytes = value;
56 }
57 +
58 uint64_t ImageProgress::TotalBytes()
59 {
34 - throw hresult_not_implemented();
60 + return m_totalBytes;
61 +}
62 +
63 +void ImageProgress::TotalBytes(uint64_t value)
64 +{
65 + m_totalBytes = value;
66 }
67 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ImageProgress.h
+14
@@ -14,15 +14,29 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ImageProgress.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct ImageProgress : ImageProgressT<ImageProgress>
21 {
22 ImageProgress() = default;
23 + ImageProgress(const WslcImageProgressMessage* progress);
24
25 hstring Id();
26 + void Id(hstring const& value);
27 winrt::Microsoft::WSL::Containers::ImageProgressStatus Status();
28 + void Status(winrt::Microsoft::WSL::Containers::ImageProgressStatus const& value);
29 uint64_t CurrentBytes();
30 + void CurrentBytes(uint64_t value);
31 uint64_t TotalBytes();
32 + void TotalBytes(uint64_t value);
33 +
34 +private:
35 + hstring m_id;
36 + winrt::Microsoft::WSL::Containers::ImageProgressStatus m_status{};
37 + uint64_t m_currentBytes{};
38 + uint64_t m_totalBytes{};
39 };
40 } // namespace winrt::Microsoft::WSL::Containers::implementation
41 +
42 +DEFINE_TYPE_HELPERS(ImageProgress);
src/windows/WslcSDK/winrt/InstallProgress.cpp
+11 -4
@@ -17,16 +17,23 @@ Abstract:
17 #include "Microsoft.WSL.Containers.InstallProgress.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -winrt::Microsoft::WSL::Containers::ComponentFlags InstallProgress::Component()
20 +InstallProgress::InstallProgress(ComponentFlags component, uint32_t progress, uint32_t total) :
21 + m_component(component), m_progress(progress), m_total(total)
22 {
22 - throw hresult_not_implemented();
23 }
24 +
25 +ComponentFlags InstallProgress::Component()
26 +{
27 + return m_component;
28 +}
29 +
30 uint32_t InstallProgress::Progress()
31 {
26 - throw hresult_not_implemented();
32 + return m_progress;
33 }
34 +
35 uint32_t InstallProgress::Total()
36 {
30 - throw hresult_not_implemented();
37 + return m_total;
38 }
39 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/InstallProgress.h
+9
@@ -14,14 +14,23 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.InstallProgress.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct InstallProgress : InstallProgressT<InstallProgress>
21 {
22 InstallProgress() = default;
23 + InstallProgress(winrt::Microsoft::WSL::Containers::ComponentFlags component, uint32_t progress, uint32_t total);
24
25 winrt::Microsoft::WSL::Containers::ComponentFlags Component();
26 uint32_t Progress();
27 uint32_t Total();
28 +
29 +private:
30 + winrt::Microsoft::WSL::Containers::ComponentFlags m_component{};
31 + uint32_t m_progress{};
32 + uint32_t m_total{};
33 };
34 } // namespace winrt::Microsoft::WSL::Containers::implementation
35 +
36 +DEFINE_TYPE_HELPERS(InstallProgress);
src/windows/WslcSDK/winrt/Process.cpp
+205 -13
@@ -14,59 +14,251 @@ Abstract:
14
15 #include "precomp.h"
16 #include "Process.h"
17 +#include "Streams.h"
18 #include "Microsoft.WSL.Containers.Process.g.cpp"
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 +
22 +Process::Process(winrt::Microsoft::WSL::Containers::Container const& container, winrt::Microsoft::WSL::Containers::ProcessSettings const& settings) :
23 + Process(settings)
24 +{
25 + m_container = container;
26 +}
27 +
28 +Process::Process(winrt::Microsoft::WSL::Containers::ProcessSettings const& settings) : m_settings(settings)
29 +{
30 + if (m_settings)
31 + {
32 + m_outputMode = GetImplementation(m_settings)->OutputMode();
33 + ApplyCallbacksToSettings();
34 + }
35 +}
36 +
37 +void Process::ApplyCallbacksToSettings()
38 +{
39 + // Callbacks are only used with OutputMode::Event.
40 + // Stream and Discard modes use the exit event path (StartWaitingForExitAsync).
41 + if (m_outputMode != ProcessOutputMode::Event)
42 + {
43 + return;
44 + }
45 +
46 + auto settingsPtr = GetStructPointer(m_settings);
47 +
48 + WslcProcessCallbacks callbacks{};
49 + callbacks.onExit = ExitCallback;
50 + callbacks.onStdOut = OutputCallback;
51 + callbacks.onStdErr = OutputCallback;
52 +
53 + winrt::check_hresult(WslcSetProcessSettingsCallbacks(settingsPtr, &callbacks, this));
54 +}
55 +
56 +void Process::StartWaitingForExit()
57 +{
58 + m_waitForExitAction = StartWaitingForExitAsync();
59 +}
60 +
61 +winrt::Windows::Foundation::IAsyncAction Process::StartWaitingForExitAsync()
62 +{
63 + // Event mode uses the exit callback set in ApplyCallbacksToSettings; no need to wait here.
64 + if (m_outputMode == ProcessOutputMode::Event)
65 + {
66 + co_return;
67 + }
68 +
69 + wil::unique_handle exitEventHandle;
70 + winrt::check_hresult(WslcGetProcessExitEvent(ToHandle(), exitEventHandle.put()));
71 +
72 + // Allow the wait to be cancelled even if suspended for resume_on_signal.
73 + auto cancellation = co_await winrt::get_cancellation_token();
74 + cancellation.enable_propagation();
75 +
76 + auto weak_this = get_weak();
77 + co_await winrt::resume_on_signal(exitEventHandle.get());
78 +
79 + try
80 + {
81 + if (auto strong_this = weak_this.get())
82 + {
83 + strong_this->m_exitedEvent(strong_this->ExitCode());
84 + }
85 + }
86 + CATCH_LOG();
87 +}
88 +
89 +void Process::AttachHandle(WslcProcess handle)
90 +{
91 + if (m_process)
92 + {
93 + throw winrt::hresult_illegal_method_call(L"Process handle has already been attached");
94 + }
95 +
96 + m_process.reset(handle);
97 + StartWaitingForExit();
98 +}
99 +
100 +ProcessOutputMode Process::OutputMode()
101 +{
102 + return m_outputMode;
103 +}
104 +
105 void Process::Start()
106 {
22 - throw hresult_not_implemented();
107 + EnsureCanStart();
108 +
109 + wil::unique_cotaskmem_string errorMessage;
110 + auto hr = WslcCreateContainerProcess(GetHandle(m_container), GetStructPointer(m_settings), m_process.put(), errorMessage.put());
111 + THROW_MSG_IF_FAILED(hr, errorMessage);
112 +
113 + m_container = nullptr;
114 + m_settings = nullptr;
115 +
116 + StartWaitingForExit();
117 }
118 +
119 +void Process::EnsureStarted() const
120 +{
121 + if (!m_process)
122 + {
123 + throw winrt::hresult_illegal_method_call(L"Process has not been started");
124 + }
125 +}
126 +
127 +void Process::EnsureNotStarted() const
128 +{
129 + if (m_process)
130 + {
131 + throw winrt::hresult_illegal_method_call(L"Process has already been started");
132 + }
133 +}
134 +
135 +void Process::EnsureCanStart() const
136 +{
137 + EnsureNotStarted();
138 +
139 + if (!m_container)
140 + {
141 + throw winrt::hresult_illegal_method_call(L"Start() cannot be called on the init process, it is started by the container");
142 + }
143 +
144 + auto cmdLine = GetImplementation(m_settings)->CmdLine();
145 + if (!cmdLine || cmdLine.Size() == 0)
146 + {
147 + throw winrt::hresult_invalid_argument(L"Process requires a non-empty CmdLine to start");
148 + }
149 +}
150 +
151 uint32_t Process::Pid()
152 {
26 - throw hresult_not_implemented();
153 + uint32_t pid;
154 + winrt::check_hresult(WslcGetProcessPid(ToHandle(), &pid));
155 + return pid;
156 }
157 +
158 winrt::Microsoft::WSL::Containers::ProcessState Process::State()
159 {
30 - throw hresult_not_implemented();
160 + WslcProcessState state;
161 + winrt::check_hresult(WslcGetProcessState(ToHandle(), &state));
162 + return static_cast<winrt::Microsoft::WSL::Containers::ProcessState>(state);
163 }
164 +
165 int32_t Process::ExitCode()
166 {
34 - throw hresult_not_implemented();
167 + int32_t exitCode;
168 + winrt::check_hresult(WslcGetProcessExitCode(ToHandle(), &exitCode));
169 + return exitCode;
170 }
171 +
172 void Process::Signal(winrt::Microsoft::WSL::Containers::Signal const& signal)
173 {
38 - throw hresult_not_implemented();
174 + winrt::check_hresult(WslcSignalProcess(ToHandle(), static_cast<WslcSignal>(signal)));
175 }
176 +
177 winrt::Windows::Storage::Streams::IInputStream Process::GetOutputStream(winrt::Microsoft::WSL::Containers::ProcessOutputHandle const& outputHandle)
178 {
42 - throw hresult_not_implemented();
179 + if (m_outputMode != ProcessOutputMode::Stream)
180 + {
181 + throw winrt::hresult_illegal_method_call(L"GetOutputStream requires OutputMode::Stream");
182 + }
183 +
184 + wil::unique_handle handle;
185 + winrt::check_hresult(WslcGetProcessIOHandle(ToHandle(), static_cast<WslcProcessIOHandle>(outputHandle), handle.put()));
186 + return winrt::make<IOHandleInputStream>(std::move(handle));
187 }
188 +
189 winrt::Windows::Storage::Streams::IOutputStream Process::GetInputStream()
190 {
46 - throw hresult_not_implemented();
191 + wil::unique_handle handle;
192 + winrt::check_hresult(WslcGetProcessIOHandle(ToHandle(), WSLC_PROCESS_IO_HANDLE_STDIN, handle.put()));
193 + return winrt::make<IOHandleOutputStream>(std::move(handle));
194 }
195 +
196 winrt::event_token Process::OutputReceived(winrt::Microsoft::WSL::Containers::ProcessOutputHandler const& handler)
197 {
50 - throw hresult_not_implemented();
198 + if (m_outputMode != ProcessOutputMode::Event)
199 + {
200 + throw winrt::hresult_illegal_method_call(L"OutputReceived requires OutputMode::Event");
201 + }
202 +
203 + return m_outputReceivedEvent.add(handler);
204 }
205 +
206 void Process::OutputReceived(winrt::event_token const& token) noexcept
207 {
54 - assert(false); // TODO: not implemented, but this can't throw
208 + m_outputReceivedEvent.remove(token);
209 }
210 +
211 winrt::event_token Process::ErrorReceived(winrt::Microsoft::WSL::Containers::ProcessOutputHandler const& handler)
212 {
58 - throw hresult_not_implemented();
213 + if (m_outputMode != ProcessOutputMode::Event)
214 + {
215 + throw winrt::hresult_illegal_method_call(L"ErrorReceived requires OutputMode::Event");
216 + }
217 +
218 + return m_errorReceivedEvent.add(handler);
219 }
220 +
221 void Process::ErrorReceived(winrt::event_token const& token) noexcept
222 {
62 - assert(false); // TODO: not implemented, but this can't throw
223 + m_errorReceivedEvent.remove(token);
224 }
225 +
226 winrt::event_token Process::Exited(winrt::Microsoft::WSL::Containers::ProcessExitHandler const& handler)
227 {
66 - throw hresult_not_implemented();
228 + return m_exitedEvent.add(handler);
229 }
230 +
231 void Process::Exited(winrt::event_token const& token) noexcept
232 {
70 - assert(false); // TODO: not implemented, but this can't throw
233 + m_exitedEvent.remove(token);
234 +}
235 +
236 +void CALLBACK Process::OutputCallback(WslcProcessIOHandle ioHandle, _In_reads_bytes_(dataBytes) const BYTE* data, _In_ uint32_t dataBytes, _In_opt_ PVOID context) noexcept
237 +{
238 + try
239 + {
240 + auto process = static_cast<Process*>(context);
241 +
242 + auto& outputEvent = (ioHandle == WSLC_PROCESS_IO_HANDLE_STDOUT) ? process->m_outputReceivedEvent : process->m_errorReceivedEvent;
243 + winrt::array_view<const uint8_t> buffer{data, dataBytes};
244 + outputEvent(buffer);
245 + }
246 + CATCH_LOG();
247 +}
248 +
249 +void CALLBACK Process::ExitCallback(INT32 exitCode, _In_opt_ PVOID context) noexcept
250 +{
251 + try
252 + {
253 + auto process = static_cast<Process*>(context);
254 + process->m_exitedEvent(exitCode);
255 + }
256 + CATCH_LOG();
257 +}
258 +
259 +WslcProcess Process::ToHandle()
260 +{
261 + EnsureStarted();
262 + return m_process.get();
263 }
264 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/Process.h
+50 -1
@@ -9,16 +9,19 @@ Module Name:
9 Abstract:
10
11 This file contains the definition of the WinRT wrapper for the WSLC SDK Process class.
12 -
12 --*/
13
14 #pragma once
15 #include "Microsoft.WSL.Containers.Process.g.h"
16 +#include "Helpers.h"
17
18 namespace winrt::Microsoft::WSL::Containers::implementation {
19 +
20 struct Process : ProcessT<Process>
21 {
22 Process() = default;
23 + Process(winrt::Microsoft::WSL::Containers::ProcessSettings const& settings); // For the init process
24 + Process(winrt::Microsoft::WSL::Containers::Container const& container, winrt::Microsoft::WSL::Containers::ProcessSettings const& settings);
25
26 void Start();
27 uint32_t Pid();
@@ -33,5 +36,51 @@ struct Process : ProcessT<Process>
36 void ErrorReceived(winrt::event_token const& token) noexcept;
37 winrt::event_token Exited(winrt::Microsoft::WSL::Containers::ProcessExitHandler const& handler);
38 void Exited(winrt::event_token const& token) noexcept;
39 +
40 + WslcProcess ToHandle();
41 + ProcessOutputMode OutputMode();
42 + void AttachHandle(WslcProcess handle);
43 +
44 + static void final_release(std::unique_ptr<Process> self)
45 + {
46 + self->m_process.reset();
47 + if (self->m_waitForExitAction)
48 + {
49 + self->m_waitForExitAction.Cancel();
50 + }
51 + }
52 +
53 +private:
54 + void EnsureStarted() const;
55 + void EnsureNotStarted() const;
56 + void EnsureCanStart() const;
57 +
58 + void ApplyCallbacksToSettings();
59 + void StartWaitingForExit();
60 + winrt::Windows::Foundation::IAsyncAction StartWaitingForExitAsync();
61 +
62 + static void CALLBACK OutputCallback(
63 + WslcProcessIOHandle ioHandle, _In_reads_bytes_(dataBytes) const BYTE* data, _In_ uint32_t dataBytes, _In_opt_ PVOID context) noexcept;
64 + static void CALLBACK ExitCallback(INT32 exitCode, _In_opt_ PVOID context) noexcept;
65 +
66 + // Only kept until Start() is called
67 + winrt::Microsoft::WSL::Containers::Container m_container{nullptr};
68 + winrt::Microsoft::WSL::Containers::ProcessSettings m_settings{nullptr};
69 +
70 + winrt::Microsoft::WSL::Containers::ProcessOutputMode m_outputMode{winrt::Microsoft::WSL::Containers::ProcessOutputMode::Discard};
71 +
72 + winrt::Windows::Foundation::IAsyncAction m_waitForExitAction{nullptr};
73 +
74 + // For output mode Event
75 + winrt::event<winrt::Microsoft::WSL::Containers::ProcessOutputHandler> m_outputReceivedEvent;
76 + winrt::event<winrt::Microsoft::WSL::Containers::ProcessOutputHandler> m_errorReceivedEvent;
77 + winrt::event<winrt::Microsoft::WSL::Containers::ProcessExitHandler> m_exitedEvent;
78 +
79 + // Releasing the process handle will disconnect the callbacks.
80 + // Keep this at the end so that it is released first, ensuring the events aren't destroyed while they may still be signaled.
81 + wil::unique_any<WslcProcess, decltype(&WslcReleaseProcess), &WslcReleaseProcess> m_process{nullptr};
82 };
83 +
84 } // namespace winrt::Microsoft::WSL::Containers::implementation
85 +
86 +DEFINE_TYPE_HELPERS(Process);
src/windows/WslcSDK/winrt/ProcessSettings.cpp
+113 -6
@@ -14,31 +14,138 @@ Abstract:
14
15 #include "precomp.h"
16 #include "ProcessSettings.h"
17 +#include "Process.h"
18 #include "Microsoft.WSL.Containers.ProcessSettings.g.cpp"
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 +
22 +StringArray::StringArray(size_t capacity)
23 +{
24 + m_strings.reserve(capacity);
25 + m_rawStrings.reserve(capacity);
26 +}
27 +
28 +void StringArray::Add(std::string&& s)
29 +{
30 + m_strings.push_back(std::move(s));
31 + m_rawStrings.push_back(m_strings.back().c_str());
32 +}
33 +
34 +PCSTR* StringArray::GetRawPointer()
35 +{
36 + return m_rawStrings.data();
37 +}
38 +
39 hstring ProcessSettings::WorkingDirectory()
40 {
22 - throw hresult_not_implemented();
41 + return winrt::to_hstring(m_workingDirectory);
42 }
43 +
44 void ProcessSettings::WorkingDirectory(hstring const& value)
45 {
26 - throw hresult_not_implemented();
46 + if (m_processSettings)
47 + {
48 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
49 + }
50 +
51 + m_workingDirectory = winrt::to_string(value);
52 }
53 +
54 winrt::Windows::Foundation::Collections::IVector<hstring> ProcessSettings::CmdLine()
55 {
30 - throw hresult_not_implemented();
56 + return m_cmdLine;
57 }
58 +
59 void ProcessSettings::CmdLine(winrt::Windows::Foundation::Collections::IVector<hstring> const& value)
60 {
34 - throw hresult_not_implemented();
61 + if (m_processSettings)
62 + {
63 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
64 + }
65 +
66 + if (!value)
67 + {
68 + throw winrt::hresult_error(E_POINTER, L"CmdLine cannot be null");
69 + }
70 +
71 + m_cmdLine = value;
72 }
73 +
74 winrt::Windows::Foundation::Collections::IMap<hstring, hstring> ProcessSettings::EnvironmentVariables()
75 {
38 - throw hresult_not_implemented();
76 + return m_environmentVariables;
77 }
78 +
79 void ProcessSettings::EnvironmentVariables(winrt::Windows::Foundation::Collections::IMap<hstring, hstring> const& value)
80 {
42 - throw hresult_not_implemented();
81 + if (m_processSettings)
82 + {
83 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
84 + }
85 +
86 + if (!value)
87 + {
88 + throw winrt::hresult_error(E_POINTER, L"EnvironmentVariables cannot be null");
89 + }
90 +
91 + m_environmentVariables = value;
92 +}
93 +
94 +winrt::Microsoft::WSL::Containers::ProcessOutputMode ProcessSettings::OutputMode()
95 +{
96 + return m_outputMode;
97 +}
98 +
99 +void ProcessSettings::OutputMode(winrt::Microsoft::WSL::Containers::ProcessOutputMode const& value)
100 +{
101 + if (m_processSettings)
102 + {
103 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
104 + }
105 +
106 + m_outputMode = value;
107 }
108 +
109 +WslcProcessSettings* ProcessSettings::ToStructPointer()
110 +{
111 + if (m_processSettings)
112 + {
113 + return m_processSettings.get();
114 + }
115 +
116 + m_processSettings = std::make_unique<WslcProcessSettings>();
117 + winrt::check_hresult(WslcInitProcessSettings(m_processSettings.get()));
118 +
119 + if (!m_workingDirectory.empty())
120 + {
121 + winrt::check_hresult(WslcSetProcessSettingsWorkingDirectory(m_processSettings.get(), m_workingDirectory.c_str()));
122 + }
123 +
124 + if (m_cmdLine && m_cmdLine.Size() > 0)
125 + {
126 + auto argc = m_cmdLine.Size();
127 + m_cmdLineStrings = StringArray{argc};
128 + for (auto const& arg : m_cmdLine)
129 + {
130 + m_cmdLineStrings.Add(winrt::to_string(arg));
131 + }
132 +
133 + winrt::check_hresult(WslcSetProcessSettingsCmdLine(m_processSettings.get(), m_cmdLineStrings.GetRawPointer(), argc));
134 + }
135 +
136 + if (m_environmentVariables.Size() > 0)
137 + {
138 + auto size = m_environmentVariables.Size();
139 + m_envStrings = StringArray{size};
140 + for (auto const& [key, value] : m_environmentVariables)
141 + {
142 + m_envStrings.Add(winrt::to_string(key) + "=" + winrt::to_string(value));
143 + }
144 +
145 + winrt::check_hresult(WslcSetProcessSettingsEnvVariables(m_processSettings.get(), m_envStrings.GetRawPointer(), size));
146 + }
147 +
148 + return m_processSettings.get();
149 +}
150 +
151 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ProcessSettings.h
+32 -1
@@ -9,13 +9,27 @@ Module Name:
9 Abstract:
10
11 This file contains the definition of the WinRT wrapper for the WSLC SDK ProcessSettings class.
12 -
12 --*/
13
14 #pragma once
15 #include "Microsoft.WSL.Containers.ProcessSettings.g.h"
16 +#include "Helpers.h"
17 +#include "Process.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 +
21 +struct StringArray
22 +{
23 + StringArray() = default;
24 + StringArray(size_t capacity);
25 + void Add(std::string&& s);
26 + PCSTR* GetRawPointer();
27 +
28 +private:
29 + std::vector<std::string> m_strings;
30 + std::vector<PCSTR> m_rawStrings;
31 +};
32 +
33 struct ProcessSettings : ProcessSettingsT<ProcessSettings>
34 {
35 ProcessSettings() = default;
@@ -26,10 +40,27 @@ struct ProcessSettings : ProcessSettingsT<ProcessSettings>
40 void CmdLine(winrt::Windows::Foundation::Collections::IVector<hstring> const& value);
41 winrt::Windows::Foundation::Collections::IMap<hstring, hstring> EnvironmentVariables();
42 void EnvironmentVariables(winrt::Windows::Foundation::Collections::IMap<hstring, hstring> const& value);
43 + winrt::Microsoft::WSL::Containers::ProcessOutputMode OutputMode();
44 + void OutputMode(winrt::Microsoft::WSL::Containers::ProcessOutputMode const& value);
45 +
46 + WslcProcessSettings* ToStructPointer();
47 +
48 +private:
49 + std::string m_workingDirectory;
50 + winrt::Windows::Foundation::Collections::IVector<hstring> m_cmdLine{winrt::single_threaded_vector<hstring>()};
51 + winrt::Windows::Foundation::Collections::IMap<hstring, hstring> m_environmentVariables{winrt::single_threaded_map<hstring, hstring>()};
52 + winrt::Microsoft::WSL::Containers::ProcessOutputMode m_outputMode{winrt::Microsoft::WSL::Containers::ProcessOutputMode::Discard};
53 +
54 + std::unique_ptr<WslcProcessSettings> m_processSettings;
55 + StringArray m_cmdLineStrings;
56 + StringArray m_envStrings;
57 };
58 } // namespace winrt::Microsoft::WSL::Containers::implementation
59 +
60 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
61 struct ProcessSettings : ProcessSettingsT<ProcessSettings, implementation::ProcessSettings>
62 {
63 };
64 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
65 +
66 +DEFINE_TYPE_HELPERS(ProcessSettings);
src/windows/WslcSDK/winrt/PullImageOptions.cpp
+44 -6
@@ -17,24 +17,62 @@ Abstract:
17 #include "Microsoft.WSL.Containers.PullImageOptions.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -PullImageOptions::PullImageOptions(hstring const& uri)
20 +
21 +PullImageOptions::PullImageOptions(hstring const& uri) : m_uri(winrt::to_string(uri))
22 {
22 - throw hresult_not_implemented();
23 + if (uri.empty())
24 + {
25 + throw hresult_invalid_argument(L"URI cannot be empty");
26 + }
27 }
28 +
29 hstring PullImageOptions::Uri()
30 {
26 - throw hresult_not_implemented();
31 + return winrt::to_hstring(m_uri);
32 }
33 +
34 void PullImageOptions::Uri(hstring const& value)
35 {
30 - throw hresult_not_implemented();
36 + if (m_pullImageOptions)
37 + {
38 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
39 + }
40 +
41 + if (value.empty())
42 + {
43 + throw hresult_invalid_argument(L"URI cannot be empty");
44 + }
45 +
46 + m_uri = winrt::to_string(value);
47 }
48 +
49 hstring PullImageOptions::RegistryAuth()
50 {
34 - throw hresult_not_implemented();
51 + return winrt::to_hstring(m_registryAuth);
52 }
53 +
54 void PullImageOptions::RegistryAuth(hstring const& value)
55 {
38 - throw hresult_not_implemented();
56 + if (m_pullImageOptions)
57 + {
58 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
59 + }
60 +
61 + m_registryAuth = winrt::to_string(value);
62 +}
63 +
64 +WslcPullImageOptions PullImageOptions::ToStruct()
65 +{
66 + if (!m_pullImageOptions)
67 + {
68 + m_pullImageOptions = std::make_unique<WslcPullImageOptions>();
69 + m_pullImageOptions->uri = m_uri.c_str();
70 + m_pullImageOptions->registryAuth = m_registryAuth.empty() ? nullptr : m_registryAuth.c_str();
71 + m_pullImageOptions->progressCallback = nullptr;
72 + m_pullImageOptions->progressCallbackContext = nullptr;
73 + }
74 +
75 + return *m_pullImageOptions;
76 }
77 +
78 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/PullImageOptions.h
+12
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.PullImageOptions.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct PullImageOptions : PullImageOptionsT<PullImageOptions>
@@ -25,10 +26,21 @@ struct PullImageOptions : PullImageOptionsT<PullImageOptions>
26 void Uri(hstring const& value);
27 hstring RegistryAuth();
28 void RegistryAuth(hstring const& value);
29 +
30 + WslcPullImageOptions ToStruct();
31 +
32 +private:
33 + std::string m_uri;
34 + std::string m_registryAuth;
35 +
36 + std::unique_ptr<WslcPullImageOptions> m_pullImageOptions;
37 };
38 } // namespace winrt::Microsoft::WSL::Containers::implementation
39 +
40 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
41 struct PullImageOptions : PullImageOptionsT<PullImageOptions, implementation::PullImageOptions>
42 {
43 };
44 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
45 +
46 +DEFINE_TYPE_HELPERS(PullImageOptions);
src/windows/WslcSDK/winrt/PushImageOptions.cpp
+55 -6
@@ -17,24 +17,73 @@ Abstract:
17 #include "Microsoft.WSL.Containers.PushImageOptions.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -PushImageOptions::PushImageOptions(hstring const& image, hstring const& registryAuth)
20 +
21 +PushImageOptions::PushImageOptions(hstring const& image, hstring const& registryAuth) :
22 + m_image(winrt::to_string(image)), m_registryAuth(winrt::to_string(registryAuth))
23 {
22 - throw hresult_not_implemented();
24 + if (image.empty())
25 + {
26 + throw hresult_invalid_argument(L"Image cannot be empty");
27 + }
28 +
29 + if (registryAuth.empty())
30 + {
31 + throw hresult_invalid_argument(L"Registry auth cannot be empty");
32 + }
33 }
34 +
35 hstring PushImageOptions::Image()
36 {
26 - throw hresult_not_implemented();
37 + return winrt::to_hstring(m_image);
38 }
39 +
40 void PushImageOptions::Image(hstring const& value)
41 {
30 - throw hresult_not_implemented();
42 + if (m_pushImageOptions)
43 + {
44 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
45 + }
46 +
47 + if (value.empty())
48 + {
49 + throw hresult_invalid_argument(L"Image cannot be empty");
50 + }
51 +
52 + m_image = winrt::to_string(value);
53 }
54 +
55 hstring PushImageOptions::RegistryAuth()
56 {
34 - throw hresult_not_implemented();
57 + return winrt::to_hstring(m_registryAuth);
58 }
59 +
60 void PushImageOptions::RegistryAuth(hstring const& value)
61 {
38 - throw hresult_not_implemented();
62 + if (m_pushImageOptions)
63 + {
64 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
65 + }
66 +
67 + if (value.empty())
68 + {
69 + throw hresult_invalid_argument(L"Registry auth cannot be empty");
70 + }
71 +
72 + m_registryAuth = winrt::to_string(value);
73 }
74 +
75 +WslcPushImageOptions PushImageOptions::ToStruct()
76 +{
77 + if (!m_pushImageOptions)
78 + {
79 + m_pushImageOptions = std::make_unique<WslcPushImageOptions>();
80 + m_pushImageOptions->image = m_image.c_str();
81 + m_pushImageOptions->registryAuth = m_registryAuth.c_str();
82 + m_pushImageOptions->progressCallback = nullptr;
83 + m_pushImageOptions->progressCallbackContext = nullptr;
84 + }
85 +
86 + return *m_pushImageOptions;
87 +}
88 +
89 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/PushImageOptions.h
+12
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.PushImageOptions.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct PushImageOptions : PushImageOptionsT<PushImageOptions>
@@ -25,10 +26,21 @@ struct PushImageOptions : PushImageOptionsT<PushImageOptions>
26 void Image(hstring const& value);
27 hstring RegistryAuth();
28 void RegistryAuth(hstring const& value);
29 +
30 + WslcPushImageOptions ToStruct();
31 +
32 +private:
33 + std::string m_image;
34 + std::string m_registryAuth;
35 +
36 + std::unique_ptr<WslcPushImageOptions> m_pushImageOptions;
37 };
38 } // namespace winrt::Microsoft::WSL::Containers::implementation
39 +
40 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
41 struct PushImageOptions : PushImageOptionsT<PushImageOptions, implementation::PushImageOptions>
42 {
43 };
44 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
45 +
46 +DEFINE_TYPE_HELPERS(PushImageOptions);
src/windows/WslcSDK/winrt/ServiceVersion.cpp
+10 -3
@@ -17,16 +17,23 @@ Abstract:
17 #include "Microsoft.WSL.Containers.ServiceVersion.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 +ServiceVersion::ServiceVersion(uint32_t major, uint32_t minor, uint32_t revision) :
21 + m_major(major), m_minor(minor), m_revision(revision)
22 +{
23 +}
24 +
25 uint32_t ServiceVersion::Major()
26 {
22 - throw hresult_not_implemented();
27 + return m_major;
28 }
29 +
30 uint32_t ServiceVersion::Minor()
31 {
26 - throw hresult_not_implemented();
32 + return m_minor;
33 }
34 +
35 uint32_t ServiceVersion::Revision()
36 {
30 - throw hresult_not_implemented();
37 + return m_revision;
38 }
39 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/ServiceVersion.h
+9
@@ -14,14 +14,23 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.ServiceVersion.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct ServiceVersion : ServiceVersionT<ServiceVersion>
21 {
22 ServiceVersion() = default;
23 + ServiceVersion(uint32_t major, uint32_t minor, uint32_t revision);
24
25 uint32_t Major();
26 uint32_t Minor();
27 uint32_t Revision();
28 +
29 +private:
30 + uint32_t m_major{};
31 + uint32_t m_minor{};
32 + uint32_t m_revision{};
33 };
34 } // namespace winrt::Microsoft::WSL::Containers::implementation
35 +
36 +DEFINE_TYPE_HELPERS(ServiceVersion);
src/windows/WslcSDK/winrt/Session.cpp
+254 -27
@@ -14,73 +14,300 @@ Abstract:
14
15 #include "precomp.h"
16 #include "Session.h"
17 +#include "SessionSettings.h"
18 #include "Microsoft.WSL.Containers.Session.g.cpp"
19
20 +using namespace winrt::Windows::Foundation;
21 +using namespace winrt::Windows::Foundation::Collections;
22 +
23 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -Session::Session(winrt::Microsoft::WSL::Containers::SessionSettings const& settings)
24 +
25 +namespace {
26 +
27 + HRESULT CALLBACK ImageProgressCallback(const WslcImageProgressMessage* progressMessage, PVOID context) noexcept
28 + {
29 + try
30 + {
31 + auto progress = winrt::make<implementation::ImageProgress>(progressMessage);
32 + ProgressCallbackHelper<decltype(progress)>::ReportProgress(context, progress);
33 + }
34 + CATCH_LOG();
35 + return S_OK;
36 + }
37 +
38 +} // namespace
39 +
40 +Session::Session(winrt::Microsoft::WSL::Containers::SessionSettings const& settings) : m_settings(settings)
41 {
22 - throw hresult_not_implemented();
42 + if (!m_settings)
43 + {
44 + throw winrt::hresult_error(E_POINTER, L"Session settings cannot be null");
45 + }
46 }
47 +
48 void Session::Start()
49 {
26 - throw hresult_not_implemented();
50 + if (m_session)
51 + {
52 + throw winrt::hresult_illegal_method_call(L"Session has already been started");
53 + }
54 +
55 + winrt::check_hresult(WslcSetSessionSettingsTerminationCallback(GetStructPointer(m_settings), TerminatedCallback, /* context */ this));
56 +
57 + wil::unique_cotaskmem_string errorMessage;
58 + auto hr = WslcCreateSession(GetStructPointer(m_settings), m_session.put(), errorMessage.put());
59 + THROW_MSG_IF_FAILED(hr, errorMessage);
60 + m_settings = nullptr;
61 }
62 +
63 +void Session::EnsureStarted() const
64 +{
65 + if (!m_session)
66 + {
67 + throw winrt::hresult_illegal_method_call(L"Session has not been started");
68 + }
69 +}
70 +
71 void Session::Terminate()
72 {
30 - throw hresult_not_implemented();
73 + winrt::check_hresult(WslcTerminateSession(ToHandle()));
74 }
75 +
76 winrt::Microsoft::WSL::Containers::Container Session::CreateContainer(winrt::Microsoft::WSL::Containers::ContainerSettings const& containerSettings)
77 {
34 - throw hresult_not_implemented();
78 + EnsureStarted();
79 +
80 + if (!containerSettings)
81 + {
82 + throw winrt::hresult_error(E_POINTER, L"Container settings cannot be null");
83 + }
84 +
85 + return winrt::make<implementation::Container>(ToHandle(), containerSettings);
86 }
36 -winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::PullImageAsync(
37 - winrt::Microsoft::WSL::Containers::PullImageOptions options)
87 +
88 +IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::PullImageAsync(winrt::Microsoft::WSL::Containers::PullImageOptions options)
89 {
39 - throw hresult_not_implemented();
90 + if (!options)
91 + {
92 + throw winrt::hresult_error(E_POINTER, L"Options for pull cannot be null");
93 + }
94 +
95 + EnsureStarted();
96 +
97 + auto self = get_strong(); // keep session alive across suspension
98 + co_await winrt::resume_background();
99 +
100 + auto context = ProgressCallbackHelper<winrt::Microsoft::WSL::Containers::ImageProgress>{co_await winrt::get_progress_token()};
101 +
102 + auto pullOptions = GetStruct(options);
103 + pullOptions.progressCallback = ImageProgressCallback;
104 + pullOptions.progressCallbackContext = &context;
105 +
106 + wil::unique_cotaskmem_string errorMessage;
107 + auto hr = WslcPullSessionImage(ToHandle(), &pullOptions, errorMessage.put());
108 + THROW_MSG_IF_FAILED(hr, errorMessage);
109 }
41 -winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::ImportImageAsync(hstring path, hstring imageName)
110 +
111 +IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::ImportImageAsync(hstring path, hstring imageName)
112 {
43 - throw hresult_not_implemented();
113 + if (path.empty())
114 + {
115 + throw winrt::hresult_invalid_argument(L"Path cannot be empty");
116 + }
117 +
118 + if (imageName.empty())
119 + {
120 + throw winrt::hresult_invalid_argument(L"Image name cannot be empty");
121 + }
122 +
123 + EnsureStarted();
124 +
125 + auto self = get_strong(); // keep session alive across suspension
126 + co_await winrt::resume_background();
127 +
128 + auto context = ProgressCallbackHelper<winrt::Microsoft::WSL::Containers::ImageProgress>{co_await winrt::get_progress_token()};
129 +
130 + auto name = winrt::to_string(imageName);
131 +
132 + WslcImportImageOptions importOptions{};
133 + importOptions.progressCallback = ImageProgressCallback;
134 + importOptions.progressCallbackContext = &context;
135 +
136 + wil::unique_cotaskmem_string errorMessage;
137 + auto hr = WslcImportSessionImageFromFile(ToHandle(), name.c_str(), path.c_str(), &importOptions, errorMessage.put());
138 + THROW_MSG_IF_FAILED(hr, errorMessage);
139 }
45 -winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::LoadImageAsync(hstring path)
140 +
141 +IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::LoadImageAsync(hstring path)
142 {
47 - throw hresult_not_implemented();
143 + if (path.empty())
144 + {
145 + throw winrt::hresult_invalid_argument(L"Path cannot be empty");
146 + }
147 +
148 + EnsureStarted();
149 +
150 + auto self = get_strong(); // keep session alive across suspension
151 + co_await winrt::resume_background();
152 +
153 + auto context = ProgressCallbackHelper<winrt::Microsoft::WSL::Containers::ImageProgress>{co_await winrt::get_progress_token()};
154 +
155 + WslcLoadImageOptions loadOptions{};
156 + loadOptions.progressCallback = ImageProgressCallback;
157 + loadOptions.progressCallbackContext = &context;
158 +
159 + wil::unique_cotaskmem_string errorMessage;
160 + auto hr = WslcLoadSessionImageFromFile(ToHandle(), path.c_str(), &loadOptions, errorMessage.put());
161 + THROW_MSG_IF_FAILED(hr, errorMessage);
162 }
49 -winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::PushImageAsync(
50 - winrt::Microsoft::WSL::Containers::PushImageOptions options)
163 +
164 +IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::ImageProgress> Session::PushImageAsync(winrt::Microsoft::WSL::Containers::PushImageOptions options)
165 {
52 - throw hresult_not_implemented();
166 + if (!options)
167 + {
168 + throw winrt::hresult_error(E_POINTER, L"Options for push cannot be null");
169 + }
170 +
171 + EnsureStarted();
172 +
173 + auto self = get_strong(); // keep session alive across suspension
174 + co_await winrt::resume_background();
175 +
176 + auto context = ProgressCallbackHelper<winrt::Microsoft::WSL::Containers::ImageProgress>{co_await winrt::get_progress_token()};
177 +
178 + auto pushOptions = GetStruct(options);
179 + pushOptions.progressCallback = ImageProgressCallback;
180 + pushOptions.progressCallbackContext = &context;
181 +
182 + wil::unique_cotaskmem_string errorMessage;
183 + auto hr = WslcPushSessionImage(ToHandle(), &pushOptions, errorMessage.put());
184 + THROW_MSG_IF_FAILED(hr, errorMessage);
185 }
186 +
187 void Session::DeleteImage(hstring const& nameOrId)
188 {
56 - throw hresult_not_implemented();
189 + if (nameOrId.empty())
190 + {
191 + throw winrt::hresult_invalid_argument(L"Image name cannot be empty");
192 + }
193 +
194 + EnsureStarted();
195 +
196 + wil::unique_cotaskmem_string errorMessage;
197 + auto hr = WslcDeleteSessionImage(ToHandle(), winrt::to_string(nameOrId).c_str(), errorMessage.put());
198 + THROW_MSG_IF_FAILED(hr, errorMessage);
199 }
200 +
201 void Session::TagImage(winrt::Microsoft::WSL::Containers::TagImageOptions const& options)
202 {
60 - throw hresult_not_implemented();
203 + if (!options)
204 + {
205 + throw winrt::hresult_error(E_POINTER, L"Tag image options cannot be null");
206 + }
207 +
208 + EnsureStarted();
209 +
210 + wil::unique_cotaskmem_string errorMessage;
211 + auto hr = WslcTagSessionImage(ToHandle(), GetStructPointer(options), errorMessage.put());
212 + THROW_MSG_IF_FAILED(hr, errorMessage);
213 }
214 +
215 void Session::CreateVhdVolume(winrt::Microsoft::WSL::Containers::VhdOptions const& options)
216 {
64 - throw hresult_not_implemented();
217 + if (!options)
218 + {
219 + throw winrt::hresult_error(E_POINTER, L"VHD options cannot be null");
220 + }
221 +
222 + EnsureStarted();
223 +
224 + wil::unique_cotaskmem_string errorMessage;
225 + auto hr = WslcCreateSessionVhdVolume(ToHandle(), GetStructPointer(options), errorMessage.put());
226 + THROW_MSG_IF_FAILED(hr, errorMessage);
227 }
228 +
229 void Session::DeleteVhdVolume(hstring const& name)
230 {
68 - throw hresult_not_implemented();
69 -}
70 -hstring Session::Authenticate(winrt::Windows::Foundation::Uri const& serverAddress, hstring const& username, hstring const& password)
71 -{
72 - throw hresult_not_implemented();
231 + if (name.empty())
232 + {
233 + throw winrt::hresult_invalid_argument(L"VHD name cannot be empty");
234 + }
235 +
236 + EnsureStarted();
237 +
238 + wil::unique_cotaskmem_string errorMessage;
239 + auto hr = WslcDeleteSessionVhdVolume(ToHandle(), winrt::to_string(name).c_str(), errorMessage.put());
240 + THROW_MSG_IF_FAILED(hr, errorMessage);
241 }
74 -winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::ImageInfo> Session::Images()
242 +
243 +hstring Session::Authenticate(Uri const& serverAddress, hstring const& username, hstring const& password)
244 {
76 - throw hresult_not_implemented();
245 + if (!serverAddress)
246 + {
247 + throw winrt::hresult_invalid_argument(L"Server address cannot be null");
248 + }
249 +
250 + if (username.empty())
251 + {
252 + throw winrt::hresult_invalid_argument(L"Username cannot be empty");
253 + }
254 +
255 + EnsureStarted();
256 +
257 + wil::unique_cotaskmem_string errorMessage;
258 + wil::unique_cotaskmem_ansistring token;
259 + auto hr = WslcSessionAuthenticate(
260 + ToHandle(),
261 + winrt::to_string(serverAddress.ToString()).c_str(),
262 + winrt::to_string(username).c_str(),
263 + winrt::to_string(password).c_str(),
264 + token.put(),
265 + errorMessage.put());
266 + THROW_MSG_IF_FAILED(hr, errorMessage);
267 + return winrt::to_hstring(token.get());
268 }
269 +
270 winrt::event_token Session::Terminated(winrt::Microsoft::WSL::Containers::SessionTerminationHandler const& handler)
271 {
80 - throw hresult_not_implemented();
272 + return m_terminatedEvent.add(handler);
273 }
274 +
275 void Session::Terminated(winrt::event_token const& token) noexcept
276 {
84 - assert(false); // TODO: not implemented, but this can't throw
277 + m_terminatedEvent.remove(token);
278 +}
279 +
280 +IVectorView<winrt::Microsoft::WSL::Containers::ImageInfo> Session::Images()
281 +{
282 + EnsureStarted();
283 +
284 + wil::unique_cotaskmem_array_ptr<WslcImageInfo> imagesArray;
285 + winrt::check_hresult(WslcListSessionImages(ToHandle(), imagesArray.put(), imagesArray.size_address<uint32_t>()));
286 +
287 + auto images = std::vector<winrt::Microsoft::WSL::Containers::ImageInfo>();
288 + images.reserve(imagesArray.size());
289 + for (uint32_t i = 0; i < imagesArray.size(); i++)
290 + {
291 + images.push_back(winrt::make<implementation::ImageInfo>(imagesArray[i]));
292 + }
293 +
294 + return winrt::single_threaded_vector(std::move(images)).GetView();
295 +}
296 +
297 +WslcSession Session::ToHandle()
298 +{
299 + EnsureStarted();
300 + return m_session.get();
301 }
302 +
303 +void CALLBACK Session::TerminatedCallback(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context) noexcept
304 +{
305 + try
306 + {
307 + auto session = static_cast<Session*>(context);
308 + session->m_terminatedEvent(static_cast<SessionTerminationReason>(reason));
309 + }
310 + CATCH_LOG();
311 +}
312 +
313 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/Session.h
+16 -1
@@ -9,11 +9,11 @@ Module Name:
9 Abstract:
10
11 This file contains the definition of the WinRT wrapper for the WSLC SDK Session class.
12 -
12 --*/
13
14 #pragma once
15 #include "Microsoft.WSL.Containers.Session.g.h"
16 +#include "Helpers.h"
17
18 namespace winrt::Microsoft::WSL::Containers::implementation {
19 struct Session : SessionT<Session>
@@ -38,6 +38,19 @@ struct Session : SessionT<Session>
38 winrt::Windows::Foundation::Collections::IVectorView<winrt::Microsoft::WSL::Containers::ImageInfo> Images();
39 winrt::event_token Terminated(winrt::Microsoft::WSL::Containers::SessionTerminationHandler const& handler);
40 void Terminated(winrt::event_token const& token) noexcept;
41 +
42 + WslcSession ToHandle();
43 +
44 +private:
45 + void EnsureStarted() const;
46 + winrt::Microsoft::WSL::Containers::SessionSettings m_settings; // Only kept until Start() is called
47 +
48 + static void CALLBACK TerminatedCallback(_In_ WslcSessionTerminationReason reason, _In_opt_ PVOID context) noexcept;
49 +
50 + // Releasing the session handle may trigger the termination callback.
51 + // Keep these two in this order so that the session handle is released before the termination event is destructed.
52 + winrt::event<winrt::Microsoft::WSL::Containers::SessionTerminationHandler> m_terminatedEvent;
53 + wil::unique_any<WslcSession, decltype(&WslcReleaseSession), &WslcReleaseSession> m_session{nullptr};
54 };
55 } // namespace winrt::Microsoft::WSL::Containers::implementation
56 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
@@ -45,3 +58,5 @@ struct Session : SessionT<Session, implementation::Session>
58 {
59 };
60 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
61 +
62 +DEFINE_TYPE_HELPERS(Session);
src/windows/WslcSDK/winrt/SessionSettings.cpp
+164 -22
@@ -15,66 +15,208 @@ Abstract:
15 #include "precomp.h"
16 #include "SessionSettings.h"
17 #include "Microsoft.WSL.Containers.SessionSettings.g.cpp"
18 +#include "Session.h"
19 +
20 +using namespace winrt::Windows::Foundation;
21
22 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -SessionSettings::SessionSettings(hstring const& name, hstring const& storagePath)
23 +SessionSettings::SessionSettings(hstring const& name, hstring const& storagePath) : m_name(name), m_storagePath(storagePath)
24 {
22 - throw hresult_not_implemented();
25 + if (name.empty())
26 + {
27 + throw winrt::hresult_invalid_argument(L"Session name cannot be empty");
28 + }
29 +
30 + if (storagePath.empty())
31 + {
32 + throw winrt::hresult_invalid_argument(L"Storage path cannot be empty");
33 + }
34 }
35 +
36 hstring SessionSettings::Name()
37 {
26 - throw hresult_not_implemented();
38 + return hstring(m_name);
39 }
40 +
41 void SessionSettings::Name(hstring const& value)
42 {
30 - throw hresult_not_implemented();
43 + if (m_sessionSettings)
44 + {
45 + throw hresult_illegal_state_change(L"Cannot change session name after session has been initialized");
46 + }
47 +
48 + if (value.empty())
49 + {
50 + throw winrt::hresult_invalid_argument(L"Session name cannot be empty");
51 + }
52 +
53 + m_name = value;
54 }
55 +
56 hstring SessionSettings::StoragePath()
57 {
34 - throw hresult_not_implemented();
58 + return hstring(m_storagePath);
59 }
60 +
61 void SessionSettings::StoragePath(hstring const& value)
62 {
38 - throw hresult_not_implemented();
63 + if (m_sessionSettings)
64 + {
65 + throw hresult_illegal_state_change(L"Cannot change storage path after session has been initialized");
66 + }
67 +
68 + if (value.empty())
69 + {
70 + throw winrt::hresult_invalid_argument(L"Storage path cannot be empty");
71 + }
72 +
73 + m_storagePath = value;
74 }
40 -winrt::Windows::Foundation::IReference<uint32_t> SessionSettings::CpuCount()
75 +
76 +IReference<uint32_t> SessionSettings::CpuCount()
77 {
42 - throw hresult_not_implemented();
78 + return m_cpuCount;
79 }
44 -void SessionSettings::CpuCount(winrt::Windows::Foundation::IReference<uint32_t> const& value)
80 +
81 +void SessionSettings::CpuCount(IReference<uint32_t> const& value)
82 {
46 - throw hresult_not_implemented();
83 + if (m_sessionSettings)
84 + {
85 + throw hresult_illegal_state_change(L"Cannot change CPU count after session has been initialized");
86 + }
87 +
88 + if (value && value.Value() == 0)
89 + {
90 + throw hresult_invalid_argument(L"CPU count cannot be 0");
91 + }
92 +
93 + m_cpuCount = value;
94 }
48 -winrt::Windows::Foundation::IReference<uint32_t> SessionSettings::MemoryMB()
95 +
96 +IReference<uint32_t> SessionSettings::MemoryMB()
97 {
50 - throw hresult_not_implemented();
98 + return m_memoryMB;
99 }
52 -void SessionSettings::MemoryMB(winrt::Windows::Foundation::IReference<uint32_t> const& value)
100 +
101 +void SessionSettings::MemoryMB(IReference<uint32_t> const& value)
102 {
54 - throw hresult_not_implemented();
103 + if (m_sessionSettings)
104 + {
105 + throw hresult_illegal_state_change(L"Cannot change memory size after session has been initialized");
106 + }
107 +
108 + if (value && value.Value() == 0)
109 + {
110 + throw hresult_invalid_argument(L"Memory size cannot be 0");
111 + }
112 +
113 + m_memoryMB = value;
114 }
56 -winrt::Windows::Foundation::IReference<uint32_t> SessionSettings::TimeoutMS()
115 +
116 +IReference<TimeSpan> SessionSettings::Timeout()
117 {
58 - throw hresult_not_implemented();
118 + return m_timeout;
119 }
60 -void SessionSettings::TimeoutMS(winrt::Windows::Foundation::IReference<uint32_t> const& value)
120 +
121 +void SessionSettings::Timeout(IReference<TimeSpan> const& value)
122 {
62 - throw hresult_not_implemented();
123 + if (m_sessionSettings)
124 + {
125 + throw hresult_illegal_state_change(L"Cannot change timeout after session has been initialized");
126 + }
127 +
128 + if (value)
129 + {
130 + if (value.Value() == TimeSpan::zero())
131 + {
132 + throw hresult_invalid_argument(L"Timeout cannot be 0");
133 + }
134 +
135 + // The C API takes the timeout in milliseconds as a uint32_t, so we need to validate that the value is within range.
136 + auto timeoutMS = std::chrono::duration_cast<std::chrono::milliseconds>(value.Value()).count();
137 + if (timeoutMS > std::numeric_limits<uint32_t>::max())
138 + {
139 + throw hresult_invalid_argument(L"Timeout exceeds the allowed limit");
140 + }
141 +
142 + if (timeoutMS < 0)
143 + {
144 + throw hresult_invalid_argument(L"Timeout cannot be negative");
145 + }
146 + }
147 +
148 + m_timeout = value;
149 }
150 +
151 winrt::Microsoft::WSL::Containers::VhdOptions SessionSettings::VhdRequirements()
152 {
66 - throw hresult_not_implemented();
153 + return m_vhdRequirements;
154 }
155 +
156 void SessionSettings::VhdRequirements(winrt::Microsoft::WSL::Containers::VhdOptions const& value)
157 {
70 - throw hresult_not_implemented();
158 + if (m_sessionSettings)
159 + {
160 + throw hresult_illegal_state_change(L"Cannot change VHD requirements after session has been initialized");
161 + }
162 +
163 + if (!value)
164 + {
165 + throw winrt::hresult_error(E_POINTER, L"VHD requirements cannot be null");
166 + }
167 +
168 + m_vhdRequirements = value;
169 }
170 +
171 winrt::Microsoft::WSL::Containers::SessionFeatureFlags SessionSettings::FeatureFlags()
172 {
74 - throw hresult_not_implemented();
173 + return m_featureFlags;
174 }
175 +
176 void SessionSettings::FeatureFlags(winrt::Microsoft::WSL::Containers::SessionFeatureFlags const& value)
177 {
78 - throw hresult_not_implemented();
178 + if (m_sessionSettings)
179 + {
180 + throw hresult_illegal_state_change(L"Cannot change feature flags after session has been initialized");
181 + }
182 +
183 + m_featureFlags = value;
184 }
185 +
186 +WslcSessionSettings* SessionSettings::ToStructPointer()
187 +{
188 + if (m_sessionSettings)
189 + {
190 + return m_sessionSettings.get();
191 + }
192 +
193 + m_sessionSettings = std::make_unique<WslcSessionSettings>();
194 + winrt::check_hresult(WslcInitSessionSettings(m_name.c_str(), m_storagePath.c_str(), m_sessionSettings.get()));
195 +
196 + if (m_cpuCount)
197 + {
198 + winrt::check_hresult(WslcSetSessionSettingsCpuCount(m_sessionSettings.get(), m_cpuCount.Value()));
199 + }
200 +
201 + if (m_memoryMB)
202 + {
203 + winrt::check_hresult(WslcSetSessionSettingsMemory(m_sessionSettings.get(), m_memoryMB.Value()));
204 + }
205 +
206 + if (m_timeout)
207 + {
208 + auto timeoutMS = std::chrono::duration_cast<std::chrono::milliseconds>(m_timeout.Value()).count();
209 + winrt::check_hresult(WslcSetSessionSettingsTimeout(m_sessionSettings.get(), static_cast<uint32_t>(timeoutMS)));
210 + }
211 +
212 + if (m_vhdRequirements)
213 + {
214 + winrt::check_hresult(WslcSetSessionSettingsVhd(m_sessionSettings.get(), GetStructPointer(m_vhdRequirements)));
215 + }
216 +
217 + winrt::check_hresult(WslcSetSessionSettingsFeatureFlags(m_sessionSettings.get(), static_cast<WslcSessionFeatureFlags>(m_featureFlags)));
218 +
219 + return m_sessionSettings.get();
220 +}
221 +
222 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/SessionSettings.h
+20 -2
@@ -14,6 +14,8 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.SessionSettings.g.h"
17 +#include "VhdOptions.h"
18 +#include "Helpers.h"
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 struct SessionSettings : SessionSettingsT<SessionSettings>
@@ -29,16 +31,32 @@ struct SessionSettings : SessionSettingsT<SessionSettings>
31 void CpuCount(winrt::Windows::Foundation::IReference<uint32_t> const& value);
32 winrt::Windows::Foundation::IReference<uint32_t> MemoryMB();
33 void MemoryMB(winrt::Windows::Foundation::IReference<uint32_t> const& value);
32 - winrt::Windows::Foundation::IReference<uint32_t> TimeoutMS();
33 - void TimeoutMS(winrt::Windows::Foundation::IReference<uint32_t> const& value);
34 + winrt::Windows::Foundation::IReference<winrt::Windows::Foundation::TimeSpan> Timeout();
35 + void Timeout(winrt::Windows::Foundation::IReference<winrt::Windows::Foundation::TimeSpan> const& value);
36 winrt::Microsoft::WSL::Containers::VhdOptions VhdRequirements();
37 void VhdRequirements(winrt::Microsoft::WSL::Containers::VhdOptions const& value);
38 winrt::Microsoft::WSL::Containers::SessionFeatureFlags FeatureFlags();
39 void FeatureFlags(winrt::Microsoft::WSL::Containers::SessionFeatureFlags const& value);
40 +
41 + WslcSessionSettings* ToStructPointer();
42 +
43 +private:
44 + std::wstring m_name;
45 + std::wstring m_storagePath;
46 + winrt::Windows::Foundation::IReference<uint32_t> m_cpuCount{nullptr};
47 + winrt::Windows::Foundation::IReference<uint32_t> m_memoryMB{nullptr};
48 + winrt::Windows::Foundation::IReference<winrt::Windows::Foundation::TimeSpan> m_timeout{nullptr};
49 + winrt::Microsoft::WSL::Containers::VhdOptions m_vhdRequirements{nullptr};
50 + winrt::Microsoft::WSL::Containers::SessionFeatureFlags m_featureFlags{winrt::Microsoft::WSL::Containers::SessionFeatureFlags::None};
51 +
52 + std::unique_ptr<WslcSessionSettings> m_sessionSettings;
53 };
54 } // namespace winrt::Microsoft::WSL::Containers::implementation
55 +
56 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
57 struct SessionSettings : SessionSettingsT<SessionSettings, implementation::SessionSettings>
58 {
59 };
60 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
61 +
62 +DEFINE_TYPE_HELPERS(SessionSettings);
src/windows/WslcSDK/winrt/Streams.cpp new
+117
@@ -0,0 +1,117 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + Streams.cpp
8 +
9 +Abstract:
10 +
11 + This file contains the implementation of WinRT wrappers for streams.
12 +
13 +--*/
14 +
15 +#include "precomp.h"
16 +#include "Streams.h"
17 +
18 +using namespace winrt::Windows::Foundation;
19 +using namespace winrt::Windows::Storage::Streams;
20 +
21 +namespace winrt::Microsoft::WSL::Containers::implementation {
22 +
23 +IOHandleInputStream::IOHandleInputStream(wil::unique_handle&& handle) : m_handle(std::move(handle))
24 +{
25 +}
26 +
27 +IAsyncOperationWithProgress<IBuffer, uint32_t> IOHandleInputStream::ReadAsync(IBuffer buffer, uint32_t count, InputStreamOptions options)
28 +{
29 + if (!m_handle)
30 + {
31 + throw winrt::hresult_illegal_method_call(L"Stream is closed");
32 + }
33 +
34 + if (options != InputStreamOptions::None)
35 + {
36 + throw winrt::hresult_error(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), L"Only InputStreamOptions::None is supported");
37 + }
38 +
39 + if (buffer == nullptr)
40 + {
41 + throw winrt::hresult_error(E_POINTER, L"Buffer cannot be null");
42 + }
43 +
44 + if (count > buffer.Capacity())
45 + {
46 + throw winrt::hresult_error(E_BOUNDS, L"Count cannot be greater than the buffer capacity");
47 + }
48 +
49 + // Move to a background thread, ensuring that this object stays alive until the async operation completes.
50 + auto self = get_strong();
51 + co_await winrt::resume_background();
52 +
53 + DWORD bytesRead = 0;
54 + if (!ReadFile(self->m_handle.get(), buffer.data(), count, &bytesRead, nullptr))
55 + {
56 + const auto error = GetLastError();
57 + if (error == ERROR_BROKEN_PIPE)
58 + {
59 + bytesRead = 0;
60 + }
61 + else
62 + {
63 + THROW_WIN32(error);
64 + }
65 + }
66 +
67 + buffer.Length(bytesRead);
68 + co_return buffer;
69 +}
70 +
71 +void IOHandleInputStream::Close()
72 +{
73 + m_handle.reset();
74 +}
75 +
76 +IOHandleOutputStream::IOHandleOutputStream(wil::unique_handle&& handle) : m_handle(std::move(handle))
77 +{
78 +}
79 +
80 +IAsyncOperationWithProgress<uint32_t, uint32_t> IOHandleOutputStream::WriteAsync(IBuffer const& buffer)
81 +{
82 + if (!m_handle)
83 + {
84 + throw winrt::hresult_illegal_method_call(L"Stream is closed");
85 + }
86 +
87 + // Move to a background thread, ensuring that this object stays alive until the async operation completes.
88 + auto self = get_strong();
89 + co_await winrt::resume_background();
90 +
91 + DWORD bytesWritten = 0;
92 + THROW_IF_WIN32_BOOL_FALSE(WriteFile(self->m_handle.get(), buffer.data(), buffer.Length(), &bytesWritten, nullptr));
93 +
94 + co_return bytesWritten;
95 +}
96 +
97 +winrt::Windows::Foundation::IAsyncOperation<bool> IOHandleOutputStream::FlushAsync()
98 +{
99 + if (!m_handle)
100 + {
101 + throw winrt::hresult_illegal_method_call(L"Stream is closed");
102 + }
103 +
104 + // Move to a background thread, ensuring that this object stays alive until the async operation completes.
105 + auto self = get_strong();
106 + co_await winrt::resume_background();
107 +
108 + THROW_IF_WIN32_BOOL_FALSE(FlushFileBuffers(self->m_handle.get()));
109 + co_return true;
110 +}
111 +
112 +void IOHandleOutputStream::Close()
113 +{
114 + m_handle.reset();
115 +}
116 +
117 +} // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/Streams.h new
+49
@@ -0,0 +1,49 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + Streams.h
8 +
9 +Abstract:
10 +
11 + This file contains the definition of WinRT stream wrappers for IO handles.
12 +--*/
13 +
14 +#pragma once
15 +#include <winrt/Windows.Storage.Streams.h>
16 +
17 +namespace winrt::Microsoft::WSL::Containers::implementation {
18 +
19 +// WinRT IInputStream wrapper around a Windows HANDLE (read end).
20 +struct IOHandleInputStream
21 + : winrt::implements<IOHandleInputStream, winrt::Windows::Storage::Streams::IInputStream, winrt::Windows::Foundation::IClosable>
22 +{
23 + explicit IOHandleInputStream(wil::unique_handle&& handle);
24 +
25 + winrt::Windows::Foundation::IAsyncOperationWithProgress<winrt::Windows::Storage::Streams::IBuffer, uint32_t> ReadAsync(
26 + winrt::Windows::Storage::Streams::IBuffer buffer, uint32_t count, winrt::Windows::Storage::Streams::InputStreamOptions options);
27 +
28 + void Close();
29 +
30 +private:
31 + wil::unique_handle m_handle;
32 +};
33 +
34 +// WinRT IOutputStream wrapper around a Windows HANDLE (write end).
35 +struct IOHandleOutputStream
36 + : winrt::implements<IOHandleOutputStream, winrt::Windows::Storage::Streams::IOutputStream, winrt::Windows::Foundation::IClosable>
37 +{
38 + explicit IOHandleOutputStream(wil::unique_handle&& handle);
39 +
40 + winrt::Windows::Foundation::IAsyncOperationWithProgress<uint32_t, uint32_t> WriteAsync(winrt::Windows::Storage::Streams::IBuffer const& buffer);
41 + winrt::Windows::Foundation::IAsyncOperation<bool> FlushAsync();
42 +
43 + void Close();
44 +
45 +private:
46 + wil::unique_handle m_handle;
47 +};
48 +
49 +} // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/TagImageOptions.cpp
+73 -8
@@ -17,32 +17,97 @@ Abstract:
17 #include "Microsoft.WSL.Containers.TagImageOptions.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -TagImageOptions::TagImageOptions(hstring const& image, hstring const& repository, hstring const& tag)
20 +
21 +TagImageOptions::TagImageOptions(hstring const& image, hstring const& repository, hstring const& tag) :
22 + m_image(winrt::to_string(image)), m_repository(winrt::to_string(repository)), m_tag(winrt::to_string(tag))
23 {
22 - throw hresult_not_implemented();
24 + if (m_image.empty())
25 + {
26 + throw hresult_invalid_argument(L"Image cannot be empty");
27 + }
28 +
29 + if (m_repository.empty())
30 + {
31 + throw hresult_invalid_argument(L"Repository cannot be empty");
32 + }
33 +
34 + if (m_tag.empty())
35 + {
36 + throw hresult_invalid_argument(L"Tag cannot be empty");
37 + }
38 }
39 +
40 hstring TagImageOptions::Image()
41 {
26 - throw hresult_not_implemented();
42 + return winrt::to_hstring(m_image);
43 }
44 +
45 void TagImageOptions::Image(hstring const& value)
46 {
30 - throw hresult_not_implemented();
47 + if (m_tagImageOptions)
48 + {
49 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
50 + }
51 +
52 + if (value.empty())
53 + {
54 + throw hresult_invalid_argument(L"Image cannot be empty");
55 + }
56 +
57 + m_image = winrt::to_string(value);
58 }
59 +
60 hstring TagImageOptions::Repository()
61 {
34 - throw hresult_not_implemented();
62 + return winrt::to_hstring(m_repository);
63 }
64 +
65 void TagImageOptions::Repository(hstring const& value)
66 {
38 - throw hresult_not_implemented();
67 + if (m_tagImageOptions)
68 + {
69 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
70 + }
71 +
72 + if (value.empty())
73 + {
74 + throw hresult_invalid_argument(L"Repository cannot be empty");
75 + }
76 +
77 + m_repository = winrt::to_string(value);
78 }
79 +
80 hstring TagImageOptions::Tag()
81 {
42 - throw hresult_not_implemented();
82 + return winrt::to_hstring(m_tag);
83 }
84 +
85 void TagImageOptions::Tag(hstring const& value)
86 {
46 - throw hresult_not_implemented();
87 + if (m_tagImageOptions)
88 + {
89 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
90 + }
91 +
92 + if (value.empty())
93 + {
94 + throw hresult_invalid_argument(L"Tag cannot be empty");
95 + }
96 +
97 + m_tag = winrt::to_string(value);
98 +}
99 +
100 +WslcTagImageOptions* TagImageOptions::ToStructPointer()
101 +{
102 + if (!m_tagImageOptions)
103 + {
104 + m_tagImageOptions = std::make_unique<WslcTagImageOptions>();
105 + m_tagImageOptions->image = m_image.c_str();
106 + m_tagImageOptions->repo = m_repository.c_str();
107 + m_tagImageOptions->tag = m_tag.c_str();
108 + }
109 +
110 + return m_tagImageOptions.get();
111 }
112 +
113 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/TagImageOptions.h
+13
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.TagImageOptions.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct TagImageOptions : TagImageOptionsT<TagImageOptions>
@@ -27,10 +28,22 @@ struct TagImageOptions : TagImageOptionsT<TagImageOptions>
28 void Repository(hstring const& value);
29 hstring Tag();
30 void Tag(hstring const& value);
31 +
32 + WslcTagImageOptions* ToStructPointer();
33 +
34 +private:
35 + std::string m_image;
36 + std::string m_repository;
37 + std::string m_tag;
38 +
39 + std::unique_ptr<WslcTagImageOptions> m_tagImageOptions;
40 };
41 } // namespace winrt::Microsoft::WSL::Containers::implementation
42 +
43 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
44 struct TagImageOptions : TagImageOptionsT<TagImageOptions, implementation::TagImageOptions>
45 {
46 };
47 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
48 +
49 +DEFINE_TYPE_HELPERS(TagImageOptions);
src/windows/WslcSDK/winrt/VhdOptions.cpp
+69 -11
@@ -17,36 +17,94 @@ Abstract:
17 #include "Microsoft.WSL.Containers.VhdOptions.g.cpp"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 -VhdOptions::VhdOptions(hstring const& name, uint64_t sizeInBytes, winrt::Microsoft::WSL::Containers::VhdType const& type)
20 +
21 +VhdOptions::VhdOptions(hstring const& name, uint64_t sizeInBytes, VhdType const& type) :
22 + m_name(winrt::to_string(name)), m_sizeInBytes(sizeInBytes), m_type(type)
23 {
22 - throw hresult_not_implemented();
24 + if (sizeInBytes == 0)
25 + {
26 + throw hresult_invalid_argument(L"VHD size cannot be zero");
27 + }
28 }
29 +
30 hstring VhdOptions::Name()
31 {
26 - throw hresult_not_implemented();
32 + return winrt::to_hstring(m_name);
33 }
34 +
35 void VhdOptions::Name(hstring const& value)
36 {
30 - throw hresult_not_implemented();
37 + if (m_vhdOptions)
38 + {
39 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
40 + }
41 +
42 + m_name = winrt::to_string(value);
43 }
44 +
45 uint64_t VhdOptions::SizeInBytes()
46 {
34 - throw hresult_not_implemented();
47 + return m_sizeInBytes;
48 }
49 +
50 void VhdOptions::SizeInBytes(uint64_t value)
51 {
38 - throw hresult_not_implemented();
52 + if (m_vhdOptions)
53 + {
54 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
55 + }
56 +
57 + if (value == 0)
58 + {
59 + throw hresult_invalid_argument(L"VHD size cannot be zero");
60 + }
61 +
62 + m_sizeInBytes = value;
63 }
40 -winrt::Microsoft::WSL::Containers::VhdType VhdOptions::Type()
64 +
65 +VhdType VhdOptions::Type()
66 {
42 - throw hresult_not_implemented();
67 + return m_type;
68 }
44 -void VhdOptions::Type(winrt::Microsoft::WSL::Containers::VhdType const& value)
69 +
70 +void VhdOptions::Type(VhdType const& value)
71 {
46 - throw hresult_not_implemented();
72 + if (m_vhdOptions)
73 + {
74 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
75 + }
76 +
77 + m_type = value;
78 }
79 +
80 void VhdOptions::SetOwner(uint32_t uid, uint32_t gid)
81 {
50 - throw hresult_not_implemented();
82 + if (m_vhdOptions)
83 + {
84 + throw hresult_illegal_state_change(L"Cannot change value after options have been applied");
85 + }
86 +
87 + m_owner = {uid, gid};
88 }
89 +
90 +WslcVhdRequirements* VhdOptions::ToStructPointer()
91 +{
92 + if (!m_vhdOptions)
93 + {
94 + m_vhdOptions = std::make_unique<WslcVhdRequirements>();
95 + m_vhdOptions->name = m_name.c_str();
96 + m_vhdOptions->sizeBytes = m_sizeInBytes;
97 + m_vhdOptions->type = static_cast<WslcVhdType>(m_type);
98 +
99 + if (m_owner)
100 + {
101 + m_vhdOptions->uid = m_owner->first;
102 + m_vhdOptions->gid = m_owner->second;
103 + WI_SetFlag(m_vhdOptions->flags, WSLC_VHD_REQ_FLAG_OWNER);
104 + }
105 + }
106 +
107 + return m_vhdOptions.get();
108 +}
109 +
110 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/VhdOptions.h
+15
@@ -14,6 +14,8 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.VhdOptions.g.h"
17 +#include "Helpers.h"
18 +#include "Defaults.h"
19
20 namespace winrt::Microsoft::WSL::Containers::implementation {
21 struct VhdOptions : VhdOptionsT<VhdOptions>
@@ -29,10 +31,23 @@ struct VhdOptions : VhdOptionsT<VhdOptions>
31 void Type(winrt::Microsoft::WSL::Containers::VhdType const& value);
32
33 void SetOwner(uint32_t uid, uint32_t gid);
34 +
35 + WslcVhdRequirements* ToStructPointer();
36 +
37 +private:
38 + std::string m_name;
39 + uint64_t m_sizeInBytes = s_DefaultStorageSize;
40 + winrt::Microsoft::WSL::Containers::VhdType m_type = winrt::Microsoft::WSL::Containers::VhdType::Dynamic;
41 + std::optional<std::pair<uint32_t, uint32_t>> m_owner;
42 +
43 + std::unique_ptr<WslcVhdRequirements> m_vhdOptions;
44 };
45 } // namespace winrt::Microsoft::WSL::Containers::implementation
46 +
47 namespace winrt::Microsoft::WSL::Containers::factory_implementation {
48 struct VhdOptions : VhdOptionsT<VhdOptions, implementation::VhdOptions>
49 {
50 };
51 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
52 +
53 +DEFINE_TYPE_HELPERS(VhdOptions);
\ No newline at end of file
src/windows/WslcSDK/winrt/VhdRequirements.cpp deleted
-54
@@ -1,54 +0,0 @@
1 -/*++
2 -
3 -Copyright (c) Microsoft. All rights reserved.
4 -
5 -Module Name:
6 -
7 - VhdRequirements.cpp
8 -
9 -Abstract:
10 -
11 - This file contains the implementation of the WinRT wrapper for the WSLC SDK VhdRequirements class.
12 -
13 ---*/
14 -
15 -#include "precomp.h"
16 -#include "VhdRequirements.h"
17 -#include "Microsoft.WSL.Containers.VhdRequirements.g.cpp"
18 -
19 -namespace winrt::Microsoft::WSL::Containers::implementation {
20 -VhdRequirements::VhdRequirements(hstring const& name, uint64_t sizeInBytes, winrt::Microsoft::WSL::Containers::VhdType const& type) :
21 - m_name(winrt::to_string(name))
22 -{
23 - m_vhdRequirements.name = m_name.c_str();
24 - m_vhdRequirements.sizeBytes = sizeInBytes;
25 - m_vhdRequirements.type = static_cast<WslcVhdType>(type);
26 -}
27 -
28 -hstring VhdRequirements::Name()
29 -{
30 - return winrt::to_hstring(m_name);
31 -}
32 -
33 -uint64_t VhdRequirements::SizeInBytes()
34 -{
35 - return m_vhdRequirements.sizeBytes;
36 -}
37 -
38 -winrt::Microsoft::WSL::Containers::VhdType VhdRequirements::Type()
39 -{
40 - return static_cast<winrt::Microsoft::WSL::Containers::VhdType>(m_vhdRequirements.type);
41 -}
42 -
43 -void VhdRequirements::SetOwner(uint32_t uid, uint32_t gid)
44 -{
45 - m_vhdRequirements.uid = uid;
46 - m_vhdRequirements.gid = gid;
47 - m_vhdRequirements.flags = m_vhdRequirements.flags | WSLC_VHD_REQ_FLAG_OWNER;
48 -}
49 -
50 -WslcVhdRequirements* VhdRequirements::ToStructPointer()
51 -{
52 - return &m_vhdRequirements;
53 -}
54 -} // namespace winrt::Microsoft::WSL::Containers::implementation
\ No newline at end of file
src/windows/WslcSDK/winrt/VhdRequirements.h deleted
-45
@@ -1,45 +0,0 @@
1 -/*++
2 -
3 -Copyright (c) Microsoft. All rights reserved.
4 -
5 -Module Name:
6 -
7 - VhdRequirements.h
8 -
9 -Abstract:
10 -
11 - This file contains the definition of the WinRT wrapper for the WSLC SDK VhdRequirements class.
12 -
13 ---*/
14 -
15 -#pragma once
16 -#include "Microsoft.WSL.Containers.VhdRequirements.g.h"
17 -#include "Helpers.h"
18 -
19 -namespace winrt::Microsoft::WSL::Containers::implementation {
20 -struct VhdRequirements : VhdRequirementsT<VhdRequirements>
21 -{
22 - VhdRequirements() = default;
23 -
24 - VhdRequirements(hstring const& name, uint64_t sizeInBytes, winrt::Microsoft::WSL::Containers::VhdType const& type);
25 - hstring Name();
26 - uint64_t SizeInBytes();
27 - winrt::Microsoft::WSL::Containers::VhdType Type();
28 -
29 - void SetOwner(uint32_t uid, uint32_t gid);
30 -
31 - WslcVhdRequirements* ToStructPointer();
32 -
33 -private:
34 - std::string m_name;
35 - WslcVhdRequirements m_vhdRequirements{nullptr};
36 -};
37 -} // namespace winrt::Microsoft::WSL::Containers::implementation
38 -
39 -namespace winrt::Microsoft::WSL::Containers::factory_implementation {
40 -struct VhdRequirements : VhdRequirementsT<VhdRequirements, implementation::VhdRequirements>
41 -{
42 -};
43 -} // namespace winrt::Microsoft::WSL::Containers::factory_implementation
44 -
45 -DEFINE_TYPE_HELPERS(VhdRequirements);
\ No newline at end of file
src/windows/WslcSDK/winrt/WslcService.cpp
+30 -4
@@ -15,18 +15,44 @@ Abstract:
15 #include "precomp.h"
16 #include "WslcService.h"
17 #include "Microsoft.WSL.Containers.WslcService.g.cpp"
18 +#include "ServiceVersion.h"
19 +#include "InstallProgress.h"
20 +
21 +using namespace winrt::Windows::Foundation;
22
23 namespace winrt::Microsoft::WSL::Containers::implementation {
24 +
25 +namespace {
26 + void CALLBACK InstallProgressCallback(WslcComponentFlags component, uint32_t progressSteps, uint32_t totalSteps, PVOID context) noexcept
27 + {
28 + try
29 + {
30 + auto installProgress = winrt::make<implementation::InstallProgress>(
31 + static_cast<winrt::Microsoft::WSL::Containers::ComponentFlags>(component), progressSteps, totalSteps);
32 + ProgressCallbackHelper<decltype(installProgress)>::ReportProgress(context, installProgress);
33 + }
34 + CATCH_LOG();
35 + }
36 +} // namespace
37 +
38 winrt::Microsoft::WSL::Containers::ComponentFlags WslcService::GetMissingComponents()
39 {
22 - throw hresult_not_implemented();
40 + WslcComponentFlags missing;
41 + winrt::check_hresult(WslcGetMissingComponents(&missing));
42 + return static_cast<winrt::Microsoft::WSL::Containers::ComponentFlags>(missing);
43 }
44 +
45 winrt::Microsoft::WSL::Containers::ServiceVersion WslcService::GetVersion()
46 {
26 - throw hresult_not_implemented();
47 + WslcVersion version;
48 + winrt::check_hresult(WslcGetVersion(&version));
49 + return winrt::make<implementation::ServiceVersion>(version.major, version.minor, version.revision);
50 }
28 -winrt::Windows::Foundation::IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::InstallProgress> WslcService::InstallWithDependenciesAsync()
51 +
52 +IAsyncActionWithProgress<winrt::Microsoft::WSL::Containers::InstallProgress> WslcService::InstallWithDependenciesAsync()
53 {
30 - throw hresult_not_implemented();
54 + co_await winrt::resume_background();
55 + auto context = ProgressCallbackHelper<winrt::Microsoft::WSL::Containers::InstallProgress>{co_await winrt::get_progress_token()};
56 + winrt::check_hresult(WslcInstallWithDependencies(InstallProgressCallback, &context));
57 }
58 } // namespace winrt::Microsoft::WSL::Containers::implementation
src/windows/WslcSDK/winrt/WslcService.h
+3
@@ -14,6 +14,7 @@ Abstract:
14
15 #pragma once
16 #include "Microsoft.WSL.Containers.WslcService.g.h"
17 +#include "Helpers.h"
18
19 namespace winrt::Microsoft::WSL::Containers::implementation {
20 struct WslcService
@@ -30,3 +31,5 @@ struct WslcService : WslcServiceT<WslcService, implementation::WslcService>
31 {
32 };
33 } // namespace winrt::Microsoft::WSL::Containers::factory_implementation
34 +
35 +DEFINE_TYPE_HELPERS(WslcService);
src/windows/WslcSDK/winrt/precomp.h
+21
@@ -20,3 +20,24 @@ Abstract:
20 #include <wil/resource.h>
21
22 #include <winrt/Windows.Foundation.h>
23 +#include <winrt/Windows.Foundation.Collections.h>
24 +#include <winrt/Windows.Storage.Streams.h>
25 +
26 +#include "Container.h"
27 +#include "ContainerNamedVolume.h"
28 +#include "ContainerPortMapping.h"
29 +#include "ContainerSettings.h"
30 +#include "ContainerVolume.h"
31 +#include "ImageInfo.h"
32 +#include "ImageProgress.h"
33 +#include "InstallProgress.h"
34 +#include "Process.h"
35 +#include "ProcessSettings.h"
36 +#include "PullImageOptions.h"
37 +#include "PushImageOptions.h"
38 +#include "ServiceVersion.h"
39 +#include "Session.h"
40 +#include "SessionSettings.h"
41 +#include "TagImageOptions.h"
42 +#include "VhdOptions.h"
43 +#include "WslcService.h"
src/windows/WslcSDK/winrt/wslcsdk.idl
+16 -15
@@ -28,7 +28,7 @@ namespace Microsoft.WSL.Containers
28 Crashed = 2,
29 };
30
31 - delegate void SessionTerminationHandler(Session session, SessionTerminationReason reason);
31 + delegate void SessionTerminationHandler(SessionTerminationReason reason);
32
33 runtimeclass SessionSettings
34 {
@@ -39,7 +39,7 @@ namespace Microsoft.WSL.Containers
39
40 Windows.Foundation.IReference<UInt32> CpuCount;
41 Windows.Foundation.IReference<UInt32> MemoryMB;
42 - Windows.Foundation.IReference<UInt32> TimeoutMS;
42 + Windows.Foundation.IReference<Windows.Foundation.TimeSpan> Timeout;
43 VhdOptions VhdRequirements;
44 SessionFeatureFlags FeatureFlags;
45 };
@@ -123,13 +123,6 @@ namespace Microsoft.WSL.Containers
123 Boolean ReadOnly;
124 };
125
126 - [flags]
127 - enum ContainerStartFlags
128 - {
129 - None = 0x00000000,
130 - Attach = 0x00000001,
131 - };
132 -
126 enum ContainerState
127 {
128 Invalid = 0,
@@ -175,8 +168,8 @@ namespace Microsoft.WSL.Containers
168
169 runtimeclass Container
170 {
178 - void Start(ContainerStartFlags flags);
179 - void Stop(Signal signal, UInt32 timeoutSeconds);
171 + void Start();
172 + void Stop(Signal signal, Windows.Foundation.TimeSpan timeout);
173 void Delete(DeleteContainerFlags flags);
174
175 Process CreateProcess(ProcessSettings newProcessSettings);
@@ -194,6 +187,13 @@ namespace Microsoft.WSL.Containers
187 StandardError = 2,
188 };
189
190 + enum ProcessOutputMode
191 + {
192 + Discard = 0,
193 + Stream = 1,
194 + Event = 2,
195 + };
196 +
197 runtimeclass ProcessSettings
198 {
199 ProcessSettings();
@@ -201,6 +201,7 @@ namespace Microsoft.WSL.Containers
201 String WorkingDirectory;
202 IVector<String> CmdLine;
203 IMap<String, String> EnvironmentVariables;
204 + ProcessOutputMode OutputMode;
205 };
206
207 enum ProcessState
@@ -211,8 +212,8 @@ namespace Microsoft.WSL.Containers
212 Signalled = 3,
213 };
214
214 - delegate void ProcessOutputHandler(Process process, UInt8[] data);
215 - delegate void ProcessExitHandler(Process process, Int32 exitCode);
215 + delegate void ProcessOutputHandler(UInt8[] data);
216 + delegate void ProcessExitHandler(Int32 exitCode);
217
218 runtimeclass Process
219 {
@@ -276,7 +277,7 @@ namespace Microsoft.WSL.Containers
277
278 // Sets owner uid/gid on the volume root inode at mkfs time. Only
279 // supported on named volumes; setting this on a SessionSettings
279 - // VhdRequirements fails at property-set time with E_INVALIDARG.
280 + // VhdOptions fails at property-set time with E_INVALIDARG.
281 void SetOwner(UInt32 uid, UInt32 gid);
282 };
283
@@ -331,4 +332,4 @@ namespace Microsoft.WSL.Containers
332 UInt64 SizeBytes { get; };
333 Windows.Foundation.DateTime CreatedTimestamp { get; };
334 };
334 -}
\ No newline at end of file
335 +}
test/windows/CMakeLists.txt
+5 -2
@@ -11,6 +11,7 @@ set(SOURCES
11 InstallerTests.cpp
12 WSLCTests.cpp
13 WslcSdkTests.cpp
14 + WslcSdkWinRtTests.cpp
15 WindowsUpdateTests.cpp)
16
17 set(HEADERS
@@ -22,7 +23,9 @@ add_compile_definitions(INLINE_TEST_METHOD_MARKUP)
23
24 add_library(wsltests SHARED ${SOURCES} ${HEADERS})
25
25 -target_include_directories(wsltests PRIVATE ${CMAKE_SOURCE_DIR}/src/windows/WslcSDK)
26 +target_include_directories(wsltests PRIVATE
27 + ${CMAKE_SOURCE_DIR}/src/windows/WslcSDK
28 + ${CMAKE_BINARY_DIR}/src/windows/WslcSDK/winrt/${TARGET_PLATFORM}/${CMAKE_BUILD_TYPE})
29 target_link_directories(wsltests PRIVATE ${BIN})
30 target_precompile_headers(wsltests REUSE_FROM common)
31 target_link_libraries(wsltests
@@ -41,7 +44,7 @@ target_link_libraries(wsltests
44 sfc.lib
45 Crypt32.lib)
46
44 -add_dependencies(wsltests wslserviceidl wslclib wslc wslcsdk)
47 +add_dependencies(wsltests wslserviceidl wslclib wslc wslcsdk wslcsdkwinrtidl)
48 add_subdirectory(testplugin)
49 add_subdirectory(wslc)
50
test/windows/WslcSdkWinRTTests.cpp new
+1681
@@ -0,0 +1,1681 @@
1 +/*++
2 +
3 +Copyright (c) Microsoft. All rights reserved.
4 +
5 +Module Name:
6 +
7 + WslcSdkWinRtTests.cpp
8 +
9 +Abstract:
10 +
11 + This file contains test cases for the WSLC SDK WinRT projection.
12 +
13 +--*/
14 +
15 +#include "precomp.h"
16 +#include "Common.h"
17 +#include "wslcsdk.h"
18 +#include "WslcsdkPrivate.h"
19 +#include "WSLCContainerLauncher.h"
20 +#include "wslutil.h"
21 +
22 +#include "winrt/Session.h"
23 +#include "winrt/Helpers.h"
24 +
25 +#include <winrt/Microsoft.WSL.Containers.h>
26 +#include <winrt/Windows.Foundation.h>
27 +#include <winrt/Windows.Foundation.Collections.h>
28 +#include <winrt/Windows.Networking.h>
29 +#include <winrt/Windows.Storage.Streams.h>
30 +
31 +using namespace winrt::Windows::Foundation;
32 +using namespace winrt::Windows::Foundation::Collections;
33 +using namespace winrt::Windows::Storage::Streams;
34 +using namespace std::chrono_literals;
35 +
36 +namespace WSLCSDK = winrt::Microsoft::WSL::Containers;
37 +
38 +extern std::wstring g_testDataPath;
39 +extern bool g_fastTestRun;
40 +
41 +#define VERIFY_THROWS_HR(operation, expectedHr) \
42 + VERIFY_THROWS_SPECIFIC( \
43 + operation, winrt::hresult_error, [&](winrt::hresult_error const& e) { return e.code().value == expectedHr; })
44 +
45 +#define IGNORE_ERRORS(operation) \
46 + try \
47 + { \
48 + operation; \
49 + } \
50 + CATCH_LOG()
51 +#define SCOPE_CLEANUP(operation) wil::scope_exit([&]() { IGNORE_ERRORS(operation) })
52 +#define DELETE_CONTAINER_ON_SCOPE_EXIT(container) SCOPE_CLEANUP(container.Delete(WSLCSDK::DeleteContainerFlags::Force))
53 +#define DELETE_IMAGE_ON_SCOPE_EXIT(imageName) SCOPE_CLEANUP(m_defaultSession.DeleteImage(imageName))
54 +
55 +struct ProcessOutput
56 +{
57 + uint32_t ExitCode;
58 + std::wstring StandardOutput;
59 + std::wstring StandardError;
60 +};
61 +
62 +std::wstring ReadStream(IInputStream const& stream)
63 +{
64 + std::wstring output;
65 + DataReader reader{stream};
66 + reader.UnicodeEncoding(winrt::Windows::Storage::Streams::UnicodeEncoding::Utf8);
67 + uint32_t bytesRead;
68 + do
69 + {
70 + bytesRead = reader.LoadAsync(1024).get();
71 + output += reader.ReadString(bytesRead).c_str();
72 + } while (bytesRead > 0);
73 + return output;
74 +}
75 +
76 +class WslcSdkWinRtTests
77 +{
78 + WSLC_TEST_CLASS(WslcSdkWinRtTests)
79 +
80 + std::filesystem::path m_storagePath;
81 + WSLCSDK::Session m_defaultSession{nullptr};
82 +
83 + static inline constexpr auto c_testSessionName = L"wslc-winrt-test";
84 +
85 + // -----------------------------------------------------------------------
86 + // Helpers
87 + // -----------------------------------------------------------------------
88 +
89 + void StartProcessAndWaitForExit(WSLCSDK::Process const& process, std::chrono::milliseconds timeout = 2min)
90 + {
91 + std::promise<void> promise;
92 + auto autoRevoker = process.Exited(winrt::auto_revoke, [&](int32_t) { promise.set_value(); });
93 + process.Start();
94 + VERIFY_ARE_EQUAL(promise.get_future().wait_for(timeout), std::future_status::ready);
95 + }
96 +
97 + void StartContainerAndWaitForInitProcessExit(WSLCSDK::Container const& container, std::chrono::milliseconds timeout = 2min)
98 + {
99 + auto initProcess = container.InitProcess();
100 + std::promise<void> promise;
101 + auto autoRevoker = initProcess.Exited(winrt::auto_revoke, [&](int32_t) { promise.set_value(); });
102 + container.Start();
103 + VERIFY_ARE_EQUAL(promise.get_future().wait_for(timeout), std::future_status::ready);
104 + }
105 +
106 + ProcessOutput GetProcessOutput(WSLCSDK::Process const& process)
107 + {
108 + ProcessOutput output;
109 + output.ExitCode = process.ExitCode();
110 + output.StandardOutput = ReadStream(process.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardOutput));
111 + output.StandardError = ReadStream(process.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardError));
112 +
113 + return output;
114 + }
115 +
116 + struct RunContainerOptions
117 + {
118 + std::vector<winrt::hstring> cmdLine = {};
119 + WSLCSDK::ContainerFlags flags = WSLCSDK::ContainerFlags::None;
120 + std::optional<winrt::hstring> name = std::nullopt;
121 + std::chrono::milliseconds timeout = 2min;
122 + std::optional<WSLCSDK::ContainerNetworkingMode> networkingMode = std::nullopt;
123 + };
124 +
125 + // Creates and starts a one-shot container, waits for the init process to
126 + // exit, and returns the exit code.
127 + ProcessOutput RunContainerAndWaitForExit(winrt::hstring imageName, RunContainerOptions options = {})
128 + {
129 + auto procSettings = WSLCSDK::ProcessSettings();
130 + if (!options.cmdLine.empty())
131 + {
132 + procSettings.CmdLine(winrt::single_threaded_vector(std::move(options.cmdLine)));
133 + }
134 +
135 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream);
136 +
137 + auto containerSettings = WSLCSDK::ContainerSettings(imageName);
138 + containerSettings.InitProcess(procSettings);
139 + containerSettings.Flags(options.flags);
140 +
141 + if (options.name)
142 + {
143 + containerSettings.Name(options.name.value());
144 + }
145 +
146 + if (options.networkingMode)
147 + {
148 + containerSettings.NetworkingMode(options.networkingMode.value());
149 + }
150 +
151 + auto container = m_defaultSession.CreateContainer(containerSettings);
152 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
153 +
154 + StartContainerAndWaitForInitProcessExit(container, options.timeout);
155 + auto output = GetProcessOutput(container.InitProcess());
156 +
157 + IGNORE_ERRORS(container.Delete(WSLCSDK::DeleteContainerFlags::Force));
158 +
159 + return output;
160 + }
161 +
162 + bool HasImage(winrt::hstring const& imageName)
163 + {
164 + auto images = m_defaultSession.Images();
165 + return std::any_of(images.begin(), images.end(), [&](auto const& img) { return img.Name() == imageName; });
166 + }
167 +
168 + // Starts a local wslc-registry container using host-mode networking.
169 + // Host networking is not exposed by the WinRT projection, so this helper
170 + // uses WSLCContainerLauncher with the raw session handle.
171 + std::pair<wsl::windows::common::RunningWSLCContainer, std::string> StartLocalRegistry(
172 + const std::string& username = {}, const std::string& password = {}, uint16_t port = 5000)
173 + {
174 + VERIFY_IS_TRUE(HasImage(L"wslc-registry:latest"));
175 +
176 + std::vector<std::string> env = {std::format("REGISTRY_HTTP_ADDR=0.0.0.0:{}", port)};
177 + if (!username.empty())
178 + {
179 + env.push_back(std::format("USERNAME={}", username));
180 + env.push_back(std::format("PASSWORD={}", password));
181 + }
182 +
183 + wsl::windows::common::WSLCContainerLauncher launcher("wslc-registry:latest", {}, {}, env);
184 + launcher.SetEntrypoint({"/entrypoint.sh"});
185 + launcher.AddPort(port, port, AF_INET);
186 +
187 + // Get the IWSLCSession COM object from the SDK session handle.
188 + auto& comSession = *reinterpret_cast<WslcSessionImpl*>(WSLCSDK::implementation::GetHandle(m_defaultSession))->session;
189 + auto container = launcher.Launch(comSession, WSLCContainerStartFlagsNone);
190 +
191 + auto registryAddress = std::format("127.0.0.1:{}", port);
192 +
193 + // Wait for the registry to be ready by probing from the host.
194 + auto hostUrl = std::format(L"http://{}", registryAddress);
195 + ExpectHttpResponse(hostUrl.c_str(), 200, true);
196 +
197 + return {std::move(container), registryAddress};
198 + }
199 +
200 + // Tags and pushes an image to a local registry via the SDK APIs.
201 + void PushImageToRegistry(const std::string& repo, const std::string& tag, const std::string& registryAddress, const std::string& registryAuth)
202 + {
203 + const auto imageName = winrt::to_hstring(std::format("{}:{}", repo, tag));
204 + const auto registryRepo = winrt::to_hstring(std::format("{}/{}", registryAddress, repo));
205 + const auto registryImage = winrt::to_hstring(std::format("{}/{}:{}", registryAddress, repo, tag));
206 +
207 + VERIFY_IS_TRUE(HasImage(imageName));
208 +
209 + m_defaultSession.TagImage(WSLCSDK::TagImageOptions(imageName, registryRepo, winrt::to_hstring(tag)));
210 +
211 + // Ensures the registry-prefixed tag is removed after the push.
212 + auto cleanup = DELETE_IMAGE_ON_SCOPE_EXIT(registryImage);
213 +
214 + m_defaultSession.PushImageAsync(WSLCSDK::PushImageOptions(registryImage, winrt::to_hstring(registryAuth))).get();
215 + }
216 +
217 + TEST_CLASS_SETUP(TestClassSetup)
218 + {
219 + WSADATA wsaData;
220 + THROW_IF_WIN32_ERROR(WSAStartup(MAKEWORD(2, 2), &wsaData));
221 +
222 + winrt::init_apartment();
223 +
224 + // Use the same storage path as WSLC runtime tests to reduce pull overhead.
225 + m_storagePath = std::filesystem::current_path() / "test-storage";
226 +
227 + // Build session settings using the WinRT API
228 + auto settings = WSLCSDK::SessionSettings(c_testSessionName, m_storagePath.wstring());
229 + settings.CpuCount(4);
230 + settings.MemoryMB(2048);
231 + settings.Timeout(std::chrono::duration_cast<TimeSpan>(30s));
232 + settings.VhdRequirements(WSLCSDK::VhdOptions(L"", 4096ull * 1024 * 1024, WSLCSDK::VhdType::Dynamic));
233 +
234 + m_defaultSession = WSLCSDK::Session(settings);
235 + m_defaultSession.Start();
236 +
237 + // Pull images required by the tests (no-op if already present).
238 + for (const auto* imageName : {"debian:latest", "python:3.12-alpine", "hello-world:latest", "wslc-registry:latest"})
239 + {
240 + const auto imagePath = GetTestImagePath(imageName);
241 + m_defaultSession.LoadImageAsync(imagePath.wstring()).get();
242 + }
243 +
244 + return true;
245 + }
246 +
247 + TEST_CLASS_CLEANUP(TestClassCleanup)
248 + {
249 + if (m_defaultSession)
250 + {
251 + m_defaultSession.Terminate();
252 + m_defaultSession = nullptr;
253 + }
254 +
255 + // Preserve the VHD in fast-run mode so subsequent runs skip image pulling.
256 + if (!g_fastTestRun && !m_storagePath.empty())
257 + {
258 + std::error_code error;
259 + std::filesystem::remove_all(m_storagePath, error);
260 + if (error)
261 + {
262 + LogError("Failed to cleanup storage path %ws: %hs", m_storagePath.c_str(), error.message().c_str());
263 + }
264 + }
265 +
266 + return true;
267 + }
268 +
269 + // -----------------------------------------------------------------------
270 + // Session tests
271 + // -----------------------------------------------------------------------
272 +
273 + WSLC_TEST_METHOD(CreateSession)
274 + {
275 + const std::filesystem::path extraStorage = m_storagePath / "wslc-winrt-extra-session-storage";
276 +
277 + auto settings = WSLCSDK::SessionSettings(L"wslc-winrt-extra-session", extraStorage.wstring());
278 + settings.CpuCount(2);
279 + settings.MemoryMB(1024);
280 + settings.Timeout(std::chrono::duration_cast<TimeSpan>(30s));
281 + settings.VhdRequirements(WSLCSDK::VhdOptions(L"", 1024ull * 1024 * 1024, WSLCSDK::VhdType::Dynamic));
282 +
283 + // Positive: Creation must succeed with valid settings.
284 + {
285 + auto session = WSLCSDK::Session(settings);
286 + VERIFY_IS_NOT_NULL(session);
287 + }
288 +
289 + // Negative: Must throw if used before Start()
290 + {
291 + auto session = WSLCSDK::Session(settings);
292 + VERIFY_THROWS_HR(std::ignore = session.Images(), E_ILLEGAL_METHOD_CALL);
293 + }
294 +
295 + // Positive: Starting the session must succeed.
296 + {
297 + auto session = WSLCSDK::Session(settings);
298 + VERIFY_NO_THROW(session.Start());
299 + }
300 +
301 + // Negative: Null settings must fail.
302 + {
303 + VERIFY_THROWS_HR(WSLCSDK::Session(WSLCSDK::SessionSettings{nullptr}), E_POINTER);
304 + }
305 + }
306 +
307 + WSLC_TEST_METHOD(TerminationHandler)
308 + {
309 + // Positive: Terminating the session must trigger a graceful shutdown and fire the event
310 + std::promise<WSLCSDK::SessionTerminationReason> promise;
311 +
312 + const std::filesystem::path extraStorage = m_storagePath / "wslc-winrt-termh-storage";
313 +
314 + auto settings = WSLCSDK::SessionSettings(L"wslc-winrt-termh", extraStorage.wstring());
315 + settings.Timeout(std::chrono::duration_cast<TimeSpan>(30s));
316 +
317 + auto session = WSLCSDK::Session(settings);
318 + session.Terminated([&](WSLCSDK::SessionTerminationReason reason) { promise.set_value(reason); });
319 +
320 + session.Start();
321 +
322 + session.Terminate();
323 +
324 + auto future = promise.get_future();
325 + VERIFY_ARE_EQUAL(future.wait_for(30s), std::future_status::ready);
326 + VERIFY_ARE_EQUAL(future.get(), WSLCSDK::SessionTerminationReason::Shutdown);
327 + }
328 +
329 + // -----------------------------------------------------------------------
330 + // Image tests
331 + // -----------------------------------------------------------------------
332 +
333 + WSLC_TEST_METHOD(ImageList)
334 + {
335 + // Session has images pre-loaded - list must return at least one entry.
336 + const auto images = m_defaultSession.Images();
337 + VERIFY_IS_TRUE(images.Size() >= 1);
338 +
339 + // At least one image must be non-empty
340 + bool foundNonEmpty = false;
341 + for (auto const& img : images)
342 + {
343 + if (!img.Name().empty() && img.SizeBytes() != 0)
344 + {
345 + foundNonEmpty = true;
346 + break;
347 + }
348 + }
349 + VERIFY_IS_TRUE(foundNonEmpty);
350 + }
351 +
352 + WSLC_TEST_METHOD(LoadImage)
353 + {
354 + // Positive: load a saved image tar and verify the image can be run
355 + {
356 + // Remove the image if it already exists
357 + IGNORE_ERRORS(m_defaultSession.DeleteImage(L"hello-world:latest"));
358 +
359 + const auto imageTar = GetTestImagePath("hello-world:latest");
360 +
361 + // Positive: load from file path.
362 + VERIFY_NO_THROW(m_defaultSession.LoadImageAsync(imageTar.wstring()).get());
363 +
364 + // Verify the loaded image is usable
365 + VERIFY_IS_TRUE(HasImage(L"hello-world:latest"));
366 + auto output = RunContainerAndWaitForExit(L"hello-world:latest", {});
367 + VERIFY_ARE_EQUAL(output.ExitCode, 0);
368 + VERIFY_IS_TRUE(output.StandardOutput.find(L"Hello from Docker!") != std::string::npos);
369 + }
370 +
371 + // Negative: empty path must fail
372 + {
373 + VERIFY_THROWS_HR(m_defaultSession.LoadImageAsync(L"").get(), E_INVALIDARG);
374 + }
375 +
376 + // Negative: non-existent path must fail.
377 + {
378 + VERIFY_THROWS_HR(m_defaultSession.LoadImageAsync(L"C:\\bogus\\image.tar").get(), HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND));
379 + }
380 + }
381 +
382 + WSLC_TEST_METHOD(ImportImage)
383 + {
384 + const auto exportedImageTar = std::filesystem::path{g_testDataPath} / L"HelloWorldExported.tar";
385 + constexpr auto c_importedImageName = L"my-hello-world-winrt:test";
386 +
387 + IGNORE_ERRORS(m_defaultSession.DeleteImage(c_importedImageName));
388 +
389 + // Positive: import an exported image tar via path.
390 + {
391 + auto cleanup = DELETE_IMAGE_ON_SCOPE_EXIT(c_importedImageName);
392 +
393 + VERIFY_NO_THROW(m_defaultSession.ImportImageAsync(exportedImageTar.wstring(), c_importedImageName).get());
394 +
395 + VERIFY_IS_TRUE(HasImage(c_importedImageName));
396 +
397 + auto output = RunContainerAndWaitForExit(c_importedImageName, {.cmdLine = {L"/hello"}});
398 + VERIFY_ARE_EQUAL(output.ExitCode, 0);
399 + VERIFY_IS_TRUE(output.StandardOutput.find(L"Hello from Docker!") != std::string::npos);
400 + }
401 +
402 + // Negative: empty path must fail.
403 + {
404 + VERIFY_THROWS_HR(m_defaultSession.ImportImageAsync(L"", c_importedImageName).get(), E_INVALIDARG);
405 + }
406 +
407 + // Negative: empty image name must fail.
408 + {
409 + VERIFY_THROWS_HR(m_defaultSession.ImportImageAsync(exportedImageTar.wstring(), L"").get(), E_INVALIDARG);
410 + }
411 +
412 + // Negative: non-tar file must fail.
413 + {
414 + std::filesystem::path pathToSelf = wil::QueryFullProcessImageNameW<std::wstring>(GetCurrentProcess());
415 + VERIFY_THROWS_HR(m_defaultSession.ImportImageAsync(pathToSelf.wstring(), L"import-self:test").get(), E_FAIL);
416 + }
417 + }
418 +
419 + WSLC_TEST_METHOD(ImageDelete)
420 + {
421 + VERIFY_IS_TRUE(HasImage(L"hello-world:latest"));
422 +
423 + // Positive: delete an existing image.
424 + {
425 + m_defaultSession.DeleteImage(L"hello-world:latest");
426 + VERIFY_IS_FALSE(HasImage(L"hello-world:latest"));
427 +
428 + // Reload for subsequent tests.
429 + const auto imageTar = GetTestImagePath("hello-world:latest");
430 + m_defaultSession.LoadImageAsync(imageTar.wstring()).get();
431 + }
432 +
433 + // Negative: non-existent image name must throw.
434 + {
435 + VERIFY_THROWS_HR(m_defaultSession.DeleteImage(L"nonexistent:no-such-tag"), WSLC_E_IMAGE_NOT_FOUND);
436 + }
437 + }
438 +
439 + // -----------------------------------------------------------------------
440 + // Container lifecycle tests
441 + // -----------------------------------------------------------------------
442 +
443 + WSLC_TEST_METHOD(CreateContainer)
444 + {
445 + // Positive: stdout is captured correctly.
446 + {
447 + auto output = RunContainerAndWaitForExit(L"debian:latest", {.cmdLine = {L"/bin/echo", L"OK"}});
448 + VERIFY_ARE_EQUAL(output.ExitCode, 0);
449 + VERIFY_ARE_EQUAL(output.StandardOutput, L"OK\n");
450 + VERIFY_ARE_EQUAL(output.StandardError, L"");
451 + }
452 +
453 + // Positive: stdout and stderr are routed independently.
454 + {
455 + auto output =
456 + RunContainerAndWaitForExit(L"debian:latest", {.cmdLine = {L"/bin/sh", L"-c", L"echo stdout && echo stderr >&2"}});
457 + VERIFY_ARE_EQUAL(output.ExitCode, 0);
458 + VERIFY_ARE_EQUAL(output.StandardOutput, L"stdout\n");
459 + VERIFY_ARE_EQUAL(output.StandardError, L"stderr\n");
460 + }
461 +
462 + // Negative: creating a container with a non-existent image fails at CreateContainer.
463 + {
464 + WSLCSDK::ContainerSettings containerSettings{L"invalid-image:notfound"};
465 + VERIFY_THROWS_HR(m_defaultSession.CreateContainer(containerSettings), WSLC_E_IMAGE_NOT_FOUND);
466 + }
467 +
468 + // Negative: an empty image name is rejected.
469 + {
470 + VERIFY_THROWS_HR(WSLCSDK::ContainerSettings{L""}, E_INVALIDARG);
471 + }
472 +
473 + // Verify that a null settings pointer is rejected.
474 + {
475 + VERIFY_THROWS_HR(m_defaultSession.CreateContainer(nullptr), E_POINTER);
476 + }
477 + }
478 +
479 + WSLC_TEST_METHOD(ContainerGetId)
480 + {
481 + auto container = m_defaultSession.CreateContainer(WSLCSDK::ContainerSettings(L"debian:latest"));
482 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
483 +
484 + const auto id = container.Id();
485 + VERIFY_IS_FALSE(id.empty());
486 + // Container ID is a 64-character lowercase hex string.
487 + VERIFY_ARE_EQUAL(id.size(), 64u);
488 + }
489 +
490 + WSLC_TEST_METHOD(ContainerGetState)
491 + {
492 + auto procSettings = WSLCSDK::ProcessSettings();
493 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
494 +
495 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
496 + containerSettings.InitProcess(procSettings);
497 +
498 + auto container = m_defaultSession.CreateContainer(containerSettings);
499 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
500 +
501 + // State after creation: Created.
502 + VERIFY_ARE_EQUAL(container.State(), WSLCSDK::ContainerState::Created);
503 +
504 + container.Start();
505 +
506 + // State while running: Running.
507 + VERIFY_ARE_EQUAL(container.State(), WSLCSDK::ContainerState::Running);
508 +
509 + container.Stop(WSLCSDK::Signal::SIGKILL, TimeSpan::zero());
510 +
511 + // State after stop: Exited.
512 + VERIFY_ARE_EQUAL(container.State(), WSLCSDK::ContainerState::Exited);
513 + }
514 +
515 + WSLC_TEST_METHOD(ContainerStopAndDelete)
516 + {
517 + auto procSettings = WSLCSDK::ProcessSettings();
518 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"999"}));
519 +
520 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
521 + containerSettings.InitProcess(procSettings);
522 +
523 + auto container = m_defaultSession.CreateContainer(containerSettings);
524 + VERIFY_NO_THROW(container.Start());
525 +
526 + VERIFY_ARE_EQUAL(container.State(), WSLCSDK::ContainerState::Running);
527 +
528 + VERIFY_NO_THROW(container.Stop(WSLCSDK::Signal::SIGKILL, TimeSpan::zero()));
529 + VERIFY_ARE_EQUAL(container.State(), WSLCSDK::ContainerState::Exited);
530 +
531 + VERIFY_NO_THROW(container.Delete(WSLCSDK::DeleteContainerFlags::None));
532 + VERIFY_ARE_EQUAL(container.State(), WSLCSDK::ContainerState::Deleted);
533 + }
534 +
535 + WSLC_TEST_METHOD(ProcessIOHandles)
536 + {
537 + auto procSettings = WSLCSDK::ProcessSettings();
538 + procSettings.CmdLine(
539 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"echo STDOUT_TOKEN; echo STDERR_TOKEN >&2"}));
540 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream);
541 +
542 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
543 + containerSettings.InitProcess(procSettings);
544 +
545 + auto container = m_defaultSession.CreateContainer(containerSettings);
546 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
547 +
548 + auto initProcess = container.InitProcess();
549 +
550 + std::promise<void> promise;
551 + auto autoRevoker = initProcess.Exited(winrt::auto_revoke, [&](int32_t) { promise.set_value(); });
552 +
553 + container.Start();
554 +
555 + auto stdoutStream = initProcess.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardOutput);
556 + auto stderrStream = initProcess.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardError);
557 +
558 + // Verify that each handle can only be acquired once.
559 + {
560 + VERIFY_THROWS_HR(initProcess.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardOutput), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
561 + VERIFY_THROWS_HR(initProcess.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardError), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
562 + }
563 +
564 + VERIFY_ARE_EQUAL(promise.get_future().wait_for(1min), std::future_status::ready);
565 +
566 + VERIFY_ARE_EQUAL(ReadStream(stdoutStream), L"STDOUT_TOKEN\n");
567 + VERIFY_ARE_EQUAL(ReadStream(stderrStream), L"STDERR_TOKEN\n");
568 + }
569 +
570 + WSLC_TEST_METHOD(ContainerNetworkingMode)
571 + {
572 + // BRIDGED: eth0 interface must be present.
573 + {
574 + auto output = RunContainerAndWaitForExit(
575 + L"debian:latest",
576 + {.cmdLine = {L"/bin/sh", L"-c", L"[ -d /sys/class/net/eth0 ] && echo 'HAS_ETH0' || echo 'NO_ETH0'"},
577 + .flags = WSLCSDK::ContainerFlags::None,
578 + .networkingMode = WSLCSDK::ContainerNetworkingMode::Bridged});
579 +
580 + VERIFY_ARE_EQUAL(output.StandardOutput, L"HAS_ETH0\n");
581 + }
582 +
583 + // NONE: eth0 interface must not be present.
584 + {
585 + auto output = RunContainerAndWaitForExit(
586 + L"debian:latest",
587 + {.cmdLine = {L"/bin/sh", L"-c", L"[ -d /sys/class/net/eth0 ] && echo 'HAS_ETH0' || echo 'NO_ETH0'"},
588 + .flags = WSLCSDK::ContainerFlags::None,
589 + .networkingMode = WSLCSDK::ContainerNetworkingMode::None});
590 +
591 + VERIFY_ARE_EQUAL(output.StandardOutput, L"NO_ETH0\n");
592 + }
593 +
594 + // Invalid networking mode must fail.
595 + {
596 + WSLCSDK::ContainerSettings containerSettings{L"debian:latest"};
597 + VERIFY_THROWS_HR(containerSettings.NetworkingMode(static_cast<WSLCSDK::ContainerNetworkingMode>(99)), E_INVALIDARG);
598 + }
599 + }
600 +
601 + WSLC_TEST_METHOD(ContainerPortMapping)
602 + {
603 + // Negative: port mappings with None networking mode must fail at Start.
604 + {
605 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
606 + containerSettings.NetworkingMode(WSLCSDK::ContainerNetworkingMode::None);
607 + containerSettings.PortMappings(winrt::single_threaded_vector<WSLCSDK::ContainerPortMapping>(
608 + {WSLCSDK::ContainerPortMapping(12342, 8000, WSLCSDK::PortProtocol::TCP)}));
609 +
610 + VERIFY_THROWS_HR(m_defaultSession.CreateContainer(containerSettings), E_INVALIDARG);
611 + }
612 +
613 + // Functional: BRIDGED networking with port mapping; HTTP server must be reachable.
614 + {
615 + auto procSettings = WSLCSDK::ProcessSettings();
616 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"python3", L"-m", L"http.server", L"8000"}));
617 + procSettings.EnvironmentVariables(
618 + winrt::single_threaded_map(std::map<winrt::hstring, winrt::hstring>{{L"PYTHONUNBUFFERED", L"1"}}));
619 +
620 + auto containerSettings = WSLCSDK::ContainerSettings(L"python:3.12-alpine");
621 + containerSettings.InitProcess(procSettings);
622 + containerSettings.NetworkingMode(WSLCSDK::ContainerNetworkingMode::Bridged);
623 + containerSettings.PortMappings(winrt::single_threaded_vector<WSLCSDK::ContainerPortMapping>(
624 + {WSLCSDK::ContainerPortMapping(12341, 8000, WSLCSDK::PortProtocol::TCP)}));
625 +
626 + auto container = m_defaultSession.CreateContainer(containerSettings);
627 + container.Start();
628 +
629 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
630 +
631 + ExpectHttpResponse(L"http://127.0.0.1:12341", 200, true);
632 + }
633 +
634 + // Functional: port mapping with explicit IPv4 WindowsAddress.
635 + {
636 + auto procSettings = WSLCSDK::ProcessSettings();
637 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"python3", L"-m", L"http.server", L"8000"}));
638 + procSettings.EnvironmentVariables(
639 + winrt::single_threaded_map(std::map<winrt::hstring, winrt::hstring>{{L"PYTHONUNBUFFERED", L"1"}}));
640 +
641 + auto portMapping = WSLCSDK::ContainerPortMapping(12343, 8000, WSLCSDK::PortProtocol::TCP);
642 + portMapping.WindowsAddress(winrt::Windows::Networking::HostName(L"127.0.0.1"));
643 +
644 + auto containerSettings = WSLCSDK::ContainerSettings(L"python:3.12-alpine");
645 + containerSettings.InitProcess(procSettings);
646 + containerSettings.NetworkingMode(WSLCSDK::ContainerNetworkingMode::Bridged);
647 + containerSettings.PortMappings(winrt::single_threaded_vector<WSLCSDK::ContainerPortMapping>({portMapping}));
648 +
649 + auto container = m_defaultSession.CreateContainer(containerSettings);
650 + container.Start();
651 +
652 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
653 +
654 + ExpectHttpResponse(L"http://127.0.0.1:12343", 200, true);
655 + }
656 +
657 + // Functional: port mapping with explicit IPv6 WindowsAddress.
658 + {
659 + auto procSettings = WSLCSDK::ProcessSettings();
660 + procSettings.CmdLine(
661 + winrt::single_threaded_vector<winrt::hstring>({L"python3", L"-m", L"http.server", L"--bind", L"::", L"8000"}));
662 + procSettings.EnvironmentVariables(
663 + winrt::single_threaded_map(std::map<winrt::hstring, winrt::hstring>{{L"PYTHONUNBUFFERED", L"1"}}));
664 +
665 + auto portMapping = WSLCSDK::ContainerPortMapping(12344, 8000, WSLCSDK::PortProtocol::TCP);
666 + portMapping.WindowsAddress(winrt::Windows::Networking::HostName(L"::1"));
667 +
668 + auto containerSettings = WSLCSDK::ContainerSettings(L"python:3.12-alpine");
669 + containerSettings.InitProcess(procSettings);
670 + containerSettings.NetworkingMode(WSLCSDK::ContainerNetworkingMode::Bridged);
671 + containerSettings.PortMappings(winrt::single_threaded_vector<WSLCSDK::ContainerPortMapping>({portMapping}));
672 +
673 + auto container = m_defaultSession.CreateContainer(containerSettings);
674 + container.Start();
675 +
676 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
677 +
678 + ExpectHttpResponse(L"http://[::1]:12344", 200, true);
679 + }
680 + }
681 +
682 + WSLC_TEST_METHOD(ContainerVolumeUnit)
683 + {
684 + const auto currentDirectory = std::filesystem::current_path().wstring();
685 +
686 + // Negative: non-absolute Windows path must fail at CreateContainer.
687 + VERIFY_THROWS_HR(
688 + {
689 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
690 + containerSettings.Volumes(winrt::single_threaded_vector<WSLCSDK::ContainerVolume>(
691 + {WSLCSDK::ContainerVolume(L"relative", L"/mnt/path", false)}));
692 + m_defaultSession.CreateContainer(containerSettings);
693 + },
694 + E_INVALIDARG);
695 +
696 + // Negative: non-absolute container path must fail at CreateContainer.
697 + VERIFY_THROWS_HR(
698 + {
699 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
700 + containerSettings.Volumes(winrt::single_threaded_vector<WSLCSDK::ContainerVolume>(
701 + {WSLCSDK::ContainerVolume(currentDirectory, L"./mnt/path", false)}));
702 + m_defaultSession.CreateContainer(containerSettings);
703 + },
704 + E_INVALIDARG);
705 +
706 + // Positive: absolute paths must succeed.
707 + {
708 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
709 + containerSettings.Volumes(winrt::single_threaded_vector<WSLCSDK::ContainerVolume>(
710 + {WSLCSDK::ContainerVolume(currentDirectory, L"/mnt/path", false)}));
711 + auto container = m_defaultSession.CreateContainer(containerSettings);
712 + container.Delete(WSLCSDK::DeleteContainerFlags::None);
713 + }
714 + }
715 +
716 + WSLC_TEST_METHOD(ContainerVolumeFunctional)
717 + {
718 + const auto hostRwDir = std::filesystem::current_path() / "wslc-winrt-test-vol-rw";
719 + const auto hostRoDir = std::filesystem::current_path() / "wslc-winrt-test-vol-ro";
720 + std::filesystem::create_directories(hostRwDir);
721 + std::filesystem::create_directories(hostRoDir);
722 +
723 + auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&]() {
724 + std::error_code ec;
725 + std::filesystem::remove_all(hostRwDir, ec);
726 + std::filesystem::remove_all(hostRoDir, ec);
727 + });
728 +
729 + std::ofstream{hostRwDir / "hello.txt"} << "hello-rw";
730 + std::ofstream{hostRoDir / "hello.txt"} << "hello-ro";
731 +
732 + // Container script exits 0 if all checks pass:
733 + // 1. RW mount is readable (hello-rw).
734 + // 2. RO mount is readable (hello-ro).
735 + // 3. Writing to RW mount succeeds.
736 + // 4. Writing to RO mount fails (! touch).
737 + constexpr auto c_script =
738 + "test \"$(cat /mnt/rw/hello.txt)\" = hello-rw && "
739 + "test \"$(cat /mnt/ro/hello.txt)\" = hello-ro && "
740 + "echo container-write > /mnt/rw/written.txt && "
741 + "! touch /mnt/ro/probe 2>/dev/null";
742 +
743 + auto procSettings = WSLCSDK::ProcessSettings();
744 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", winrt::to_hstring(c_script)}));
745 +
746 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
747 + containerSettings.InitProcess(procSettings);
748 + containerSettings.Volumes(winrt::single_threaded_vector<WSLCSDK::ContainerVolume>({
749 + WSLCSDK::ContainerVolume(hostRwDir.wstring(), L"/mnt/rw", false),
750 + WSLCSDK::ContainerVolume(hostRoDir.wstring(), L"/mnt/ro", true),
751 + }));
752 +
753 + auto container = m_defaultSession.CreateContainer(containerSettings);
754 + StartContainerAndWaitForInitProcessExit(container);
755 +
756 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
757 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
758 +
759 + // Verify the file written by the container is visible on the host.
760 + std::ifstream written(hostRwDir / "written.txt");
761 + VERIFY_IS_TRUE(written.is_open());
762 + std::string writtenContent((std::istreambuf_iterator<char>(written)), std::istreambuf_iterator<char>());
763 + VERIFY_ARE_EQUAL(writtenContent, "container-write\n");
764 + }
765 +
766 + WSLC_TEST_METHOD(ContainerInspect)
767 + {
768 + auto container = m_defaultSession.CreateContainer(WSLCSDK::ContainerSettings(L"debian:latest"));
769 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
770 +
771 + const auto inspectJson = container.Inspect();
772 + VERIFY_IS_FALSE(inspectJson.empty());
773 +
774 + const auto id = container.Id();
775 + VERIFY_IS_FALSE(id.empty());
776 +
777 + // The inspect JSON must contain the container ID.
778 + VERIFY_IS_TRUE(winrt::to_string(inspectJson).find(winrt::to_string(id)) != std::string::npos);
779 +
780 + container.Delete(WSLCSDK::DeleteContainerFlags::None);
781 + cleanup.release();
782 + }
783 +
784 + WSLC_TEST_METHOD(ContainerExec)
785 + {
786 + // Start a long-running container so we can exec into it.
787 + auto initProcSettings = WSLCSDK::ProcessSettings();
788 + initProcSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
789 +
790 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
791 + containerSettings.InitProcess(initProcSettings);
792 +
793 + auto container = m_defaultSession.CreateContainer(containerSettings);
794 + container.Start();
795 +
796 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
797 +
798 + // Positive: exec a command that exits 0.
799 + {
800 + auto execSettings = WSLCSDK::ProcessSettings();
801 + execSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/true"}));
802 +
803 + auto execProcess = container.CreateProcess(execSettings);
804 + StartProcessAndWaitForExit(execProcess);
805 + VERIFY_ARE_EQUAL(execProcess.ExitCode(), 0);
806 + }
807 +
808 + // Negative: no command line must fail.
809 + VERIFY_THROWS_HR(container.CreateProcess(WSLCSDK::ProcessSettings()).Start(), E_INVALIDARG);
810 + }
811 +
812 + WSLC_TEST_METHOD(ContainerHostName)
813 + {
814 + // Unit: setting a hostname must succeed.
815 + {
816 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
817 + containerSettings.HostName(L"unit-test-host");
818 + }
819 +
820 + // Functional: container process should see the configured hostname.
821 + {
822 + auto procSettings = WSLCSDK::ProcessSettings();
823 + procSettings.CmdLine(
824 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"test \"$(hostname)\" = my-test-host"}));
825 +
826 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
827 + containerSettings.InitProcess(procSettings);
828 + containerSettings.HostName(L"my-test-host");
829 +
830 + auto container = m_defaultSession.CreateContainer(containerSettings);
831 + StartContainerAndWaitForInitProcessExit(container);
832 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
833 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
834 + }
835 + }
836 +
837 + WSLC_TEST_METHOD(ContainerDomainName)
838 + {
839 + auto procSettings = WSLCSDK::ProcessSettings();
840 + procSettings.CmdLine(
841 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"test \"$(domainname)\" = test.local"}));
842 +
843 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
844 + containerSettings.InitProcess(procSettings);
845 + containerSettings.DomainName(L"test.local");
846 +
847 + auto container = m_defaultSession.CreateContainer(containerSettings);
848 + StartContainerAndWaitForInitProcessExit(container);
849 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
850 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
851 + }
852 +
853 + // -----------------------------------------------------------------------
854 + // Process tests
855 + // -----------------------------------------------------------------------
856 +
857 + WSLC_TEST_METHOD(ProcessEnvVariables)
858 + {
859 + auto procSettings = WSLCSDK::ProcessSettings();
860 + procSettings.CmdLine(
861 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"test \"$MY_TEST_VAR\" = hello-from-test"}));
862 + procSettings.EnvironmentVariables(
863 + winrt::single_threaded_map(std::map<winrt::hstring, winrt::hstring>{{L"MY_TEST_VAR", L"hello-from-test"}}));
864 +
865 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
866 + containerSettings.InitProcess(procSettings);
867 +
868 + auto container = m_defaultSession.CreateContainer(containerSettings);
869 + StartContainerAndWaitForInitProcessExit(container);
870 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
871 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
872 + }
873 +
874 + WSLC_TEST_METHOD(ProcessSignal)
875 + {
876 + // Negative: Signal() before Start() must throw.
877 + {
878 + auto container = m_defaultSession.CreateContainer(WSLCSDK::ContainerSettings(L"debian:latest"));
879 + VERIFY_THROWS_HR(container.InitProcess().Signal(WSLCSDK::Signal::SIGKILL), E_ILLEGAL_METHOD_CALL);
880 + }
881 +
882 + auto procSettings = WSLCSDK::ProcessSettings();
883 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
884 +
885 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
886 + containerSettings.InitProcess(procSettings);
887 +
888 + auto container = m_defaultSession.CreateContainer(containerSettings);
889 + auto process = container.InitProcess();
890 +
891 + std::promise<void> promise;
892 + auto autoRevoker = process.Exited(winrt::auto_revoke, [&](int32_t) { promise.set_value(); });
893 +
894 + container.Start();
895 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
896 +
897 + VERIFY_ARE_EQUAL(process.State(), WSLCSDK::ProcessState::Running);
898 +
899 + process.Signal(WSLCSDK::Signal::SIGKILL);
900 +
901 + VERIFY_ARE_EQUAL(promise.get_future().wait_for(2min), std::future_status::ready);
902 +
903 + const auto state = process.State();
904 + VERIFY_IS_TRUE(state == WSLCSDK::ProcessState::Signalled || state == WSLCSDK::ProcessState::Exited);
905 + }
906 +
907 + WSLC_TEST_METHOD(ProcessGetPid)
908 + {
909 + // Negative: Pid() before Start() must throw.
910 + {
911 + auto container = m_defaultSession.CreateContainer(WSLCSDK::ContainerSettings(L"debian:latest"));
912 + VERIFY_THROWS_HR(std::ignore = container.InitProcess().Pid(), E_ILLEGAL_METHOD_CALL);
913 + }
914 +
915 + auto procSettings = WSLCSDK::ProcessSettings();
916 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
917 +
918 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
919 + containerSettings.InitProcess(procSettings);
920 +
921 + auto container = m_defaultSession.CreateContainer(containerSettings);
922 + container.Start();
923 +
924 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
925 +
926 + auto process = container.InitProcess();
927 + VERIFY_IS_TRUE(process.Pid() > 0);
928 + }
929 +
930 + WSLC_TEST_METHOD(ProcessGetExitCode)
931 + {
932 + auto runAndGetExitCode = [&](int code) -> int32_t {
933 + auto procSettings = WSLCSDK::ProcessSettings();
934 + procSettings.CmdLine(
935 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", winrt::to_hstring(std::format("exit {}", code))}));
936 +
937 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
938 + containerSettings.InitProcess(procSettings);
939 +
940 + auto container = m_defaultSession.CreateContainer(containerSettings);
941 + StartContainerAndWaitForInitProcessExit(container);
942 + auto exitCode = container.InitProcess().ExitCode();
943 +
944 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
945 + return exitCode;
946 + };
947 +
948 + VERIFY_ARE_EQUAL(runAndGetExitCode(0), 0);
949 + VERIFY_ARE_EQUAL(runAndGetExitCode(42), 42);
950 +
951 + // Negative: querying ExitCode while process is still running must throw.
952 + {
953 + auto procSettings = WSLCSDK::ProcessSettings();
954 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
955 +
956 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
957 + containerSettings.InitProcess(procSettings);
958 +
959 + auto container = m_defaultSession.CreateContainer(containerSettings);
960 + container.Start();
961 +
962 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
963 +
964 + auto process = container.InitProcess();
965 + VERIFY_ARE_EQUAL(process.State(), WSLCSDK::ProcessState::Running);
966 +
967 + VERIFY_THROWS_HR(std::ignore = process.ExitCode(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
968 + }
969 + }
970 +
971 + WSLC_TEST_METHOD(ProcessGetState)
972 + {
973 + auto procSettings = WSLCSDK::ProcessSettings();
974 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
975 +
976 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
977 + containerSettings.InitProcess(procSettings);
978 +
979 + auto container = m_defaultSession.CreateContainer(containerSettings);
980 + container.Start();
981 +
982 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
983 +
984 + auto process = container.InitProcess();
985 +
986 + // State while running.
987 + VERIFY_ARE_EQUAL(process.State(), WSLCSDK::ProcessState::Running);
988 +
989 + // Querying ExitCode while running must throw ERROR_INVALID_STATE.
990 + VERIFY_THROWS_HR(std::ignore = process.ExitCode(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE));
991 +
992 + // Register the Exited event.
993 + std::promise<int32_t> exitPromise;
994 + auto token = process.Exited([&](int32_t code) { exitPromise.set_value(code); });
995 + auto cleanupToken = wil::scope_exit([&]() { process.Exited(token); });
996 +
997 + process.Signal(WSLCSDK::Signal::SIGKILL);
998 +
999 + // The Exited event must fire after the signal.
1000 + auto future = exitPromise.get_future();
1001 + VERIFY_ARE_EQUAL(future.wait_for(30s), std::future_status::ready);
1002 +
1003 + const auto state = process.State();
1004 + VERIFY_IS_TRUE(state == WSLCSDK::ProcessState::Signalled || state == WSLCSDK::ProcessState::Exited);
1005 + }
1006 +
1007 + WSLC_TEST_METHOD(ProcessWorkingDirectory)
1008 + {
1009 + // Functional: container should see the configured working directory.
1010 + auto procSettings = WSLCSDK::ProcessSettings();
1011 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"test \"$(pwd)\" = /tmp"}));
1012 + procSettings.WorkingDirectory(L"/tmp");
1013 +
1014 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1015 + containerSettings.InitProcess(procSettings);
1016 +
1017 + auto container = m_defaultSession.CreateContainer(containerSettings);
1018 + StartContainerAndWaitForInitProcessExit(container);
1019 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
1020 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
1021 + }
1022 +
1023 + // -----------------------------------------------------------------------
1024 + // Service tests
1025 + // -----------------------------------------------------------------------
1026 +
1027 + WSLC_TEST_METHOD(GetVersion)
1028 + {
1029 + auto version = WSLCSDK::WslcService::GetVersion();
1030 + VERIFY_IS_TRUE(version.Major() > 0 || version.Minor() > 0 || version.Revision() > 0);
1031 + }
1032 +
1033 + WSLC_TEST_METHOD(GetMissingComponents)
1034 + {
1035 + const auto missing = WSLCSDK::WslcService::GetMissingComponents();
1036 + VERIFY_ARE_EQUAL(missing, WSLCSDK::ComponentFlags::None);
1037 + }
1038 +
1039 + WSLC_TEST_METHOD(InstallWithDependencies)
1040 + {
1041 + WSLCSDK::WslcService::InstallWithDependenciesAsync().get();
1042 + VERIFY_ARE_EQUAL(WSLCSDK::WslcService::GetMissingComponents(), WSLCSDK::ComponentFlags::None);
1043 + }
1044 +
1045 + // -----------------------------------------------------------------------
1046 + // Process IO event tests
1047 + // -----------------------------------------------------------------------
1048 +
1049 + WSLC_TEST_METHOD(ProcessIoEventsUnit)
1050 + {
1051 + // Negative: registering OutputReceived/ErrorReceived without OutputMode::Event must throw.
1052 + {
1053 + auto procSettings = WSLCSDK::ProcessSettings();
1054 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"1"}));
1055 +
1056 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1057 + containerSettings.InitProcess(procSettings);
1058 +
1059 + auto container = m_defaultSession.CreateContainer(containerSettings);
1060 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1061 +
1062 + auto process = container.InitProcess();
1063 + VERIFY_THROWS_HR(process.OutputReceived([](winrt::array_view<uint8_t const>) {}), E_ILLEGAL_METHOD_CALL);
1064 + VERIFY_THROWS_HR(process.ErrorReceived([](winrt::array_view<uint8_t const>) {}), E_ILLEGAL_METHOD_CALL);
1065 +
1066 + // GetOutputStream requires OutputMode::Stream — must throw with Discard mode (even after Start).
1067 + container.Start();
1068 + VERIFY_THROWS_HR(process.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardOutput), E_ILLEGAL_METHOD_CALL);
1069 + }
1070 +
1071 + // Positive: with OutputMode::Event, registering and revoking event handlers must succeed.
1072 + {
1073 + auto procSettings = WSLCSDK::ProcessSettings();
1074 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"1"}));
1075 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1076 +
1077 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1078 + containerSettings.InitProcess(procSettings);
1079 +
1080 + auto container = m_defaultSession.CreateContainer(containerSettings);
1081 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1082 +
1083 + auto process = container.InitProcess();
1084 +
1085 + auto stdoutToken = process.OutputReceived([](winrt::array_view<uint8_t const>) {});
1086 + auto stderrToken = process.ErrorReceived([](winrt::array_view<uint8_t const>) {});
1087 + auto exitToken = process.Exited([](int32_t) {});
1088 +
1089 + process.OutputReceived(stdoutToken);
1090 + process.ErrorReceived(stderrToken);
1091 + process.Exited(exitToken);
1092 +
1093 + // GetOutputStream throws when OutputMode is Event.
1094 + container.Start();
1095 + VERIFY_THROWS_HR(process.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardOutput), E_ILLEGAL_METHOD_CALL);
1096 + }
1097 +
1098 + // Negative: OutputReceived/ErrorReceived with OutputMode::Stream must throw.
1099 + {
1100 + auto procSettings = WSLCSDK::ProcessSettings();
1101 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"1"}));
1102 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream);
1103 +
1104 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1105 + containerSettings.InitProcess(procSettings);
1106 +
1107 + auto container = m_defaultSession.CreateContainer(containerSettings);
1108 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1109 +
1110 + auto process = container.InitProcess();
1111 + VERIFY_THROWS_HR(process.OutputReceived([](winrt::array_view<uint8_t const>) {}), E_ILLEGAL_METHOD_CALL);
1112 + VERIFY_THROWS_HR(process.ErrorReceived([](winrt::array_view<uint8_t const>) {}), E_ILLEGAL_METHOD_CALL);
1113 + }
1114 + }
1115 +
1116 + WSLC_TEST_METHOD(ProcessIoEventsInitProcess)
1117 + {
1118 + std::string stdoutData, stderrData;
1119 +
1120 + auto procSettings = WSLCSDK::ProcessSettings();
1121 + procSettings.CmdLine(
1122 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"echo STDOUT && echo STDERR >&2"}));
1123 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1124 +
1125 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1126 + containerSettings.InitProcess(procSettings);
1127 +
1128 + auto container = m_defaultSession.CreateContainer(containerSettings);
1129 + auto process = container.InitProcess();
1130 +
1131 + process.OutputReceived([&](winrt::array_view<uint8_t const> data) {
1132 + stdoutData.append(reinterpret_cast<const char*>(data.data()), data.size());
1133 + });
1134 + process.ErrorReceived([&](winrt::array_view<uint8_t const> data) {
1135 + stderrData.append(reinterpret_cast<const char*>(data.data()), data.size());
1136 + });
1137 +
1138 + // Start: claims IO handles and starts the IOCallback pump thread.
1139 + StartContainerAndWaitForInitProcessExit(container);
1140 +
1141 + VERIFY_ARE_EQUAL(stdoutData, "STDOUT\n");
1142 + VERIFY_ARE_EQUAL(stderrData, "STDERR\n");
1143 + }
1144 +
1145 + WSLC_TEST_METHOD(ProcessIoEventsExecProcess)
1146 + {
1147 + // Long-running init process to keep the container alive.
1148 + auto initProcSettings = WSLCSDK::ProcessSettings();
1149 + initProcSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
1150 +
1151 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1152 + containerSettings.InitProcess(initProcSettings);
1153 +
1154 + auto container = m_defaultSession.CreateContainer(containerSettings);
1155 + container.Start();
1156 +
1157 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1158 +
1159 + std::string stdoutData, stderrData;
1160 +
1161 + auto execProcSettings = WSLCSDK::ProcessSettings();
1162 + execProcSettings.CmdLine(
1163 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"echo EXEC_OUT && echo EXEC_ERR >&2"}));
1164 + execProcSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1165 +
1166 + auto execProcess = container.CreateProcess(execProcSettings);
1167 +
1168 + execProcess.OutputReceived([&](winrt::array_view<uint8_t const> data) {
1169 + stdoutData.append(reinterpret_cast<const char*>(data.data()), data.size());
1170 + });
1171 + execProcess.ErrorReceived([&](winrt::array_view<uint8_t const> data) {
1172 + stderrData.append(reinterpret_cast<const char*>(data.data()), data.size());
1173 + });
1174 +
1175 + StartProcessAndWaitForExit(execProcess);
1176 +
1177 + VERIFY_ARE_EQUAL(stdoutData, "EXEC_OUT\n");
1178 + VERIFY_ARE_EQUAL(stderrData, "EXEC_ERR\n");
1179 + }
1180 +
1181 + WSLC_TEST_METHOD(ProcessIoEventsHandleExclusion)
1182 + {
1183 + // Register an OutputReceived handler only. The IOCallback acquires ALL pipe handles
1184 + // (draining uncallbacked streams to prevent deadlock), so both stdout and stderr
1185 + // handles are consumed and neither can be obtained via GetOutputStream.
1186 + auto procSettings = WSLCSDK::ProcessSettings();
1187 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"99"}));
1188 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1189 +
1190 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1191 + containerSettings.InitProcess(procSettings);
1192 +
1193 + auto container = m_defaultSession.CreateContainer(containerSettings);
1194 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1195 +
1196 + auto process = container.InitProcess();
1197 + process.OutputReceived([](winrt::array_view<uint8_t const>) {});
1198 +
1199 + container.Start();
1200 +
1201 + // stdout handle was consumed by the OutputReceived handler — must not be obtainable.
1202 + VERIFY_THROWS_HR(process.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardOutput), E_ILLEGAL_METHOD_CALL);
1203 +
1204 + // stderr handle was also consumed in order to drain it despite not having a handler.
1205 + VERIFY_THROWS_HR(process.GetOutputStream(WSLCSDK::ProcessOutputHandle::StandardError), E_ILLEGAL_METHOD_CALL);
1206 + }
1207 +
1208 + WSLC_TEST_METHOD(ProcessIoEventsExitCallback)
1209 + {
1210 + // Verify the Exited event fires with the correct exit code after IO has been flushed.
1211 + auto RunAndCaptureExit = [&](int exitCodeArg) -> std::pair<int32_t, std::string> {
1212 + std::string stdoutData;
1213 + std::promise<int32_t> exitPromise;
1214 +
1215 + auto procSettings = WSLCSDK::ProcessSettings();
1216 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>(
1217 + {L"/bin/sh", L"-c", winrt::hstring(std::format(L"echo HELLO && exit {}", exitCodeArg))}));
1218 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1219 +
1220 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1221 + containerSettings.InitProcess(procSettings);
1222 +
1223 + auto container = m_defaultSession.CreateContainer(containerSettings);
1224 + auto process = container.InitProcess();
1225 +
1226 + process.OutputReceived([&](winrt::array_view<uint8_t const> data) {
1227 + stdoutData.append(reinterpret_cast<const char*>(data.data()), data.size());
1228 + });
1229 + process.Exited([&](int32_t code) { exitPromise.set_value(code); });
1230 +
1231 + container.Start();
1232 +
1233 + auto future = exitPromise.get_future();
1234 + VERIFY_ARE_EQUAL(future.wait_for(60s), std::future_status::ready);
1235 +
1236 + return {future.get(), stdoutData};
1237 + };
1238 +
1239 + // Exit 0: Exited event must fire with code 0; IO must have been delivered first.
1240 + {
1241 + auto [exitCode, output] = RunAndCaptureExit(0);
1242 + VERIFY_ARE_EQUAL(exitCode, 0);
1243 + VERIFY_ARE_EQUAL(output, "HELLO\n");
1244 + }
1245 +
1246 + // Non-zero exit: Exited event must report the correct code.
1247 + {
1248 + auto [exitCode, output] = RunAndCaptureExit(42);
1249 + VERIFY_ARE_EQUAL(exitCode, 42);
1250 + VERIFY_ARE_EQUAL(output, "HELLO\n");
1251 + }
1252 + }
1253 +
1254 + WSLC_TEST_METHOD(ProcessIoEventsCancelOnRelease)
1255 + {
1256 + // Verify that releasing the process handle while an exec'd process is still running
1257 + // and writing IO cancels the event pump:
1258 + // - No IO events arrive after the handle is released.
1259 + // - Exited is never invoked (cancellation suppresses it).
1260 +
1261 + // Long-running init process to keep the container alive.
1262 + auto initProcSettings = WSLCSDK::ProcessSettings();
1263 + initProcSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"999"}));
1264 +
1265 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1266 + containerSettings.InitProcess(initProcSettings);
1267 +
1268 + auto container = m_defaultSession.CreateContainer(containerSettings);
1269 + container.Start();
1270 +
1271 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1272 +
1273 + std::atomic<int> callbackCount{0};
1274 + std::atomic<bool> exitFired{false};
1275 +
1276 + auto execProcSettings = WSLCSDK::ProcessSettings();
1277 + execProcSettings.CmdLine(
1278 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"while true; do echo LINE; sleep 0.05; done"}));
1279 + execProcSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1280 +
1281 + auto execProcess = container.CreateProcess(execProcSettings);
1282 +
1283 + execProcess.OutputReceived([&](winrt::array_view<uint8_t const>) { callbackCount.fetch_add(1); });
1284 + execProcess.Exited([&](int32_t) { exitFired.store(true); });
1285 +
1286 + execProcess.Start();
1287 +
1288 + // Wait for events to start arriving.
1289 + Sleep(500);
1290 + VERIFY_IS_TRUE(callbackCount.load() > 0);
1291 +
1292 + // Release the exec process handle while it is still running and writing.
1293 + execProcess = nullptr;
1294 +
1295 + const int countAtRelease = callbackCount.load();
1296 +
1297 + // Exited must not have fired: cancellation suppresses it.
1298 + VERIFY_IS_FALSE(exitFired.load());
1299 +
1300 + // No further events after release.
1301 + Sleep(200);
1302 + VERIFY_ARE_EQUAL(callbackCount.load(), countAtRelease);
1303 + VERIFY_IS_FALSE(exitFired.load());
1304 + }
1305 +
1306 + WSLC_TEST_METHOD(ProcessIoEventsLargeOutput)
1307 + {
1308 + // Generate ~1 MiB of stdout via: dd if=/dev/zero bs=1024 count=1024 | base64
1309 + // 1,048,576 zero bytes → base64 output is 1,398,104 bytes.
1310 + static constexpr size_t c_expectedBytes = 1'398'104;
1311 +
1312 + std::string stdoutData;
1313 + stdoutData.reserve(c_expectedBytes + 4096);
1314 +
1315 + auto procSettings = WSLCSDK::ProcessSettings();
1316 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>(
1317 + {L"/bin/sh", L"-c", L"dd if=/dev/zero bs=1024 count=1024 2>/dev/null | base64 -w 0"}));
1318 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Event);
1319 +
1320 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1321 + containerSettings.InitProcess(procSettings);
1322 +
1323 + auto container = m_defaultSession.CreateContainer(containerSettings);
1324 + auto process = container.InitProcess();
1325 +
1326 + process.OutputReceived([&](winrt::array_view<uint8_t const> data) {
1327 + stdoutData.append(reinterpret_cast<const char*>(data.data()), data.size());
1328 + });
1329 +
1330 + StartContainerAndWaitForInitProcessExit(container);
1331 +
1332 + VERIFY_ARE_EQUAL(stdoutData.size(), c_expectedBytes);
1333 + }
1334 +
1335 + // -----------------------------------------------------------------------
1336 + // Storage tests
1337 + // -----------------------------------------------------------------------
1338 +
1339 + WSLC_TEST_METHOD(SessionCreateVhd)
1340 + {
1341 + constexpr auto c_volumeName = L"wslc-winrt-test-data-vol";
1342 + constexpr uint64_t c_vhdSizeBytes = 1ull * 1024 * 1024 * 1024; // 1 GiB
1343 +
1344 + const std::filesystem::path vhdSessionStorage = m_storagePath / "wslc-winrt-vhd-test-storage";
1345 + IGNORE_ERRORS(std::filesystem::remove_all(vhdSessionStorage));
1346 + auto cleanup = SCOPE_CLEANUP(std::filesystem::remove_all(vhdSessionStorage));
1347 +
1348 + // Create a dedicated session so that volume creation does not affect the shared default session.
1349 + auto settings = WSLCSDK::SessionSettings(L"wslc-winrt-vhd-test", vhdSessionStorage.wstring());
1350 + settings.Timeout(std::chrono::duration_cast<TimeSpan>(30s));
1351 + settings.VhdRequirements(WSLCSDK::VhdOptions(L"", 4096ull * 1024 * 1024, WSLCSDK::VhdType::Dynamic));
1352 +
1353 + auto session = WSLCSDK::Session(settings);
1354 + session.Start();
1355 +
1356 + // Load debian.
1357 + const auto debianTar = GetTestImagePath("debian:latest");
1358 + session.LoadImageAsync(debianTar.wstring()).get();
1359 +
1360 + // Positive: create a named VHD volume.
1361 + session.CreateVhdVolume(WSLCSDK::VhdOptions(c_volumeName, c_vhdSizeBytes, WSLCSDK::VhdType::Dynamic));
1362 +
1363 + // The backing VHD file must exist on disk.
1364 + const auto expectedVhdPath = vhdSessionStorage / "volumes" / (std::wstring(c_volumeName) + L".vhdx");
1365 + VERIFY_IS_TRUE(std::filesystem::exists(expectedVhdPath));
1366 +
1367 + // Positive: write a marker via a container that mounts the named volume.
1368 + {
1369 + auto procSettings = WSLCSDK::ProcessSettings();
1370 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>(
1371 + {L"/bin/sh", L"-c", L"echo wslc-winrt-vhd-test > /data/marker.txt"}));
1372 +
1373 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1374 + containerSettings.InitProcess(procSettings);
1375 + containerSettings.NamedVolumes(winrt::single_threaded_vector<WSLCSDK::ContainerNamedVolume>(
1376 + {WSLCSDK::ContainerNamedVolume(c_volumeName, L"/data", false)}));
1377 +
1378 + auto container = session.CreateContainer(containerSettings);
1379 + StartContainerAndWaitForInitProcessExit(container);
1380 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
1381 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
1382 + }
1383 +
1384 + // Positive: read back the marker in a second container (read-only mount).
1385 + {
1386 + auto procSettings = WSLCSDK::ProcessSettings();
1387 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>(
1388 + {L"/bin/sh", L"-c", L"test \"$(cat /data/marker.txt)\" = wslc-winrt-vhd-test"}));
1389 +
1390 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1391 + containerSettings.InitProcess(procSettings);
1392 + containerSettings.NamedVolumes(winrt::single_threaded_vector<WSLCSDK::ContainerNamedVolume>(
1393 + {WSLCSDK::ContainerNamedVolume(c_volumeName, L"/data", true)}));
1394 +
1395 + auto container = session.CreateContainer(containerSettings);
1396 + StartContainerAndWaitForInitProcessExit(container);
1397 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
1398 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
1399 + }
1400 +
1401 + // Positive: delete the volume.
1402 + session.DeleteVhdVolume(c_volumeName);
1403 + VERIFY_IS_FALSE(std::filesystem::exists(expectedVhdPath));
1404 +
1405 + // Negative: zero size must fail.
1406 + VERIFY_THROWS_HR(session.CreateVhdVolume(WSLCSDK::VhdOptions(c_volumeName, 0, WSLCSDK::VhdType::Dynamic)), E_INVALIDARG);
1407 +
1408 + // Positive: fixed-allocation VHD; on-disk file size must be >= SizeBytes.
1409 + {
1410 + constexpr auto c_fixedVolumeName = L"wslc-sdk-vhd-fixed";
1411 + constexpr auto c_fixedSizeBytes = 64ull * _1MB;
1412 + VERIFY_NO_THROW(session.CreateVhdVolume(WSLCSDK::VhdOptions(c_fixedVolumeName, c_fixedSizeBytes, WSLCSDK::VhdType::Fixed)));
1413 +
1414 + auto deleteVolume = SCOPE_CLEANUP(session.DeleteVhdVolume(c_fixedVolumeName));
1415 +
1416 + std::filesystem::path expectedVhdPath = vhdSessionStorage / L"volumes" / (std::wstring(c_fixedVolumeName) + L".vhdx");
1417 + VERIFY_IS_TRUE(std::filesystem::exists(expectedVhdPath));
1418 + VERIFY_IS_GREATER_THAN_OR_EQUAL(std::filesystem::file_size(expectedVhdPath), c_fixedSizeBytes);
1419 + }
1420 +
1421 + // Positive: SetOwner() bakes uid/gid into the volume root inode at mkfs time.
1422 + // Verify by stat-ing the mount inside a container.
1423 + {
1424 + constexpr auto c_ownedVolumeName = L"wslc-sdk-vhd-owned";
1425 + auto vhdOptions = WSLCSDK::VhdOptions(c_ownedVolumeName, c_vhdSizeBytes, WSLCSDK::VhdType::Dynamic);
1426 + vhdOptions.SetOwner(65534, 65534); // nobody:nogroup
1427 + session.CreateVhdVolume(vhdOptions);
1428 +
1429 + auto deleteVolume = SCOPE_CLEANUP(session.DeleteVhdVolume(c_ownedVolumeName));
1430 +
1431 + auto procSettings = WSLCSDK::ProcessSettings();
1432 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/usr/bin/stat", L"-c", L"%u %g", L"/data"}));
1433 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream);
1434 +
1435 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1436 + containerSettings.InitProcess(procSettings);
1437 + containerSettings.NamedVolumes(winrt::single_threaded_vector<WSLCSDK::ContainerNamedVolume>(
1438 + {WSLCSDK::ContainerNamedVolume(c_ownedVolumeName, L"/data", false)}));
1439 +
1440 + auto container = session.CreateContainer(containerSettings);
1441 + StartContainerAndWaitForInitProcessExit(container);
1442 + auto output = GetProcessOutput(container.InitProcess());
1443 + VERIFY_ARE_EQUAL(container.InitProcess().ExitCode(), 0);
1444 + VERIFY_ARE_EQUAL(output.StandardOutput, L"65534 65534\n");
1445 + container.Delete(WSLCSDK::DeleteContainerFlags::Force);
1446 + }
1447 + }
1448 +
1449 + // -----------------------------------------------------------------------
1450 + // Authentication / registry tests
1451 + // -----------------------------------------------------------------------
1452 +
1453 + WSLC_TEST_METHOD(AuthenticateTests)
1454 + {
1455 + constexpr auto c_username = "wslctest";
1456 + constexpr auto c_password = "password";
1457 +
1458 + auto [registryContainer, registryAddress] = StartLocalRegistry(c_username, c_password);
1459 +
1460 + const auto serverUri = Uri(winrt::to_hstring(std::format("http://{}", registryAddress)));
1461 +
1462 + // Negative: wrong password must fail.
1463 + VERIFY_THROWS_HR(m_defaultSession.Authenticate(serverUri, winrt::to_hstring(c_username), L"wrong-password"), E_FAIL);
1464 +
1465 + // Positive: correct credentials
1466 + VERIFY_NO_THROW(m_defaultSession.Authenticate(serverUri, winrt::to_hstring(c_username), winrt::to_hstring(c_password)));
1467 +
1468 + const auto xRegistryAuth = wsl::windows::common::wslutil::BuildRegistryAuthHeader(c_username, c_password);
1469 + PushImageToRegistry("hello-world", "latest", registryAddress, xRegistryAuth);
1470 +
1471 + const auto image = winrt::to_hstring(std::format("{}/hello-world:latest", registryAddress));
1472 +
1473 + // Positive: pulling with correct credentials must succeed.
1474 + {
1475 + auto opts = WSLCSDK::PullImageOptions(image);
1476 + opts.RegistryAuth(winrt::to_hstring(xRegistryAuth));
1477 + m_defaultSession.PullImageAsync(opts).get();
1478 + VERIFY_IS_TRUE(HasImage(image));
1479 + }
1480 +
1481 + // Negative: pulling without credentials must fail.
1482 + {
1483 + VERIFY_THROWS_HR(m_defaultSession.PullImageAsync(WSLCSDK::PullImageOptions(image)).get(), E_FAIL);
1484 + }
1485 +
1486 + // Negative: pulling with bad credentials must fail.
1487 + {
1488 + auto badAuth = wsl::windows::common::wslutil::BuildRegistryAuthHeader(c_username, "wrong");
1489 + auto opts = WSLCSDK::PullImageOptions(image);
1490 + opts.RegistryAuth(winrt::to_hstring(badAuth));
1491 + VERIFY_THROWS_HR(m_defaultSession.PullImageAsync(opts).get(), E_FAIL);
1492 + }
1493 + }
1494 +
1495 + WSLC_TEST_METHOD(PullImage)
1496 + {
1497 + auto [registryContainer, registryAddress] = StartLocalRegistry();
1498 + const auto xRegistryAuth = wsl::windows::common::wslutil::BuildRegistryAuthHeader("", "");
1499 +
1500 + {
1501 + PushImageToRegistry("hello-world", "latest", registryAddress, xRegistryAuth);
1502 +
1503 + const auto image = winrt::to_hstring(std::format("{}/hello-world:latest", registryAddress));
1504 +
1505 + // Delete the image locally so the pull is a real network pull.
1506 + IGNORE_ERRORS(m_defaultSession.DeleteImage(image));
1507 +
1508 + // Positive: pull from the local registry.
1509 + m_defaultSession.PullImageAsync(WSLCSDK::PullImageOptions(image)).get();
1510 + VERIFY_IS_TRUE(HasImage(image));
1511 +
1512 + // Verify the pulled image is runnable.
1513 + auto output = RunContainerAndWaitForExit(image, {});
1514 + VERIFY_ARE_EQUAL(output.ExitCode, 0);
1515 + }
1516 +
1517 + // Negative: image that does not exist in the registry.
1518 + {
1519 + const auto missing = winrt::to_hstring(std::format("{}/does-not-exist", registryAddress));
1520 + auto opts = WSLCSDK::PullImageOptions(missing);
1521 + opts.RegistryAuth(winrt::to_hstring(xRegistryAuth));
1522 + VERIFY_THROWS_HR(m_defaultSession.PullImageAsync(opts).get(), static_cast<HRESULT>(WSLC_E_IMAGE_NOT_FOUND));
1523 + }
1524 +
1525 + // Negative: empty URI must fail.
1526 + VERIFY_THROWS_HR(m_defaultSession.PullImageAsync(WSLCSDK::PullImageOptions(L"")).get(), E_INVALIDARG);
1527 + }
1528 +
1529 + WSLC_TEST_METHOD(PushImage)
1530 + {
1531 + auto [registryContainer, registryAddress] = StartLocalRegistry();
1532 + const auto xRegistryAuth = wsl::windows::common::wslutil::BuildRegistryAuthHeader("", "");
1533 +
1534 + // Positive: push an existing image to the local registry.
1535 + PushImageToRegistry("hello-world", "latest", registryAddress, xRegistryAuth);
1536 +
1537 + // Negative: pushing a non-existent image must fail.
1538 + VERIFY_THROWS_HR(
1539 + m_defaultSession.PushImageAsync(WSLCSDK::PushImageOptions(L"does-not-exist", winrt::to_hstring(xRegistryAuth))).get(), E_FAIL);
1540 +
1541 + // Negative: empty image name must fail.
1542 + VERIFY_THROWS_HR(m_defaultSession.PushImageAsync(WSLCSDK::PushImageOptions(L"", winrt::to_hstring(xRegistryAuth))).get(), E_INVALIDARG);
1543 + }
1544 +
1545 + WSLC_TEST_METHOD(TagImage)
1546 + {
1547 + // Positive: tag an existing image.
1548 + m_defaultSession.TagImage(WSLCSDK::TagImageOptions(L"debian:latest", L"debian", L"winrt-sdk-test-tag"));
1549 + VERIFY_IS_TRUE(HasImage(L"debian:winrt-sdk-test-tag"));
1550 +
1551 + auto cleanup = DELETE_IMAGE_ON_SCOPE_EXIT(L"debian:winrt-sdk-test-tag");
1552 +
1553 + // Negative: empty image name must fail.
1554 + VERIFY_THROWS_HR(m_defaultSession.TagImage(WSLCSDK::TagImageOptions(L"", L"debian", L"test")), E_INVALIDARG);
1555 +
1556 + // Negative: empty repository must fail.
1557 + VERIFY_THROWS_HR(m_defaultSession.TagImage(WSLCSDK::TagImageOptions(L"debian:latest", L"", L"test")), E_INVALIDARG);
1558 +
1559 + // Negative: empty tag must fail.
1560 + VERIFY_THROWS_HR(m_defaultSession.TagImage(WSLCSDK::TagImageOptions(L"debian:latest", L"debian", L"")), E_INVALIDARG);
1561 + }
1562 +
1563 + // -----------------------------------------------------------------------
1564 + // Negative / edge-case tests
1565 + // -----------------------------------------------------------------------
1566 +
1567 + WSLC_TEST_METHOD(ExecOnStoppedContainer)
1568 + {
1569 + auto procSettings = WSLCSDK::ProcessSettings();
1570 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"10"}));
1571 +
1572 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1573 + containerSettings.InitProcess(procSettings);
1574 +
1575 + auto container = m_defaultSession.CreateContainer(containerSettings);
1576 +
1577 + // Wait for the short-lived init process to exit
1578 + StartContainerAndWaitForInitProcessExit(container);
1579 +
1580 + // The init process has now exited. Attempting to exec on a stopped container must fail.
1581 + auto execSettings = WSLCSDK::ProcessSettings();
1582 + execSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/echo", L"should-fail"}));
1583 +
1584 + VERIFY_THROWS_HR(container.CreateProcess(execSettings).Start(), static_cast<HRESULT>(WSLC_E_CONTAINER_NOT_RUNNING));
1585 + }
1586 +
1587 + WSLC_TEST_METHOD(DuplicateContainerName)
1588 + {
1589 + auto procSettings = WSLCSDK::ProcessSettings();
1590 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"10"}));
1591 +
1592 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1593 + containerSettings.InitProcess(procSettings);
1594 + containerSettings.Name(L"duplicate-name-test-winrt");
1595 +
1596 + auto container1 = m_defaultSession.CreateContainer(containerSettings);
1597 + container1.Start();
1598 +
1599 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container1);
1600 +
1601 + // Creating a second container with the same name must fail.
1602 + VERIFY_THROWS_HR(m_defaultSession.CreateContainer(containerSettings), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS));
1603 + }
1604 +
1605 + WSLC_TEST_METHOD(DeleteRunningContainerWithoutForce)
1606 + {
1607 + auto procSettings = WSLCSDK::ProcessSettings();
1608 + procSettings.CmdLine(winrt::single_threaded_vector<winrt::hstring>({L"/bin/sleep", L"10"}));
1609 +
1610 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1611 + containerSettings.InitProcess(procSettings);
1612 +
1613 + auto container = m_defaultSession.CreateContainer(containerSettings);
1614 + container.Start();
1615 +
1616 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1617 +
1618 + // Deleting a running container without Force must fail.
1619 + VERIFY_THROWS_HR(container.Delete(WSLCSDK::DeleteContainerFlags::None), static_cast<HRESULT>(WSLC_E_CONTAINER_IS_RUNNING));
1620 + }
1621 +
1622 + WSLC_TEST_METHOD(DeleteNonExistentImage)
1623 + {
1624 + VERIFY_THROWS_HR(m_defaultSession.DeleteImage(L"nonexistent-image:this-tag-does-not-exist"), static_cast<HRESULT>(WSLC_E_IMAGE_NOT_FOUND));
1625 + }
1626 +
1627 + WSLC_TEST_METHOD(PullInvalidImageUri)
1628 + {
1629 + VERIFY_THROWS_HR(m_defaultSession.PullImageAsync(WSLCSDK::PullImageOptions(L"///invalid-registry-url///")).get(), E_INVALIDARG);
1630 + }
1631 +
1632 + WSLC_TEST_METHOD(ContainerGpu)
1633 + {
1634 + // Negative: creating a GPU container on a session without GPU support must fail.
1635 + {
1636 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1637 + containerSettings.Flags(WSLCSDK::ContainerFlags::EnableGpu);
1638 +
1639 + VERIFY_THROWS_HR(m_defaultSession.CreateContainer(containerSettings).Start(), HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED));
1640 + }
1641 +
1642 + // Create a GPU-enabled session.
1643 + const std::filesystem::path gpuStorage = m_storagePath / "wslc-winrt-gpu-session-storage";
1644 + auto cleanupStorage = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
1645 + std::error_code error;
1646 + std::filesystem::remove_all(gpuStorage, error);
1647 + });
1648 +
1649 + auto settings = WSLCSDK::SessionSettings(L"wslc-winrt-gpu-test", gpuStorage.wstring());
1650 + settings.FeatureFlags(WSLCSDK::SessionFeatureFlags::EnableGpu);
1651 + settings.VhdRequirements(WSLCSDK::VhdOptions(L"", 4096ull * 1024 * 1024, WSLCSDK::VhdType::Dynamic));
1652 +
1653 + auto gpuSession = WSLCSDK::Session(settings);
1654 + gpuSession.Start();
1655 +
1656 + const auto debianTar = GetTestImagePath("debian:latest");
1657 + gpuSession.LoadImageAsync(debianTar.wstring()).get();
1658 +
1659 + // Positive: /dev/dxg must be available and LD_LIBRARY_PATH set in a GPU container.
1660 + {
1661 + auto procSettings = WSLCSDK::ProcessSettings();
1662 + procSettings.CmdLine(
1663 + winrt::single_threaded_vector<winrt::hstring>({L"/bin/sh", L"-c", L"test -c /dev/dxg && echo $LD_LIBRARY_PATH"}));
1664 + procSettings.OutputMode(WSLCSDK::ProcessOutputMode::Stream);
1665 +
1666 + auto containerSettings = WSLCSDK::ContainerSettings(L"debian:latest");
1667 + containerSettings.InitProcess(procSettings);
1668 + containerSettings.Flags(WSLCSDK::ContainerFlags::EnableGpu);
1669 +
1670 + auto container = gpuSession.CreateContainer(containerSettings);
1671 + auto cleanup = DELETE_CONTAINER_ON_SCOPE_EXIT(container);
1672 +
1673 + StartContainerAndWaitForInitProcessExit(container);
1674 + auto output = GetProcessOutput(container.InitProcess());
1675 +
1676 + VERIFY_ARE_EQUAL(output.StandardOutput, L"/usr/lib/wsl/lib\n");
1677 + }
1678 +
1679 + gpuSession.Terminate();
1680 + }
1681 +};