| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | wslutil.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | This file contains helper function declarations. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #pragma once |
| 16 | #include <functional> |
| 17 | #include <span> |
| 18 | #include <type_traits> |
| 19 | #include "SubProcess.h" |
| 20 | #include <winrt/windows.management.deployment.h> |
| 21 | #include "JsonUtils.h" |
| 22 | #include "wslc.h" |
| 23 | |
| 24 | namespace wsl::windows::common { |
| 25 | struct Error; |
| 26 | |
| 27 | namespace io { |
| 28 | struct HandleWrapper; |
| 29 | } |
| 30 | |
| 31 | struct ErrorStrings |
| 32 | { |
| 33 | std::wstring Message; |
| 34 | std::wstring Code; |
| 35 | std::optional<std::wstring> Source; |
| 36 | }; |
| 37 | } // namespace wsl::windows::common |
| 38 | |
| 39 | namespace wsl::windows::common::wslutil { |
| 40 | |
| 41 | // Namespace GUID used for Windows Terminal profile generation. |
| 42 | // {BE9372FE-59E1-4876-BDA9-C33C8F2F1AF1} |
| 43 | inline constexpr GUID WslTerminalNamespace = {0xbe9372fe, 0x59e1, 0x4876, {0xbd, 0xa9, 0xc3, 0x3c, 0x8f, 0x2f, 0x1a, 0xf1}}; |
| 44 | |
| 45 | // Namespace GUID for automatically generated Windows Terminal profiles. |
| 46 | // {2bde4a90-d05f-401c-9492-e40884ead1d8} |
| 47 | inline constexpr GUID GeneratedProfilesTerminalNamespace = {0x2bde4a90, 0xd05f, 0x401c, {0x94, 0x92, 0xe4, 0x8, 0x84, 0xea, 0xd1, 0xd8}}; |
| 48 | |
| 49 | inline auto c_msixPackageFamilyName = L"MicrosoftCorporationII.WindowsSubsystemForLinux_8wekyb3d8bbwe"; |
| 50 | inline auto c_githubUrlOverrideRegistryValue = L"GitHubUrlOverride"; |
| 51 | inline auto c_vhdFileExtension = L".vhd"; |
| 52 | inline auto c_vhdxFileExtension = L".vhdx"; |
| 53 | inline constexpr auto c_vmOwner = L"WSL"; // TODO-WSLC: Does this apply to WSLC ? |
| 54 | |
| 55 | struct GitHubReleaseAsset |
| 56 | { |
| 57 | std::wstring url; |
| 58 | uint64_t id{}; |
| 59 | std::wstring name; |
| 60 | |
| 61 | NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GitHubReleaseAsset, url, id, name); |
| 62 | }; |
| 63 | |
| 64 | struct GitHubRelease |
| 65 | { |
| 66 | std::wstring name; |
| 67 | std::vector<GitHubReleaseAsset> assets; |
| 68 | std::wstring created_at; |
| 69 | |
| 70 | NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GitHubRelease, name, assets, created_at); |
| 71 | }; |
| 72 | |
| 73 | struct COMErrorInfo |
| 74 | { |
| 75 | wil::unique_bstr Message; |
| 76 | wil::unique_bstr Source; |
| 77 | }; |
| 78 | |
| 79 | static_assert(sizeof(WSLCHandle::Handle) == sizeof(HANDLE)); |
| 80 | static_assert(sizeof(FILE_HANDLE) == sizeof(HANDLE)); |
| 81 | static_assert(sizeof(PIPE_HANDLE) == sizeof(HANDLE)); |
| 82 | static_assert(sizeof(SOCKET_HANDLE) == sizeof(HANDLE)); |
| 83 | |
| 84 | struct COMOutputHandle : public WSLCHandle |
| 85 | { |
| 86 | NON_COPYABLE(COMOutputHandle); |
| 87 | NON_MOVABLE(COMOutputHandle); |
| 88 | COMOutputHandle() |
| 89 | { |
| 90 | ZeroMemory(&Handle, sizeof(Handle)); |
| 91 | Type = WSLCHandleTypeUnknown; |
| 92 | } |
| 93 | |
| 94 | ~COMOutputHandle() |
| 95 | { |
| 96 | Reset(); |
| 97 | } |
| 98 | |
| 99 | void Reset() noexcept |
| 100 | { |
| 101 | if (!Empty()) |
| 102 | { |
| 103 | if (Type == WSLCHandleTypeSocket) |
| 104 | { |
| 105 | LOG_LAST_ERROR_IF(closesocket(reinterpret_cast<SOCKET>(Handle.Socket)) == SOCKET_ERROR); |
| 106 | } |
| 107 | else |
| 108 | { |
| 109 | LOG_IF_WIN32_BOOL_FALSE(CloseHandle(Handle.File)); |
| 110 | } |
| 111 | |
| 112 | Handle.File = nullptr; |
| 113 | Type = WSLCHandleTypeUnknown; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | [[nodiscard]] io::HandleWrapper Release(); |
| 118 | |
| 119 | HANDLE Get() const noexcept |
| 120 | { |
| 121 | return Handle.File; |
| 122 | } |
| 123 | |
| 124 | bool Empty() const noexcept |
| 125 | { |
| 126 | return Handle.File == nullptr || Handle.File == INVALID_HANDLE_VALUE; |
| 127 | } |
| 128 | }; |
| 129 | |
| 130 | struct PruneResult |
| 131 | { |
| 132 | NON_COPYABLE(PruneResult); |
| 133 | WSLCPruneContainersResults result{}; |
| 134 | |
| 135 | PruneResult() = default; |
| 136 | |
| 137 | PruneResult(PruneResult&& other) |
| 138 | { |
| 139 | *this = std::move(other); |
| 140 | } |
| 141 | |
| 142 | PruneResult& operator=(PruneResult&& other) |
| 143 | { |
| 144 | CoTaskMemFree(result.Containers); |
| 145 | result.Containers = other.result.Containers; |
| 146 | result.ContainersCount = other.result.ContainersCount; |
| 147 | result.SpaceReclaimed = other.result.SpaceReclaimed; |
| 148 | |
| 149 | other.result.Containers = nullptr; |
| 150 | other.result.ContainersCount = 0; |
| 151 | other.result.SpaceReclaimed = 0; |
| 152 | |
| 153 | return *this; |
| 154 | } |
| 155 | |
| 156 | ~PruneResult() |
| 157 | { |
| 158 | CoTaskMemFree(result.Containers); |
| 159 | } |
| 160 | }; |
| 161 | |
| 162 | class StopWatch |
| 163 | { |
| 164 | NON_COPYABLE(StopWatch); |
| 165 | NON_MOVABLE(StopWatch); |
| 166 | |
| 167 | public: |
| 168 | StopWatch() = default; |
| 169 | |
| 170 | uint64_t ElapsedMilliseconds() const |
| 171 | { |
| 172 | return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_startTime).count(); |
| 173 | } |
| 174 | |
| 175 | private: |
| 176 | std::chrono::steady_clock::time_point m_startTime = std::chrono::steady_clock::now(); |
| 177 | }; |
| 178 | |
| 179 | template <typename T> |
| 180 | void AssertValidPrintfArg() |
| 181 | { |
| 182 | static_assert(std::is_fundamental_v<T> || std::is_same_v<wchar_t*, T> || std::is_same_v<char*, T> || std::is_same_v<HRESULT, T>); |
| 183 | } |
| 184 | |
| 185 | template <typename TInterface> |
| 186 | wil::com_ptr<TInterface> CoGetCallContext(); |
| 187 | |
| 188 | void CoInitializeSecurity(); |
| 189 | |
| 190 | void ConfigureCrt(); |
| 191 | |
| 192 | /// <summary> |
| 193 | /// Creates a COM server with user impersonation. |
| 194 | /// </summary> |
| 195 | template <typename Interface> |
| 196 | wil::com_ptr_t<Interface> CreateComServerAsUser(_In_ REFCLSID RefClsId, _In_ HANDLE UserToken) |
| 197 | { |
| 198 | auto revert = wil::impersonate_token(UserToken); |
| 199 | return wil::CoCreateInstance<Interface>(RefClsId, (CLSCTX_LOCAL_SERVER | CLSCTX_ENABLE_CLOAKING | CLSCTX_ENABLE_AAA)); |
| 200 | } |
| 201 | |
| 202 | template <typename Class, typename Interface> |
| 203 | wil::com_ptr_t<Interface> CreateComServerAsUser(_In_ HANDLE UserToken) |
| 204 | { |
| 205 | return CreateComServerAsUser<Interface>(__uuidof(Class), UserToken); |
| 206 | } |
| 207 | |
| 208 | std::wstring ConstructPipePath(_In_ std::wstring_view PipeName); |
| 209 | |
| 210 | GUID CreateV5Uuid(const GUID& namespaceGuid, const std::span<const std::byte> name); |
| 211 | |
| 212 | std::wstring DownloadFile(std::wstring_view Url, std::wstring Filename, bool reportProgress = true); |
| 213 | |
| 214 | std::wstring DownloadFileImpl(std::wstring_view Url, std::wstring Filename, const std::function<void(uint64_t, uint64_t)>& Progress); |
| 215 | |
| 216 | [[nodiscard]] HANDLE DuplicateHandle(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess = std::nullopt, _In_ BOOL InheritHandle = FALSE); |
| 217 | |
| 218 | [[nodiscard]] HANDLE DuplicateHandleFromCallingProcess(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess = {}); |
| 219 | |
| 220 | [[nodiscard]] HANDLE DuplicateHandleToCallingProcess(_In_ HANDLE Handle, _In_ std::optional<DWORD> DesiredAccess = {}); |
| 221 | |
| 222 | void EnforceFileLimit(LPCWSTR Folder, size_t limit, const std::function<bool(const std::filesystem::directory_entry&)>& pred); |
| 223 | |
| 224 | std::wstring ErrorCodeToString(HRESULT Error); |
| 225 | |
| 226 | ErrorStrings ErrorToString(const Error& error); |
| 227 | |
| 228 | [[nodiscard]] HANDLE FromCOMInputHandle(WSLCHandle Handle); |
| 229 | |
| 230 | std::filesystem::path GetBasePath(); |
| 231 | |
| 232 | std::optional<COMErrorInfo> GetCOMErrorInfo(); |
| 233 | |
| 234 | DWORD GetDefaultVersion(void); |
| 235 | |
| 236 | std::wstring GetErrorString(_In_ HRESULT result); |
| 237 | |
| 238 | std::optional<std::pair<std::wstring, GitHubReleaseAsset>> GetGitHubAssetFromRelease(const GitHubRelease& Release); |
| 239 | |
| 240 | std::pair<std::wstring, GitHubReleaseAsset> GetLatestGitHubRelease(_In_ bool preRelease); |
| 241 | |
| 242 | std::pair<std::wstring, GitHubReleaseAsset> GetLatestGitHubRelease(_In_ bool preRelease, _In_ LPCWSTR releases); |
| 243 | |
| 244 | GitHubRelease GetGitHubReleaseByTag(_In_ const std::wstring& Version); |
| 245 | |
| 246 | int GetLogicalProcessorCount(); |
| 247 | |
| 248 | std::optional<std::wstring> GetMsiPackagePath(); |
| 249 | |
| 250 | std::wstring GetPackageFamilyName(_In_ HANDLE process = GetCurrentProcess()); |
| 251 | |
| 252 | std::wstring GetSystemErrorString(_In_ HRESULT result); |
| 253 | |
| 254 | std::wstring GetDebugShellPipeName(_In_ PSID Sid); |
| 255 | |
| 256 | std::optional<std::tuple<uint32_t, uint32_t, uint32_t>> GetInstalledPackageVersion(); |
| 257 | |
| 258 | std::vector<BYTE> HashFile(HANDLE File, DWORD Algorithm); |
| 259 | |
| 260 | void InitializeWil(); |
| 261 | |
| 262 | bool IsConsoleHandle(HANDLE Handle); |
| 263 | |
| 264 | bool IsInteractiveConsole(); |
| 265 | |
| 266 | bool IsRunningInMsix(); |
| 267 | |
| 268 | bool IsVhdFile(_In_ const std::filesystem::path& path); |
| 269 | |
| 270 | bool IsVirtualMachinePlatformInstalled(); |
| 271 | |
| 272 | std::vector<DWORD> ListRunningProcesses(); |
| 273 | |
| 274 | // An immutable container repository reference. Holds the original repository token together with its normalized |
| 275 | // registry server and path, following Docker's client-side normalization (e.g. "ubuntu" -> {"docker.io", |
| 276 | // "library/ubuntu"}). Construct one with Parse(). Normalization is lossy, so the original Name is retained for |
| 277 | // callers that must echo the repository exactly as it was written. |
| 278 | struct RepositoryReference |
| 279 | { |
| 280 | const std::string Name; |
| 281 | const std::string Server; |
| 282 | const std::string Path; |
| 283 | |
| 284 | // Split and normalize a repository string into its registry server and path. |
| 285 | static RepositoryReference Parse(const std::string& repository); |
| 286 | |
| 287 | // The fully-qualified "server/path" form (e.g. "docker.io/library/ubuntu"). |
| 288 | std::string GetCanonical() const; |
| 289 | }; |
| 290 | |
| 291 | std::pair<wil::unique_hfile, wil::unique_hfile> OpenAnonymousPipe(DWORD Size, bool ReadPipeOverlapped, bool WritePipeOverlapped); |
| 292 | |
| 293 | wil::unique_handle OpenCallingProcess(_In_ DWORD access); |
| 294 | |
| 295 | void ParseIpv4Address(const char* Address, in_addr& Result); |
| 296 | |
| 297 | void ParseIpv6Address(const char* Address, in_addr6& Result); |
| 298 | |
| 299 | std::tuple<uint32_t, uint32_t, uint32_t> ParseWslPackageVersion(_In_ const std::wstring& Version); |
| 300 | |
| 301 | // A parsed, immutable image reference such as "ubuntu:22.04@sha256:...". Construct one with Parse(). |
| 302 | struct ImageReference |
| 303 | { |
| 304 | const RepositoryReference Repository; |
| 305 | const std::optional<std::string> Tag; |
| 306 | const std::optional<std::string> Digest; |
| 307 | const EnumReferenceFormat Format; |
| 308 | |
| 309 | // Parse an image reference string into its components. Throws E_INVALIDARG (with a user-facing error) when the |
| 310 | // reference is malformed. |
| 311 | static ImageReference Parse(const std::string& input); |
| 312 | |
| 313 | // Collapse the reference to a single tag-or-digest field, where a digest takes precedence over a tag. This matches |
| 314 | // how callers that resolve, pull or push a single reference treat the two. |
| 315 | std::optional<std::string> TagOrDigest() const |
| 316 | { |
| 317 | return Digest.has_value() ? Digest : Tag; |
| 318 | } |
| 319 | |
| 320 | // The fully-qualified canonical reference, matching the string printed by `docker pull` |
| 321 | // (e.g. "ubuntu" -> "docker.io/library/ubuntu:latest"). |
| 322 | std::string GetCanonical() const; |
| 323 | }; |
| 324 | |
| 325 | void PrintSystemError(_In_ HRESULT result, _Inout_ FILE* stream = stdout); |
| 326 | |
| 327 | void PrintMessageImpl(_In_ const std::wstring& message, _In_ va_list& args, _Inout_ FILE* stream = stdout); |
| 328 | |
| 329 | void PrintMessageImpl(_In_ const std::wstring& message, _Inout_ FILE* stream = stdout, ...); |
| 330 | |
| 331 | void PrintMessage(_In_ const std::wstring& message, _Inout_ FILE* stream = stdout); |
| 332 | |
| 333 | // This template is used to switch between the varargs and non-vararg versions of PrintMessage(). |
| 334 | // If no varargs are passed, then the string shouldn't be used as a printf format specifier. |
| 335 | template <typename... Args> |
| 336 | void PrintMessage(_In_ const std::wstring& message, _Inout_ FILE* const stream = stdout, Args... args) |
| 337 | { |
| 338 | static_assert(sizeof...(Args) > 0); |
| 339 | |
| 340 | // Validate that all Args are valid printf arguments. |
| 341 | ( |
| 342 | [](auto e) { |
| 343 | using T = decltype(e); |
| 344 | if constexpr (std::is_pointer_v<T>) |
| 345 | { |
| 346 | AssertValidPrintfArg<std::add_pointer_t<std::remove_const_t<std::remove_pointer_t<T>>>>(); |
| 347 | } |
| 348 | else |
| 349 | { |
| 350 | AssertValidPrintfArg<std::remove_const_t<T>>(); |
| 351 | } |
| 352 | }(args), |
| 353 | ...); |
| 354 | |
| 355 | PrintMessageImpl(message, stream, std::forward<Args>(args)...); |
| 356 | } |
| 357 | |
| 358 | // Reads an environment variable. Returns nullopt iff the variable is not defined; an engaged |
| 359 | // (possibly empty) string otherwise. |
| 360 | std::optional<std::wstring> ReadEnvironmentVariable(_In_ LPCWSTR Name); |
| 361 | |
| 362 | void SetCrtEncoding(int Mode); |
| 363 | |
| 364 | void SetThreadDescription(LPCWSTR Name); |
| 365 | |
| 366 | wil::unique_hlocal_string SidToString(_In_ PSID Sid); |
| 367 | |
| 368 | WSLCHandle ToCOMInputHandle(HANDLE Handle); |
| 369 | [[nodiscard]] WSLCHandle ToCOMOutputHandle(HANDLE Handle, DWORD Access); |
| 370 | [[nodiscard]] WSLCHandle ToCOMOutputHandle(HANDLE Handle, DWORD Access, WSLCHandleType Type); |
| 371 | |
| 372 | winrt::Windows::Management::Deployment::PackageVolume GetSystemVolume(); |
| 373 | |
| 374 | std::string Base64Encode(const std::string& input); |
| 375 | std::string Base64Decode(const std::string& encoded); |
| 376 | |
| 377 | // Builds the base64-encoded X-Registry-Auth header value used by Docker APIs |
| 378 | // (PullImage, PushImage, etc.) from the given credentials. |
| 379 | std::string BuildRegistryAuthHeader(const std::string& username, const std::string& password); |
| 380 | |
| 381 | // Builds the base64-encoded X-Registry-Auth header value from an identity token |
| 382 | // returned by Authenticate(). |
| 383 | std::string BuildRegistryAuthHeader(const std::string& identityToken); |
| 384 | |
| 385 | std::map<std::string, std::string> ParseKeyValuePairs(_In_reads_opt_(count) const KeyValuePair* pairs, ULONG count, _In_opt_ LPCSTR reservedKey = nullptr); |
| 386 | std::map<std::string, std::vector<std::string>> ParseKeyMultiValuePairs(_In_reads_opt_(count) const KeyValuePair* pairs, ULONG count); |
| 387 | |
| 388 | } // namespace wsl::windows::common::wslutil |