| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | WSLCContainer.h |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | Contains the definition for WSLCContainer. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #pragma once |
| 16 | |
| 17 | #include "ServiceProcessLauncher.h" |
| 18 | #include "WSLCSession.h" |
| 19 | #include "WSLCIdleState.h" |
| 20 | #include "DockerEventTracker.h" |
| 21 | #include "DockerHTTPClient.h" |
| 22 | #include "WSLCProcessControl.h" |
| 23 | #include "IORelay.h" |
| 24 | #include "COMImplClass.h" |
| 25 | #include "wslc_schema.h" |
| 26 | #include "WSLCCompat.h" |
| 27 | #include "WSLCContainerMetadata.h" |
| 28 | #include "WSLCNetworkMetadata.h" |
| 29 | #include "WSLCVhdVolume.h" |
| 30 | #include <unordered_map> |
| 31 | |
| 32 | namespace wsl::windows::service::wslc { |
| 33 | |
| 34 | class WSLCContainer; |
| 35 | class WSLCSession; |
| 36 | class WSLCSessionRuntime; |
| 37 | class WSLCVolumes; |
| 38 | class EventStore; |
| 39 | |
| 40 | class unique_com_disconnect |
| 41 | { |
| 42 | public: |
| 43 | NON_COPYABLE(unique_com_disconnect); |
| 44 | DEFAULT_MOVABLE(unique_com_disconnect); |
| 45 | |
| 46 | unique_com_disconnect() = default; |
| 47 | unique_com_disconnect(Microsoft::WRL::ComPtr<WSLCContainer>&& wrapper) noexcept; |
| 48 | ~unique_com_disconnect() noexcept; |
| 49 | |
| 50 | private: |
| 51 | Microsoft::WRL::ComPtr<WSLCContainer> m_wrapper; |
| 52 | }; |
| 53 | |
| 54 | struct ContainerPortMapping |
| 55 | { |
| 56 | NON_COPYABLE(ContainerPortMapping); |
| 57 | |
| 58 | ContainerPortMapping(VMPortMapping&& VmMapping, uint16_t ContainerPort); |
| 59 | ContainerPortMapping(ContainerPortMapping&& Other); |
| 60 | |
| 61 | ContainerPortMapping& operator=(ContainerPortMapping&& Other); |
| 62 | const char* ProtocolString() const; |
| 63 | |
| 64 | WSLCPortMapping Serialize() const; |
| 65 | |
| 66 | VMPortMapping VmMapping; |
| 67 | uint16_t ContainerPort{}; |
| 68 | }; |
| 69 | |
| 70 | class WSLCContainerImpl : public std::enable_shared_from_this<WSLCContainerImpl> |
| 71 | { |
| 72 | public: |
| 73 | NON_COPYABLE(WSLCContainerImpl); |
| 74 | NON_MOVABLE(WSLCContainerImpl); |
| 75 | |
| 76 | WSLCContainerImpl( |
| 77 | WSLCSession& wslcSession, |
| 78 | WSLCSessionRuntime& runtime, |
| 79 | IWSLCPluginNotifier* pluginNotifier, |
| 80 | std::string&& Id, |
| 81 | std::string&& Name, |
| 82 | std::string&& Image, |
| 83 | std::string NetworkMode, |
| 84 | std::vector<WSLCVolumeMount>&& volumes, |
| 85 | std::vector<std::string>&& namedVolumes, |
| 86 | std::vector<ContainerPortMapping>&& ports, |
| 87 | std::map<std::string, std::string>&& labels, |
| 88 | std::function<void(const WSLCContainerImpl*)>&& OnDeleted, |
| 89 | EventStore& eventStore, |
| 90 | WSLCContainerState InitialState, |
| 91 | std::int64_t CreatedAt, |
| 92 | WSLCProcessFlags InitProcessFlags, |
| 93 | WSLCContainerFlags ContainerFlags); |
| 94 | |
| 95 | ~WSLCContainerImpl(); |
| 96 | |
| 97 | void Initialize(); |
| 98 | |
| 99 | void Start(WSLCContainerStartFlags Flags, const WSLCProcessStartOptions* StartOptions); |
| 100 | void Attach(LPCSTR DetachKeys, WSLCHandle* Stdin, WSLCHandle* Stdout, WSLCHandle* Stderr) const; |
| 101 | void Stop(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, bool Kill); |
| 102 | void Restart(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds); |
| 103 | void Delete(WSLCDeleteFlags Flags); |
| 104 | void Export(WSLCHandle TarHandle) const; |
| 105 | void UploadArchive(WSLCHandle TarHandle, LPCSTR DestPath, ULONGLONG ContentSize) const; |
| 106 | void DownloadArchive(LPCSTR SrcPath, WSLCHandle OutHandle) const; |
| 107 | void GetStateChangedAt(_Out_ LONGLONG* StateChangedAt); |
| 108 | void GetCreatedAt(_Out_ LONGLONG* CreatedAt); |
| 109 | void GetState(_Out_ WSLCContainerState* State); |
| 110 | void GetInitProcess(_Out_ IWSLCProcess** process) const; |
| 111 | void Exec(_In_ const WSLCProcessOptions* Options, const WSLCProcessStartOptions* StartOptions, _Out_ IWSLCProcess** Process); |
| 112 | void Inspect(BOOL Size, LPSTR* Output) const; |
| 113 | void Logs(WSLCLogsFlags Flags, WSLCHandle* Stdout, WSLCHandle* Stderr, LONGLONG Since, LONGLONG Until, ULONGLONG Tail) const; |
| 114 | void Stats(LPSTR* Output) const; |
| 115 | void GetLabels(WSLCLabelInformation** Labels, ULONG* Count) const; |
| 116 | void ConnectToNetwork(const WSLCNetworkConnectionOptions* Options); |
| 117 | void DisconnectFromNetwork(LPCSTR NetworkName); |
| 118 | |
| 119 | void CopyTo(IWSLCContainer** Container) const; |
| 120 | |
| 121 | const std::string& Image() const noexcept; |
| 122 | const std::string& Name() const noexcept; |
| 123 | WSLCContainerState State() const noexcept; |
| 124 | std::vector<WSLCPortMapping> GetPorts() const; |
| 125 | |
| 126 | // Re-registers a stopped container's VM-scoped port allocations against the restarted VM. |
| 127 | void RecoverPorts(const common::docker_schema::ContainerInfo& dockerContainer); |
| 128 | |
| 129 | __requires_lock_held(m_lock) void CommitState(WSLCContainerState State, std::int64_t Time, std::optional<int> ExitCode = std::nullopt) noexcept; |
| 130 | |
| 131 | const std::string& ID() const noexcept; |
| 132 | |
| 133 | // Returns the container flags used to decide whether to |
| 134 | // auto-delete the container on stop. |
| 135 | WSLCContainerFlags Flags() const noexcept |
| 136 | { |
| 137 | return m_containerFlags; |
| 138 | } |
| 139 | |
| 140 | static std::shared_ptr<WSLCContainerImpl> Create( |
| 141 | const WSLCContainerOptions& Options, |
| 142 | const std::string& Name, |
| 143 | WSLCSession& wslcSession, |
| 144 | WSLCSessionRuntime& runtime, |
| 145 | IWSLCPluginNotifier* pluginNotifier, |
| 146 | const std::unordered_map<std::string, NetworkEntry>& SessionNetworks, |
| 147 | std::function<void(const WSLCContainerImpl*)>&& OnDeleted, |
| 148 | EventStore& eventStore); |
| 149 | |
| 150 | static std::shared_ptr<WSLCContainerImpl> Open( |
| 151 | const common::docker_schema::ContainerInfo& DockerContainer, |
| 152 | WSLCSession& wslcSession, |
| 153 | WSLCSessionRuntime& runtime, |
| 154 | IWSLCPluginNotifier* pluginNotifier, |
| 155 | std::function<void(const WSLCContainerImpl*)>&& OnDeleted, |
| 156 | EventStore& eventStore); |
| 157 | |
| 158 | // Appends an event for this container to the session's event stream. Must be called from the Docker |
| 159 | // event stream thread so that recorded events keep Docker's delivery order. |
| 160 | void RecordEvent(std::string&& Action, std::int64_t Time, std::optional<int> ExitCode = std::nullopt) noexcept; |
| 161 | |
| 162 | private: |
| 163 | enum class TransitionKind |
| 164 | { |
| 165 | Start, |
| 166 | Stop, |
| 167 | Delete |
| 168 | }; |
| 169 | |
| 170 | struct StateTransition |
| 171 | { |
| 172 | StateTransition(TransitionKind kind, ContainerEvent expectedEvent) : Kind(kind), ExpectedEvent(expectedEvent) |
| 173 | { |
| 174 | } |
| 175 | |
| 176 | const TransitionKind Kind; |
| 177 | wil::unique_event Completed{wil::EventOptions::ManualReset}; |
| 178 | std::exception_ptr Exception; |
| 179 | |
| 180 | // Access under WSLCContainerImpl::m_lock. |
| 181 | ContainerEvent ExpectedEvent; |
| 182 | unique_com_disconnect Wrapper; |
| 183 | }; |
| 184 | |
| 185 | // Restart() runs a stop phase followed by a start phase. This marks the pair as one transaction so |
| 186 | // that Start() and Stop() cannot land in between. |
| 187 | struct RestartTransaction |
| 188 | { |
| 189 | wil::unique_event Completed{wil::EventOptions::ManualReset}; |
| 190 | }; |
| 191 | |
| 192 | __requires_exclusive_lock_held(m_lock) void RequestDeleteExclusiveLockHeld(WSLCDeleteFlags Flags); |
| 193 | |
| 194 | void AllocateBridgedModePorts(); |
| 195 | void OnEvent(ContainerEvent event, std::optional<int> exitCode, std::int64_t eventTime) noexcept; |
| 196 | |
| 197 | __requires_exclusive_lock_held(m_lock) std::shared_ptr<StateTransition> StartTransition(TransitionKind kind, ContainerEvent expectedEvent); |
| 198 | |
| 199 | // Returns with both locks held when no transition is active (or it matches kind) and, if waitForRestart, |
| 200 | // no restart is in flight either. Both conditions are re-checked every time the locks come back. |
| 201 | void WaitForConflictingTransitionToComplete( |
| 202 | wil::rwlock_release_exclusive_scope_exit& lock, |
| 203 | wil::rwlock_release_shared_scope_exit& lifecycleLock, |
| 204 | std::optional<TransitionKind> kind = std::nullopt, |
| 205 | bool waitForRestart = true); |
| 206 | |
| 207 | // Phases of Restart(). Identical to Start() and Stop() except that they do not stand down for the |
| 208 | // restart they are part of. |
| 209 | void StartPhase(WSLCContainerStartFlags Flags, const WSLCProcessStartOptions* StartOptions, bool RestartPhase); |
| 210 | void StopPhase(WSLCSignal Signal, LONG TimeoutSeconds, bool Kill, bool RestartPhase); |
| 211 | |
| 212 | // Undoes what the phases left half-done: releases the resources the stop phase held back and |
| 213 | // requests the auto-delete OnStopped() deferred, returning that delete's transition. |
| 214 | __requires_exclusive_lock_held(m_lock) std::shared_ptr<StateTransition> OnFailedRestartExclusiveLockHeld(); |
| 215 | |
| 216 | void WaitForCompletionEvent(HANDLE Event) const; |
| 217 | void WaitForTransitionCompletion(const std::shared_ptr<StateTransition>& transition) const; |
| 218 | void AttachToTransition(const std::shared_ptr<StateTransition>& transition) const; |
| 219 | |
| 220 | __requires_exclusive_lock_held(m_lock) void CompleteTransition( |
| 221 | const std::shared_ptr<StateTransition>& transition, std::exception_ptr exception = {}) noexcept; |
| 222 | |
| 223 | __requires_exclusive_lock_held(m_lock) [[nodiscard]] unique_com_disconnect ReleaseResources(); |
| 224 | __requires_exclusive_lock_held(m_lock) void ReleaseRuntimeResources(); |
| 225 | __requires_exclusive_lock_held(m_lock) void ReleaseProcesses(); |
| 226 | __requires_exclusive_lock_held(m_lock) [[nodiscard]] unique_com_disconnect PrepareDisconnectComWrapper(); |
| 227 | |
| 228 | __requires_exclusive_lock_held(m_lock) void OnStopped(int exitCode, std::int64_t stopTime); |
| 229 | |
| 230 | void SetExitCode(int ExitCode) noexcept; |
| 231 | void SignalInitProcessExit() noexcept; |
| 232 | |
| 233 | std::unique_ptr<RelayedProcessIO> CreateRelayedProcessIO(wil::shared_socket stream, WSLCProcessFlags flags); |
| 234 | |
| 235 | wsl::windows::common::wslc_schema::InspectContainer BuildInspectContainer(const wsl::windows::common::docker_schema::InspectContainer& dockerInspect) const; |
| 236 | |
| 237 | void MapPorts(); |
| 238 | void UnmapPorts(); |
| 239 | |
| 240 | // Acquires or releases the activity hold so it is held exactly while the container is Running, |
| 241 | // keeping the session's VM alive across idle teardown. |
| 242 | __requires_lock_held(m_lock) void UpdateActivityHoldLockHeld() noexcept; |
| 243 | |
| 244 | __requires_shared_lock_held(m_lock) std::string InspectLockHeld(bool Size = false) const; |
| 245 | |
| 246 | // Lifecycle requests hold this shared until their transitions are published; event delivery holds it exclusively. |
| 247 | // N.B. Stop releases it across the docker request, which can block indefinitely, and re-checks m_stateGeneration instead. |
| 248 | wil::srwlock m_lifecycleLock; |
| 249 | mutable wil::srwlock m_lock; |
| 250 | std::string m_name; |
| 251 | std::string m_image; |
| 252 | std::string m_id; |
| 253 | WSLCProcessFlags m_initProcessFlags{}; |
| 254 | WSLCContainerFlags m_containerFlags{}; |
| 255 | mutable std::mutex m_processesLock; |
| 256 | __guarded_by(m_processesLock) std::vector<std::weak_ptr<DockerExecProcessControl>> m_processes; |
| 257 | __guarded_by(m_processesLock) Microsoft::WRL::ComPtr<IWSLCProcess> m_initProcess; |
| 258 | __guarded_by(m_processesLock) DockerContainerProcessControl* m_initProcessControl = nullptr; |
| 259 | |
| 260 | _Guarded_by_(m_lock) std::shared_ptr<StateTransition> m_transition; |
| 261 | |
| 262 | // Non-null from before Restart()'s stop phase until its start phase commits Running. Start() and |
| 263 | // Stop() stand down for that window, and OnStopped() keeps the container's runtime resources mapped |
| 264 | // and skips the auto-delete of an --rm container. Delete() does not stand down: a remove that lands |
| 265 | // between the two phases takes effect, and the restart's start phase fails. |
| 266 | _Guarded_by_(m_lock) std::shared_ptr<RestartTransaction> m_restart; |
| 267 | |
| 268 | // True between a successful StartPhase() and the release of the container's ports and mounts. A |
| 269 | // restart leaves this set across the two phases, which is what tells the start phase they are still |
| 270 | // held and must not be re-acquired. |
| 271 | _Guarded_by_(m_lock) bool m_runtimeResourcesHeld = false; |
| 272 | |
| 273 | // The container outlives any single VM: it survives idle-termination and is reused when the VM |
| 274 | // restarts. VM-scoped resources (Vm(), Docker(), Volumes(), Events(), Relay()) are therefore |
| 275 | // fetched from the (stable) runtime at each use rather than cached, since a cached reference |
| 276 | // would dangle across a restart. They are only valid while a VM lease is held. |
| 277 | WSLCSessionRuntime& m_runtime; |
| 278 | std::int64_t m_stateChangedAt{static_cast<std::int64_t>(std::time(nullptr))}; |
| 279 | std::int64_t m_createdAt{}; |
| 280 | WSLCContainerState m_state = WslcContainerStateInvalid; |
| 281 | |
| 282 | // Bumped on every state change so a thread that released m_lock can detect a state cycle, not just a difference. |
| 283 | std::uint64_t m_stateGeneration{}; |
| 284 | WSLCSession& m_wslcSession; |
| 285 | IWSLCPluginNotifier* m_pluginNotifier; |
| 286 | std::vector<ContainerPortMapping> m_mappedPorts; |
| 287 | std::vector<WSLCVolumeMount> m_mountedVolumes; |
| 288 | |
| 289 | std::vector<std::string> m_namedVolumes; |
| 290 | |
| 291 | std::map<std::string, std::string> m_labels; |
| 292 | Microsoft::WRL::ComPtr<WSLCContainer> m_comWrapper; |
| 293 | DockerEventTracker::EventTrackingReference m_containerEvents; |
| 294 | EventStore& m_eventStore; |
| 295 | std::string m_networkMode; |
| 296 | |
| 297 | // Held (non-empty) exactly while the container is Running so the session's VM stays alive even |
| 298 | // when no client holds the wrapper (e.g. a detached `run -d` container). Maintained by |
| 299 | // UpdateActivityHoldLockHeld(); released automatically when the container is destroyed. |
| 300 | ActivityRef m_activityHold; |
| 301 | }; |
| 302 | |
| 303 | class DECLSPEC_UUID("B1F1C4E3-C225-4CAE-AD8A-34C004DE1AE4") WSLCContainer |
| 304 | : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IWSLCContainer, IWSLCCompatContainer, IFastRundown, ISupportErrorInfo>, |
| 305 | public COMImplClass<WSLCContainerImpl, std::weak_ptr<WSLCContainerImpl>> |
| 306 | { |
| 307 | |
| 308 | public: |
| 309 | WSLCContainer(WSLCSession& session, std::function<void(const WSLCContainerImpl*)>&& OnDeleted); |
| 310 | |
| 311 | IFACEMETHOD(Attach)(_In_opt_ LPCSTR DetachKeys, _Out_ WSLCHandle* Stdin, _Out_ WSLCHandle* Stdout, _Out_ WSLCHandle* Stderr) override; |
| 312 | IFACEMETHOD(Stop)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds) override; |
| 313 | IFACEMETHOD(Restart)(_In_ WSLCSignal Signal, _In_ LONG TimeoutSeconds, _In_opt_ IWarningCallback* WarningCallback) override; |
| 314 | IFACEMETHOD(Kill)(_In_ WSLCSignal Signal) override; |
| 315 | IFACEMETHOD(Delete)(WSLCDeleteFlags Flags) override; |
| 316 | IFACEMETHOD(Export)(_In_ WSLCHandle TarHandle) override; |
| 317 | IFACEMETHOD(UploadArchive)(_In_ WSLCHandle TarHandle, _In_ LPCSTR DestPath, _In_ ULONGLONG ContentSize) override; |
| 318 | IFACEMETHOD(DownloadArchive)(_In_ LPCSTR SrcPath, _In_ WSLCHandle OutHandle) override; |
| 319 | IFACEMETHOD(GetState)(_Out_ WSLCContainerState* State) override; |
| 320 | IFACEMETHOD(GetInitProcess)(_Out_ IWSLCProcess** process) override; |
| 321 | IFACEMETHOD(Exec)(_In_ const WSLCProcessOptions* Options, _In_opt_ const WSLCProcessStartOptions* StartOptions, _Out_ IWSLCProcess** Process) override; |
| 322 | IFACEMETHOD(Start)(WSLCContainerStartFlags Flags, _In_opt_ const WSLCProcessStartOptions* StartOptions, _In_opt_ IWarningCallback* WarningCallback) override; |
| 323 | IFACEMETHOD(Inspect)(_In_ BOOL Size, _Out_ LPSTR* Output) override; |
| 324 | IFACEMETHOD(Logs)(_In_ WSLCLogsFlags Flags, _Out_ WSLCHandle* Stdout, _Out_ WSLCHandle* Stderr, _In_ LONGLONG Since, _In_ LONGLONG Until, _In_ ULONGLONG Tail) override; |
| 325 | IFACEMETHOD(GetId)(_Out_ WSLCContainerId Id) override; |
| 326 | IFACEMETHOD(GetName)(_Out_ LPSTR* Name) override; |
| 327 | IFACEMETHOD(GetLabels)(_Out_ WSLCLabelInformation** Labels, _Out_ ULONG* Count) override; |
| 328 | IFACEMETHOD(Stats)(_Out_ LPSTR* Output) override; |
| 329 | IFACEMETHOD(ConnectToNetwork)(_In_ const WSLCNetworkConnectionOptions* Options) override; |
| 330 | IFACEMETHOD(DisconnectFromNetwork)(_In_ LPCSTR NetworkName) override; |
| 331 | |
| 332 | // IWSLCCompatContainer. |
| 333 | IFACEMETHOD(Start)(_In_ WSLCContainerStartFlags Flags) override; |
| 334 | IFACEMETHOD(GetInitProcess)(_Out_ IWSLCCompatProcess** Process) override; |
| 335 | IFACEMETHOD(Exec)(_In_ const WSLCCompatProcessOptions* Options, _Out_ IWSLCCompatProcess** Process) override; |
| 336 | IFACEMETHOD(Inspect)(_Out_ LPSTR* Output) override; |
| 337 | |
| 338 | IFACEMETHOD(InterfaceSupportsErrorInfo)(REFIID riid); |
| 339 | |
| 340 | // Cache read-only properties so they remain accessible after the impl is disconnected. |
| 341 | // Called from WSLCContainerImpl::PrepareDisconnectComWrapper() while m_lock is held exclusively. |
| 342 | void CacheState(const std::string& id, const std::string& name, WSLCContainerState state, const Microsoft::WRL::ComPtr<IWSLCProcess>& initProcess) noexcept; |
| 343 | |
| 344 | private: |
| 345 | WSLCSession& m_session; |
| 346 | std::function<void(const WSLCContainerImpl*)> m_onDeleted; |
| 347 | |
| 348 | // Cached read-only properties populated by CacheState() so they remain |
| 349 | // accessible after the impl is disconnected. |
| 350 | mutable wil::srwlock m_cacheLock; |
| 351 | _Guarded_by_(m_cacheLock) std::optional<std::string> m_cachedId; |
| 352 | _Guarded_by_(m_cacheLock) std::optional<std::string> m_cachedName; |
| 353 | _Guarded_by_(m_cacheLock) std::optional<WSLCContainerState> m_cachedState; |
| 354 | _Guarded_by_(m_cacheLock) Microsoft::WRL::ComPtr<IWSLCProcess> m_cachedInitProcess; |
| 355 | }; |
| 356 | |
| 357 | } // namespace wsl::windows::service::wslc |