1use super::{
4 SessionCommon, StreamingServer,
5 session::{ClientRx, InputEvent, ResizeRx, SessionInput},
6};
7use crate::conmon_capnp::conmon::CgroupManager;
8use crate::{
9 attach::{SharedContainerAttach, StopOutput},
10 channel::Receiver as IOReceiver,
11 child_reaper::{ChildReaper, ExitChannelData, WatchedExec},
12 config::Config,
13 container_io::{ContainerIO, Drain, EarlyStdio, Message as IOMessage, Pipe, SharedContainerIO},
14 exec_start::ExecStart,
15 process::Process,
16 streaming_server::remotecommand::{MESSAGE_TOO_LARGE, Operation, Output, Status},
17 terminal::{Terminal, TerminalResizer},
18};
19use anyhow::{Context, Result, bail};
20use bytes::Bytes;
21use nix::sys::signal::Signal;
22use std::{
23 collections::VecDeque,
24 future::{self, Future},
25 sync::{
26 Arc,
27 atomic::{AtomicBool, Ordering},
28 },
29};
30use tokio::sync::broadcast::{self, error::RecvError};
31use tokio_util::sync::CancellationToken;
32use tracing::debug;
33
34const EARLY_FRAME_SIZE: usize = 32 * 1024;
37
38pub(crate) struct ExecSession {
40 pub(crate) child_reaper: Arc<ChildReaper>,
41
42 pub(crate) tty: bool,
45 pub(crate) server_config: Arc<Config>,
46 pub(crate) cgroup_manager: CgroupManager,
47 pub(crate) command: Vec<String>,
48 pub(crate) common: SessionCommon,
49}
50
51impl StreamingServer {
52 pub(super) async fn start_exec(session: ExecSession, resize: ResizeRx) -> Result<StartedExec> {
56 let SessionCommon {
57 container_id,
58 stdin,
59 stdout,
60 stderr,
61 ..
62 } = session.common;
63 let mut exec = ExecStart::new(
64 &session.server_config,
65 &container_id,
66 session.tty,
67 &session.command,
68 session.cgroup_manager,
69 "exec_streaming",
70 )?;
71
72 exec.collect_stdio(stdout, stderr)
74 .context("collect exec output")?;
75 let stdio = exec
76 .stdio()
77 .context("retrieve stdout and stderr channels")?;
78
79 let resizer = exec.terminal().map(Terminal::resizer);
84 let deadline = ChildReaper::exec_runtime_deadline(None);
87 let create = exec.run(&session.child_reaper, stdin, deadline);
88 let res = Self::resizing(create, resize, resizer).await;
89 let started = match res {
90 Ok(started) => started,
91 Err(e) => {
92 let e = exec.runtime_error(e).await;
93 return Err(e.context("create new child process"));
94 }
95 };
96
97 let WatchedExec {
98 io,
99 token,
100 process,
101 exit_rx,
102 stop_output,
103 } = exec
104 .watch(&session.child_reaper, container_id, started, None)
105 .context("watch exec process")?;
106 let teardown = ExecTeardown {
107 stdio,
108 _stop_output: stop_output,
109 attach: io.attach().clone(),
110 token,
111 process,
112 kill: Arc::default(),
113 };
114 let early_stdio = io.take_early_stdio().await;
115 Ok(StartedExec {
116 io,
117 early_stdio,
118 exit_rx,
119 stdin_enabled: stdin,
120 stdout_enabled: stdout,
121 stderr_enabled: stderr,
122 teardown,
123 })
124 }
125
126 pub(super) async fn exec_loop(
129 started: StartedExec,
130 sender: &mut Output,
131 client: ClientRx,
132 status: &mut Option<Status>,
133 ) -> Result<()> {
134 let StartedExec {
135 io,
136 early_stdio,
137 mut exit_rx,
138 stdin_enabled,
139 stdout_enabled,
140 stderr_enabled,
141 teardown,
142 } = started;
143 let (stdout_rx, stderr_rx) = teardown.stdio.clone();
144 let mut input = SessionInput::new(io, teardown.attach.clone(), client, stdin_enabled);
145
146 let mut stdout_done = !stdout_enabled;
150 let mut stderr_done = !stderr_enabled;
151 if stdout_done {
152 stdout_rx.close();
153 }
154 if stderr_done {
155 stderr_rx.close();
156 }
157
158 let truncated = early_stdio.stdout.truncated || early_stdio.stderr.truncated;
163 let (mut early_stdout, mut early_stderr) = (VecDeque::new(), VecDeque::new());
164 for (early, rx, frames, done) in [
165 (
166 early_stdio.stdout,
167 &stdout_rx,
168 &mut early_stdout,
169 &mut stdout_done,
170 ),
171 (
172 early_stdio.stderr,
173 &stderr_rx,
174 &mut early_stderr,
175 &mut stderr_done,
176 ),
177 ] {
178 if *done {
179 continue;
180 }
181 *frames = Self::early_frames(early.data);
182 if early.done || early.truncated {
183 *done = true;
184 rx.close();
185 }
186 }
187
188 loop {
189 tokio::select! {
190 () = future::ready(()), if !early_stdout.is_empty() || !early_stderr.is_empty() => {
191 for (frames, pipe) in [(&mut early_stdout, Pipe::StdOut), (&mut early_stderr, Pipe::StdErr)] {
192 if let Some(frame) = frames.pop_front() {
193 sender.data(pipe, &frame).await?;
194 }
195 }
196 },
197
198 res = stdout_rx.recv(), if !stdout_done && early_stdout.is_empty() => match res {
199 Ok(IOMessage::Data(data, _)) => {
200 sender.data(Pipe::StdOut, &data).await?;
201 }
202 Ok(IOMessage::Done) | Err(_) => stdout_done = true,
203 },
204
205 res = stderr_rx.recv(), if !stderr_done && early_stderr.is_empty() => match res {
206 Ok(IOMessage::Data(data, _)) => {
207 sender.data(Pipe::StdErr, &data).await?;
208 }
209 Ok(IOMessage::Done) | Err(_) => stderr_done = true,
210 },
211
212 event = input.next() => match event {
213 InputEvent::Resize(data) => input.resize(&data),
214 InputEvent::Ping => sender.ping().await?,
215 InputEvent::StdinClosed => input.close_container_stdin(),
218 InputEvent::Ended => break,
221 InputEvent::TooLarge => bail!(MESSAGE_TOO_LARGE),
222 },
223
224 res = exit_rx.recv() => {
225 if res.is_err() {
228 teardown.kill.store(true, Ordering::Release);
229 }
230 *status = Some(Self::exit_status(res, truncated));
231 for (frames, pipe) in [(&mut early_stdout, Pipe::StdOut), (&mut early_stderr, Pipe::StdErr)] {
233 for frame in frames.drain(..) {
234 sender.data(pipe, &frame).await?;
235 }
236 }
237 if !stdout_done {
238 Self::drain_stdio(&stdout_rx, Pipe::StdOut, sender).await?;
239 }
240 if !stderr_done {
241 Self::drain_stdio(&stderr_rx, Pipe::StdErr, sender).await?;
242 }
243 break
244 },
245 }
246 }
247
248 Ok(())
249 }
250
251 fn exit_status(res: Result<ExitChannelData, RecvError>, truncated: bool) -> Status {
259 match res {
260 Ok(exit_data) if truncated => Status::failed(
261 Operation::Exec,
262 format_args!(
263 "the command wrote more than {} MiB while the runtime started it, \
264 the rest of it got dropped (exit code {})",
265 ContainerIO::MAX_STDIO_STREAM_SIZE / (1024 * 1024),
266 exit_data.exit_code,
267 ),
268 ),
269 Ok(exit_data) => Status::exited(exit_data.exit_code),
270 Err(e) => Status::failed(
271 Operation::Exec,
272 format_args!("unable to get the exit of the command: {e}"),
273 ),
274 }
275 }
276
277 async fn resizing<F: Future>(
282 run: F,
283 mut resize: ResizeRx,
284 resizer: Option<TerminalResizer>,
285 ) -> F::Output {
286 let Some(resizer) = resizer else {
287 return run.await;
288 };
289 tokio::pin!(run);
290 loop {
291 tokio::select! {
292 output = &mut run => return output,
293 Ok(()) = resize.changed() => {
295 if let Some(data) = resize.borrow_and_update().clone() {
296 SessionInput::resize_with(&data, |width, height| {
297 resizer.resize(width, height).map(|()| true)
298 });
299 }
300 }
301 }
302 }
303 }
304
305 fn early_frames(data: Vec<u8>) -> VecDeque<Bytes> {
308 let mut data = Bytes::from(data);
309 let mut frames = VecDeque::with_capacity(data.len().div_ceil(EARLY_FRAME_SIZE));
310 while !data.is_empty() {
311 frames.push_back(data.split_to(data.len().min(EARLY_FRAME_SIZE)));
312 }
313 frames
314 }
315
316 async fn drain_stdio(
320 rx: &IOReceiver<IOMessage>,
321 pipe: Pipe,
322 sender: &mut Output,
323 ) -> Result<()> {
324 let mut drain = Drain::default();
325 while let Some(message) = drain.recv_stdio(rx).await {
326 if let IOMessage::Data(data, _) = message {
327 sender.data(pipe, &data).await?;
328 }
329 }
330 Ok(())
331 }
332}
333
334pub(super) struct StartedExec {
336 io: SharedContainerIO,
337
338 early_stdio: EarlyStdio,
340 exit_rx: broadcast::Receiver<ExitChannelData>,
341 stdin_enabled: bool,
342 stdout_enabled: bool,
343 stderr_enabled: bool,
344 pub(super) teardown: ExecTeardown,
345}
346
347pub(super) struct ExecTeardown {
356 stdio: (IOReceiver<IOMessage>, IOReceiver<IOMessage>),
358 attach: SharedContainerAttach,
359
360 token: CancellationToken,
362
363 process: Arc<Process>,
366
367 pub(super) kill: Arc<AtomicBool>,
374
375 _stop_output: StopOutput,
378}
379
380impl Drop for ExecTeardown {
381 fn drop(&mut self) {
382 let (stdout_rx, stderr_rx) = &self.stdio;
385 stdout_rx.close();
386 stderr_rx.close();
387
388 if self.token.is_cancelled() {
389 return;
390 }
391 if self.kill.load(Ordering::Acquire) {
392 let pid = self.process.pid();
393 debug!("Killing exec process {pid}, its session ended");
394 if let Err(e) = self.process.signal_group(Signal::SIGKILL) {
395 debug!("Not killing exec process {pid}, it is gone: {e}");
396 }
397 self.attach.stop_output();
400 } else if self.attach.close_container_stdin() {
401 self.attach.stop_output();
408 } else {
409 self.hang_up();
415 }
416 }
417}
418
419impl ExecTeardown {
420 fn hang_up(&self) {
425 let pid = self.process.pid();
426 debug!("Hanging up the terminal of exec process {pid}");
427 if let Err(e) = self.process.signal_group(Signal::SIGHUP) {
428 debug!("Not hanging up exec process {pid}, it is gone: {e}");
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests;