| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | hcs.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains helper function definitions for interacting with the |
| 12 | host compute service. |
| 13 | |
| 14 | --*/ |
| 15 | |
| 16 | #include "precomp.h" |
| 17 | #include "hcs.hpp" |
| 18 | #include <ComputeCore.h> |
| 19 | |
| 20 | #pragma hdrstop |
| 21 | |
| 22 | using wsl::windows::common::Context; |
| 23 | using wsl::windows::common::ExecutionContext; |
| 24 | |
| 25 | constexpr auto c_processorCapabilities = "ProcessorCapabilities"; |
| 26 | constexpr LPCWSTR c_processorCapabilitiesQuery = L"{ \"PropertyQueries\": {\"ProcessorCapabilities\" : {}}}"; |
| 27 | constexpr LPCWSTR c_scsiResourcePath = L"VirtualMachine/Devices/Scsi/0/Attachments/"; |
| 28 | |
| 29 | void wsl::windows::common::hcs::AddPlan9Share( |
| 30 | _In_ HCS_SYSTEM ComputeSystem, _In_ PCWSTR Name, _In_ PCWSTR AccessName, _In_ PCWSTR Path, _In_ UINT32 Port, _In_ Plan9ShareFlags Flags, _In_opt_ HANDLE UserToken) |
| 31 | { |
| 32 | ModifySettingRequest<Plan9Share> request{}; |
| 33 | request.RequestType = ModifyRequestType::Add; |
| 34 | request.ResourcePath = L"VirtualMachine/Devices/Plan9/Shares"; |
| 35 | request.Settings.Name = Name; |
| 36 | request.Settings.AccessName = AccessName; |
| 37 | request.Settings.Path = Path; |
| 38 | request.Settings.Port = Port; |
| 39 | WI_SetFlagIf(Flags, Plan9ShareFlags::UseShareRootIdentity, ARGUMENT_PRESENT(UserToken)); |
| 40 | request.Settings.Flags = Flags; |
| 41 | |
| 42 | ModifyComputeSystem(ComputeSystem, wsl::shared::ToJsonW(request).c_str(), UserToken); |
| 43 | } |
| 44 | |
| 45 | void wsl::windows::common::hcs::RemovePlan9Share(_In_ HCS_SYSTEM ComputeSystem, _In_ PCWSTR AccessName, _In_ UINT32 Port) |
| 46 | { |
| 47 | ModifySettingRequest<Plan9Share> request{}; |
| 48 | request.RequestType = ModifyRequestType::Remove; |
| 49 | request.ResourcePath = L"VirtualMachine/Devices/Plan9/Shares"; |
| 50 | request.Settings.AccessName = AccessName; |
| 51 | request.Settings.Port = Port; |
| 52 | |
| 53 | ModifyComputeSystem(ComputeSystem, wsl::shared::ToJsonW(request).c_str()); |
| 54 | } |
| 55 | |
| 56 | void wsl::windows::common::hcs::AddVhd(_In_ HCS_SYSTEM ComputeSystem, _In_ PCWSTR VhdPath, _In_ ULONG Lun, _In_ bool ReadOnly) |
| 57 | { |
| 58 | ModifySettingRequest<Attachment> request{}; |
| 59 | request.RequestType = ModifyRequestType::Add; |
| 60 | request.ResourcePath = c_scsiResourcePath + std::to_wstring(Lun); |
| 61 | request.Settings.Path = VhdPath; |
| 62 | request.Settings.ReadOnly = ReadOnly; |
| 63 | request.Settings.Type = AttachmentType::VirtualDisk; |
| 64 | request.Settings.SupportCompressedVolumes = true; |
| 65 | request.Settings.AlwaysAllowSparseFiles = true; |
| 66 | request.Settings.SupportEncryptedFiles = true; |
| 67 | |
| 68 | ModifyComputeSystem(ComputeSystem, wsl::shared::ToJsonW(request).c_str()); |
| 69 | } |
| 70 | |
| 71 | void wsl::windows::common::hcs::AddPassThroughDisk(_In_ HCS_SYSTEM ComputeSystem, _In_ PCWSTR Disk, _In_ ULONG Lun) |
| 72 | { |
| 73 | ModifySettingRequest<Attachment> request{}; |
| 74 | request.RequestType = ModifyRequestType::Add; |
| 75 | request.Settings.Path = Disk; |
| 76 | request.ResourcePath = c_scsiResourcePath + std::to_wstring(Lun); |
| 77 | request.Settings.Type = AttachmentType::PassThru; |
| 78 | |
| 79 | ModifyComputeSystem(ComputeSystem, wsl::shared::ToJsonW(request).c_str()); |
| 80 | } |
| 81 | |
| 82 | wsl::windows::common::hcs::unique_hcs_operation wsl::windows::common::hcs::CreateOperation() |
| 83 | { |
| 84 | unique_hcs_operation operation(::HcsCreateOperation(nullptr, nullptr)); |
| 85 | THROW_LAST_ERROR_IF_MSG(!operation, "HcsCreateOperation"); |
| 86 | |
| 87 | return operation; |
| 88 | } |
| 89 | |
| 90 | wsl::windows::common::hcs::unique_hcs_system wsl::windows::common::hcs::CreateComputeSystem(_In_ PCWSTR Id, _In_ PCWSTR Configuration) |
| 91 | { |
| 92 | WSL_LOG_DEBUG("HcsCreateComputeSystem", TraceLoggingValue(Id, "id"), TraceLoggingValue(Configuration, "configuration")); |
| 93 | |
| 94 | ExecutionContext context(Context::HCS); |
| 95 | |
| 96 | const unique_hcs_operation operation = CreateOperation(); |
| 97 | unique_hcs_system system{}; |
| 98 | THROW_IF_FAILED(::HcsCreateComputeSystem(Id, Configuration, operation.get(), nullptr, &system)); |
| 99 | |
| 100 | wil::unique_cotaskmem_string resultDocument; |
| 101 | const auto result = ::HcsWaitForOperationResult(operation.get(), INFINITE, &resultDocument); |
| 102 | if (FAILED(result)) |
| 103 | { |
| 104 | // N.B. Logging is split into two calls because the configuration and error strings can be quite long. |
| 105 | LOG_HR_MSG(result, "HcsCreateComputeSystem(%ls, %ls)", Id, Configuration); |
| 106 | THROW_HR_MSG(result, "HcsCreateComputeSystem failed (error string: %ls)", resultDocument.get()); |
| 107 | } |
| 108 | |
| 109 | return system; |
| 110 | } |
| 111 | |
| 112 | const std::vector<std::string>& wsl::windows::common::hcs::GetProcessorFeatures() |
| 113 | { |
| 114 | static std::vector<std::string> g_processorFeatures; |
| 115 | static std::once_flag flag; |
| 116 | std::call_once(flag, []() { |
| 117 | ExecutionContext context(Context::HCS); |
| 118 | |
| 119 | wil::unique_cotaskmem_string result; |
| 120 | THROW_IF_FAILED(::HcsGetServiceProperties(c_processorCapabilitiesQuery, &result)); |
| 121 | |
| 122 | const auto properties = |
| 123 | wsl::shared::FromJson<ServicePropertiesResponse<PropertyResponse<ProcessorCapabilitiesInfo>>>(result.get()); |
| 124 | |
| 125 | const auto& response = properties.PropertyResponses.at(c_processorCapabilities); |
| 126 | if (response.Error) |
| 127 | { |
| 128 | THROW_HR_MSG(static_cast<HRESULT>(response.Error->Error), "%hs", response.Error->ErrorMessage.c_str()); |
| 129 | } |
| 130 | |
| 131 | g_processorFeatures = response.Response.ProcessorFeatures; |
| 132 | }); |
| 133 | |
| 134 | return g_processorFeatures; |
| 135 | } |
| 136 | |
| 137 | wsl::shared::hns::HNSEndpoint wsl::windows::common::hcs::GetEndpointProperties(HCN_ENDPOINT Endpoint) |
| 138 | { |
| 139 | WSL_LOG_DEBUG("HcsGetEndpointProperties"); |
| 140 | |
| 141 | ExecutionContext context(Context::HNS); |
| 142 | |
| 143 | wil::unique_cotaskmem_string propertiesString; |
| 144 | wil::unique_cotaskmem_string error; |
| 145 | const auto result = HcnQueryEndpointProperties(Endpoint, nullptr, &propertiesString, &error); |
| 146 | THROW_IF_FAILED_MSG(result, "HcnQueryEndpointProperties %ls", error.get()); |
| 147 | |
| 148 | return wsl::shared::FromJson<wsl::shared::hns::HNSEndpoint>(propertiesString.get()); |
| 149 | } |
| 150 | |
| 151 | GUID wsl::windows::common::hcs::GetRuntimeId(_In_ HCS_SYSTEM ComputeSystem) |
| 152 | { |
| 153 | ExecutionContext context(Context::HCS); |
| 154 | |
| 155 | const unique_hcs_operation operation = CreateOperation(); |
| 156 | THROW_IF_FAILED(::HcsGetComputeSystemProperties(ComputeSystem, operation.get(), nullptr)); |
| 157 | |
| 158 | wil::unique_cotaskmem_string resultDocument; |
| 159 | const auto result = ::HcsWaitForOperationResult(operation.get(), INFINITE, &resultDocument); |
| 160 | THROW_IF_FAILED_MSG(result, "HcsGetComputeSystemProperties failed (error string: %ls)", resultDocument.get()); |
| 161 | |
| 162 | const auto properties = wsl::shared::FromJson<Properties>(resultDocument.get()); |
| 163 | THROW_HR_IF(HCS_E_SYSTEM_NOT_FOUND, (properties.SystemType != SystemType::VirtualMachine)); |
| 164 | |
| 165 | return properties.RuntimeId; |
| 166 | } |
| 167 | |
| 168 | std::pair<uint32_t, uint32_t> wsl::windows::common::hcs::GetSchemaVersion() |
| 169 | { |
| 170 | static std::pair<uint32_t, uint32_t> g_schemaVersion{}; |
| 171 | static std::once_flag flag; |
| 172 | std::call_once(flag, []() { |
| 173 | ExecutionContext context(Context::HCS); |
| 174 | |
| 175 | PropertyQuery query; |
| 176 | query.PropertyTypes.emplace_back(PropertyType::Basic); |
| 177 | wil::unique_cotaskmem_string result; |
| 178 | THROW_IF_FAILED(::HcsGetServiceProperties(wsl::shared::ToJsonW(query).c_str(), &result)); |
| 179 | |
| 180 | const auto properties = wsl::shared::FromJson<ServiceProperties<BasicInformation>>(result.get()); |
| 181 | THROW_HR_IF_MSG(E_UNEXPECTED, properties.Properties.empty(), "%ls", result.get()); |
| 182 | |
| 183 | uint32_t majorVersion = 0; |
| 184 | uint32_t minorVersion = 0; |
| 185 | for (const auto& version : properties.Properties[0].SupportedSchemaVersions) |
| 186 | { |
| 187 | if (version.Major >= majorVersion) |
| 188 | { |
| 189 | if ((version.Major > majorVersion) || (version.Minor > minorVersion)) |
| 190 | { |
| 191 | majorVersion = version.Major; |
| 192 | minorVersion = version.Minor; |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | g_schemaVersion = {majorVersion, minorVersion}; |
| 198 | }); |
| 199 | |
| 200 | return g_schemaVersion; |
| 201 | } |
| 202 | |
| 203 | void wsl::windows::common::hcs::GrantVmAccess(_In_ PCWSTR VmId, _In_ PCWSTR FilePath) |
| 204 | { |
| 205 | WSL_LOG_DEBUG("HcsGrantVmAccess", TraceLoggingValue(VmId, "vmId"), TraceLoggingValue(FilePath, "filePath")); |
| 206 | |
| 207 | ExecutionContext context(Context::HCS); |
| 208 | |
| 209 | THROW_IF_FAILED_MSG(::HcsGrantVmAccess(VmId, FilePath), "HcsGrantVmAccess(%ls, %ls)", VmId, FilePath); |
| 210 | } |
| 211 | |
| 212 | void wsl::windows::common::hcs::ModifyComputeSystem(_In_ HCS_SYSTEM ComputeSystem, _In_ PCWSTR Configuration, _In_opt_ HANDLE Identity) |
| 213 | { |
| 214 | WSL_LOG_DEBUG("HcsModifyComputeSystem", TraceLoggingValue(Configuration, "configuration")); |
| 215 | |
| 216 | ExecutionContext context(Context::HCS); |
| 217 | |
| 218 | const unique_hcs_operation operation = CreateOperation(); |
| 219 | THROW_IF_FAILED_MSG( |
| 220 | ::HcsModifyComputeSystem(ComputeSystem, operation.get(), Configuration, Identity), "HcsModifyComputeSystem (%ls)", Configuration); |
| 221 | |
| 222 | wil::unique_cotaskmem_string resultDocument; |
| 223 | const auto result = ::HcsWaitForOperationResult(operation.get(), INFINITE, &resultDocument); |
| 224 | if (FAILED(result)) |
| 225 | { |
| 226 | // N.B. Logging is split into two calls because the configuration and error strings can be quite long. |
| 227 | LOG_HR_MSG(result, "HcsModifyComputeSystem(%ls)", Configuration); |
| 228 | THROW_HR_MSG(result, "HcsModifyComputeSystem failed (error string: %ls)", resultDocument.get()); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | wsl::windows::common::hcs::unique_hcs_system wsl::windows::common::hcs::OpenComputeSystem(_In_ PCWSTR Id, _In_ DWORD RequestedAccess) |
| 233 | { |
| 234 | WSL_LOG_DEBUG("HcsOpenComputeSystem", TraceLoggingValue(Id, "id"), TraceLoggingValue(RequestedAccess, "requestedAccess")); |
| 235 | |
| 236 | ExecutionContext context(Context::HCS); |
| 237 | |
| 238 | unique_hcs_system system; |
| 239 | THROW_IF_FAILED_MSG(::HcsOpenComputeSystem(Id, RequestedAccess, &system), "HcsOpenComputeSystem(%ls)", Id); |
| 240 | |
| 241 | return system; |
| 242 | } |
| 243 | |
| 244 | void wsl::windows::common::hcs::RegisterCallback(_In_ HCS_SYSTEM ComputeSystem, _In_ HCS_EVENT_CALLBACK Callback, _In_ void* Context) |
| 245 | { |
| 246 | WSL_LOG_DEBUG("HcsSetComputeSystemCallback"); |
| 247 | |
| 248 | ExecutionContext context(Context::HCS); |
| 249 | |
| 250 | THROW_IF_FAILED(::HcsSetComputeSystemCallback(ComputeSystem, HcsEventOptionNone, Context, Callback)); |
| 251 | } |
| 252 | |
| 253 | void wsl::windows::common::hcs::RemoveScsiDisk(_In_ HCS_SYSTEM ComputeSystem, _In_ ULONG Lun) |
| 254 | { |
| 255 | ModifySettingRequest<void> request{}; |
| 256 | request.RequestType = ModifyRequestType::Remove; |
| 257 | request.ResourcePath = c_scsiResourcePath + std::to_wstring(Lun); |
| 258 | ModifyComputeSystem(ComputeSystem, wsl::shared::ToJsonW(request).c_str()); |
| 259 | } |
| 260 | |
| 261 | void wsl::windows::common::hcs::RevokeVmAccess(_In_ PCWSTR VmId, _In_ PCWSTR FilePath) |
| 262 | { |
| 263 | WSL_LOG_DEBUG("HcsRevokeVmAccess", TraceLoggingValue(VmId, "vmId"), TraceLoggingValue(FilePath, "filePath")); |
| 264 | |
| 265 | ExecutionContext context(Context::HCS); |
| 266 | |
| 267 | THROW_IF_FAILED_MSG(::HcsRevokeVmAccess(VmId, FilePath), "HcsRevokeVmAccess(%ls, %ls)", VmId, FilePath); |
| 268 | } |
| 269 | |
| 270 | void wsl::windows::common::hcs::StartComputeSystem(_In_ HCS_SYSTEM ComputeSystem, _In_ LPCWSTR Configuration) |
| 271 | { |
| 272 | WSL_LOG_DEBUG("HcsStartComputeSystem", TraceLoggingValue(Configuration, "configuration")); |
| 273 | |
| 274 | ExecutionContext context(Context::HCS); |
| 275 | |
| 276 | const unique_hcs_operation operation = CreateOperation(); |
| 277 | THROW_IF_FAILED(::HcsStartComputeSystem(ComputeSystem, operation.get(), nullptr)); |
| 278 | |
| 279 | wil::unique_cotaskmem_string resultDocument; |
| 280 | const auto result = ::HcsWaitForOperationResult(operation.get(), INFINITE, &resultDocument); |
| 281 | if (FAILED(result)) |
| 282 | { |
| 283 | // N.B. Logging is split into two calls because the configuration and error strings can be quite long. |
| 284 | LOG_HR_MSG(result, "HcsStartComputeSystem(%ls)", Configuration); |
| 285 | THROW_HR_MSG(result, "HcsStartComputeSystem failed (error string: %ls)", resultDocument.get()); |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | void wsl::windows::common::hcs::TerminateComputeSystem(_In_ HCS_SYSTEM ComputeSystem) |
| 290 | { |
| 291 | WSL_LOG_DEBUG("HcsTerminateComputeSystem"); |
| 292 | |
| 293 | ExecutionContext context(Context::HCS); |
| 294 | |
| 295 | const unique_hcs_operation operation = CreateOperation(); |
| 296 | THROW_IF_FAILED(::HcsTerminateComputeSystem(ComputeSystem, operation.get(), nullptr)); |
| 297 | |
| 298 | wil::unique_cotaskmem_string resultDocument; |
| 299 | const auto result = ::HcsWaitForOperationResult(operation.get(), INFINITE, &resultDocument); |
| 300 | THROW_IF_FAILED_MSG(result, "HcsTerminateComputeSystem failed (error string: %ls)", resultDocument.get()); |
| 301 | } |
| 302 | |
| 303 | wsl::windows::common::hcs::unique_hcn_service_callback wsl::windows::common::hcs::RegisterServiceCallback( |
| 304 | _In_ HCS_NOTIFICATION_CALLBACK Callback, _In_ PVOID Context) |
| 305 | { |
| 306 | WSL_LOG_DEBUG("HcsRegisterServiceCallback"); |
| 307 | |
| 308 | ExecutionContext context(Context::HNS); |
| 309 | |
| 310 | unique_hcn_service_callback callbackHandle; |
| 311 | THROW_IF_FAILED(::HcnRegisterServiceCallback(Callback, Context, &callbackHandle)); |
| 312 | |
| 313 | return callbackHandle; |
| 314 | } |
| 315 | |
| 316 | wsl::windows::common::hcs::unique_hcn_guest_network_service_callback wsl::windows::common::hcs::RegisterGuestNetworkServiceCallback( |
| 317 | _In_ const unique_hcn_guest_network_service& GuestNetworkService, _In_ HCS_NOTIFICATION_CALLBACK Callback, _In_ PVOID Context) |
| 318 | { |
| 319 | WSL_LOG_DEBUG("HcsRegisterGuestNetworkServiceCallback"); |
| 320 | |
| 321 | ExecutionContext context(Context::HNS); |
| 322 | |
| 323 | unique_hcn_guest_network_service_callback callbackHandle; |
| 324 | THROW_IF_FAILED(::HcnRegisterGuestNetworkServiceCallback(GuestNetworkService.get(), Callback, Context, &callbackHandle)); |
| 325 | |
| 326 | return callbackHandle; |
| 327 | } |
| 328 | |
| 329 | bool wsl::windows::common::hcs::IsDisableVgpuSettingsSupported() |
| 330 | { |
| 331 | static constexpr std::pair<uint32_t, uint32_t> c_schemaVersionNickel{2, 7}; |
| 332 | |
| 333 | // See if the Windows version has the required platform change. |
| 334 | return ((GetSchemaVersion() >= c_schemaVersionNickel) && (wsl::windows::common::helpers::GetWindowsVersion().BuildNumber >= 22545)); |
| 335 | } |