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
+};