| 1 | /*++ |
| 2 | |
| 3 | Copyright (c) Microsoft. All rights reserved. |
| 4 | |
| 5 | Module Name: |
| 6 | |
| 7 | WSLCProcessControl.cpp |
| 8 | |
| 9 | Abstract: |
| 10 | |
| 11 | Contains the different WSLCProcessControl definitions for process control logic. |
| 12 | |
| 13 | --*/ |
| 14 | |
| 15 | #include "precomp.h" |
| 16 | |
| 17 | #include "WSLCProcessControl.h" |
| 18 | #include "WSLCVirtualMachine.h" |
| 19 | #include "WSLCContainer.h" |
| 20 | |
| 21 | using wsl::windows::service::wslc::DockerContainerProcessControl; |
| 22 | using wsl::windows::service::wslc::DockerExecProcessControl; |
| 23 | using wsl::windows::service::wslc::VMProcessControl; |
| 24 | using wsl::windows::service::wslc::WSLCProcessControl; |
| 25 | |
| 26 | std::pair<WSLCProcessState, int> WSLCProcessControl::GetState() const |
| 27 | { |
| 28 | if (m_exitEvent.is_signaled()) |
| 29 | { |
| 30 | WI_ASSERT(m_exitedCode.has_value()); |
| 31 | return {WslcProcessStateExited, m_exitedCode.value()}; |
| 32 | } |
| 33 | else |
| 34 | { |
| 35 | return {WslcProcessStateRunning, -1}; |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | const wil::unique_event& WSLCProcessControl::GetExitEvent() const |
| 40 | { |
| 41 | return m_exitEvent; |
| 42 | } |
| 43 | |
| 44 | DockerContainerProcessControl::DockerContainerProcessControl(WSLCContainerImpl& Container, DockerHTTPClient& DockerClient) : |
| 45 | m_container(&Container), m_client(DockerClient) |
| 46 | { |
| 47 | } |
| 48 | |
| 49 | DockerContainerProcessControl::~DockerContainerProcessControl() |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | void DockerContainerProcessControl::Signal(int Signal) |
| 54 | { |
| 55 | std::lock_guard lock{m_lock}; |
| 56 | THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_container == nullptr || m_exitEvent.is_signaled()); |
| 57 | |
| 58 | m_client.SignalContainer(m_container->ID(), static_cast<WSLCSignal>(Signal)); |
| 59 | } |
| 60 | |
| 61 | void DockerContainerProcessControl::ResizeTty(ULONG Rows, ULONG Columns) |
| 62 | { |
| 63 | std::lock_guard lock{m_lock}; |
| 64 | THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_container == nullptr || m_exitEvent.is_signaled()); |
| 65 | |
| 66 | m_client.ResizeContainerTty(m_container->ID(), Rows, Columns); |
| 67 | } |
| 68 | |
| 69 | void DockerContainerProcessControl::SetExitCode(int ExitCode) |
| 70 | { |
| 71 | std::lock_guard lock{m_lock}; |
| 72 | if (!m_exitedCode.has_value()) |
| 73 | { |
| 74 | m_exitedCode = ExitCode; |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | void DockerContainerProcessControl::SignalExit() |
| 79 | { |
| 80 | std::lock_guard lock{m_lock}; |
| 81 | if (!m_exitEvent.is_signaled()) |
| 82 | { |
| 83 | WSL_LOG("ContainerProcessStop"); |
| 84 | WI_ASSERT(m_exitedCode.has_value()); |
| 85 | m_exitEvent.SetEvent(); |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | int DockerContainerProcessControl::GetPid() const |
| 90 | { |
| 91 | return 1; |
| 92 | } |
| 93 | |
| 94 | void DockerContainerProcessControl::OnContainerReleased() noexcept |
| 95 | { |
| 96 | std::lock_guard lock{m_lock}; |
| 97 | |
| 98 | WI_ASSERT(m_container != nullptr); |
| 99 | m_container = nullptr; |
| 100 | |
| 101 | // Signal the exit event to prevent callers from being blocked on it. |
| 102 | if (!m_exitEvent.is_signaled()) |
| 103 | { |
| 104 | // If the container already produced a real exit code (recorded by SetExitCode but not yet |
| 105 | // signaled — e.g. an --rm container whose init-exit signal is deferred to the Destroy |
| 106 | // event), preserve it. Only synthesize SIGKILL when the container is released without ever |
| 107 | // having produced an exit code (an abrupt teardown of a still-running container). |
| 108 | if (!m_exitedCode.has_value()) |
| 109 | { |
| 110 | m_exitedCode = 128 + WSLCSignalSIGKILL; |
| 111 | } |
| 112 | |
| 113 | m_exitEvent.SetEvent(); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | DockerExecProcessControl::DockerExecProcessControl( |
| 118 | WSLCContainerImpl& Container, const std::string& Id, DockerHTTPClient& DockerClient, DockerEventTracker& EventTracker) : |
| 119 | m_container(&Container), |
| 120 | m_id(Id), |
| 121 | m_client(DockerClient), |
| 122 | m_eventTrackingReference(EventTracker.RegisterExecStateUpdates( |
| 123 | Container.ID(), Id, std::bind(&DockerExecProcessControl::OnEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3))) |
| 124 | { |
| 125 | } |
| 126 | |
| 127 | int DockerExecProcessControl::GetPid() const |
| 128 | { |
| 129 | std::lock_guard lock{m_lock}; |
| 130 | |
| 131 | THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_pid.has_value()); |
| 132 | |
| 133 | return m_pid.value(); |
| 134 | } |
| 135 | |
| 136 | void DockerExecProcessControl::Signal(int Signal) |
| 137 | { |
| 138 | THROW_WIN32(ERROR_NOT_SUPPORTED); |
| 139 | } |
| 140 | |
| 141 | void DockerExecProcessControl::ResizeTty(ULONG Rows, ULONG Columns) |
| 142 | { |
| 143 | std::lock_guard lock{m_lock}; |
| 144 | THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_container == nullptr || m_exitEvent.is_signaled()); |
| 145 | |
| 146 | m_client.ResizeExecTty(m_id, Rows, Columns); |
| 147 | } |
| 148 | |
| 149 | void DockerExecProcessControl::SetPid(int Pid) |
| 150 | { |
| 151 | std::lock_guard lock{m_lock}; |
| 152 | |
| 153 | // Pid must be a real (forked) process. Docker reports Pid=0 in the brief |
| 154 | // window between StartExec returning and runc actually forking the user |
| 155 | // process; treating 0 as a valid PID causes the exec wait to hang forever |
| 156 | // because Docker never emits exec_die for a process that never spawned |
| 157 | // (see PR #40550). Callers must filter Pid > 0 themselves. |
| 158 | WI_ASSERT(Pid > 0); |
| 159 | WI_ASSERT(!m_pid.has_value()); |
| 160 | |
| 161 | m_pid = Pid; |
| 162 | } |
| 163 | |
| 164 | void DockerExecProcessControl::SetExitCode(int ExitCode) |
| 165 | { |
| 166 | std::lock_guard lock{m_lock}; |
| 167 | |
| 168 | if (!m_exitedCode.has_value()) |
| 169 | { |
| 170 | m_exitedCode = ExitCode; |
| 171 | m_exitEvent.SetEvent(); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | void DockerExecProcessControl::OnEvent(ContainerEvent Event, std::optional<int> ExitCode, std::int64_t) |
| 176 | { |
| 177 | if (Event == ContainerEvent::ExecDied && !m_exitEvent.is_signaled()) |
| 178 | { |
| 179 | WI_ASSERT(ExitCode.has_value()); |
| 180 | |
| 181 | SetExitCode(ExitCode.value()); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | void DockerExecProcessControl::OnContainerReleased() noexcept |
| 186 | { |
| 187 | { |
| 188 | std::lock_guard lock{m_lock}; |
| 189 | |
| 190 | WI_ASSERT(m_container != nullptr); |
| 191 | m_container = nullptr; |
| 192 | } |
| 193 | |
| 194 | // N.B. The caller might keep a reference to the process even after the container is released. |
| 195 | // If that happens, make sure that the state tracking can't outlive the session. |
| 196 | // This is safe to call without the lock because removing the tracking reference is protected by the event tracker lock. |
| 197 | |
| 198 | m_eventTrackingReference.Reset(); |
| 199 | |
| 200 | // Signal the exit event to prevent callers being blocked on it. |
| 201 | if (!m_exitEvent.is_signaled()) |
| 202 | { |
| 203 | m_exitedCode = 128 + WSLCSignalSIGKILL; |
| 204 | m_exitEvent.SetEvent(); |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | VMProcessControl::VMProcessControl(WSLCVirtualMachine& VirtualMachine, int Pid, wil::unique_socket&& TtyControl) : |
| 209 | m_pid(Pid), m_ttyControlChannel(std::move(TtyControl), "TtyControl", {VirtualMachine.TerminatingEvent()}), m_vm(&VirtualMachine) |
| 210 | { |
| 211 | } |
| 212 | |
| 213 | VMProcessControl::~VMProcessControl() |
| 214 | { |
| 215 | std::lock_guard lock{m_lock}; |
| 216 | |
| 217 | if (m_vm != nullptr) |
| 218 | { |
| 219 | m_vm->OnProcessReleased(m_pid); |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | void VMProcessControl::Signal(int Signal) |
| 224 | { |
| 225 | std::lock_guard lock{m_lock}; |
| 226 | THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_vm == nullptr || m_exitEvent.is_signaled()); |
| 227 | |
| 228 | m_vm->Signal(m_pid, Signal); |
| 229 | } |
| 230 | |
| 231 | void VMProcessControl::ResizeTty(ULONG Rows, ULONG Columns) |
| 232 | { |
| 233 | std::lock_guard lock{m_lock}; |
| 234 | |
| 235 | THROW_WIN32_IF(ERROR_INVALID_STATE, !m_ttyControlChannel.Connected()); |
| 236 | THROW_HR_IF(E_INVALIDARG, Rows == 0 || Columns == 0 || Rows > USHORT_MAX || Columns > USHORT_MAX); |
| 237 | |
| 238 | WSLC_TERMINAL_CHANGED message{}; |
| 239 | message.Rows = static_cast<unsigned short>(Rows); |
| 240 | message.Columns = static_cast<unsigned short>(Columns); |
| 241 | m_ttyControlChannel.SendMessage(message); |
| 242 | } |
| 243 | |
| 244 | void VMProcessControl::OnExited(int Code) |
| 245 | { |
| 246 | std::lock_guard lock{m_lock}; |
| 247 | |
| 248 | if (!m_exitEvent.is_signaled()) |
| 249 | { |
| 250 | m_exitedCode = Code; |
| 251 | m_ttyControlChannel.Close(); |
| 252 | m_exitEvent.SetEvent(); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | int VMProcessControl::GetPid() const |
| 257 | { |
| 258 | return m_pid; |
| 259 | } |
| 260 | |
| 261 | void VMProcessControl::OnVmTerminated() |
| 262 | { |
| 263 | std::lock_guard lock{m_lock}; |
| 264 | m_vm = nullptr; |
| 265 | |
| 266 | // Make sure that the process is in a terminated state, so users don't think that it might still be running. |
| 267 | if (!m_exitEvent.is_signaled()) |
| 268 | { |
| 269 | m_exitedCode = 128 + WSLCSignalSIGKILL; |
| 270 | m_exitEvent.SetEvent(); |
| 271 | } |
| 272 | } |