master
cpp 272 lines 7.44 KB
Raw
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 }