Skip to main content

conmonrs/
exec_start.rs

1//! Starting an exec, of an exec_sync or a streaming one alike: the IO of the
2//! process, the PID file of its runtime and the runtime call, up to the
3//! registration of the process.
4
5use crate::{
6    channel::Receiver,
7    child_reaper::{ChildReaper, RuntimeCall, StartedChild, WatchedExec},
8    config::Config,
9    conmon_capnp::conmon::CgroupManager,
10    container_io::{ContainerIO, Message},
11    container_log::ContainerLog,
12    fs_util::TempFile,
13    runtime_args::{ExecArgs, GenerateRuntimeArgs},
14    terminal::Terminal,
15};
16use anyhow::{Context, Result};
17use std::path::PathBuf;
18use tokio::time::Instant;
19
20/// An exec while it gets started. An exec_sync and a streaming exec
21/// set it up the same way, they differ in how they wait for the runtime and
22/// take the output, see [`ExecStart::run`] and [`ExecStart::watch`].
23pub(crate) struct ExecStart {
24    /// The IO of the process, with the console socket of a terminal.
25    container_io: ContainerIO,
26
27    /// Where the runtime writes the PID of the process. Removed once the
28    /// runtime wrote the PID, see [`ExecStart::watch`], or with a start which
29    /// failed.
30    pidfile: TempFile,
31    args: ExecArgs,
32    runtime: PathBuf,
33}
34
35impl ExecStart {
36    /// Set up an exec of `command` in the container `id`, with a terminal if
37    /// `tty`. The PID file in the runtime directory starts with
38    /// `pidfile_prefix`, which tells the kind of exec apart.
39    pub(crate) fn new(
40        config: &Config,
41        id: &str,
42        tty: bool,
43        command: &[String],
44        cgroup_manager: CgroupManager,
45        pidfile_prefix: &str,
46    ) -> Result<Self> {
47        let container_io =
48            ContainerIO::new(tty, ContainerLog::new()).context("create container IO")?;
49        let pidfile = TempFile::new(Some(config.runtime_dir()), pidfile_prefix, ".pid");
50        let args = GenerateRuntimeArgs {
51            config,
52            id,
53            container_io: &container_io,
54            pidfile: pidfile.path(),
55            cgroup_manager,
56        }
57        .exec_args(command)
58        .context("generate exec args")?;
59        Ok(Self {
60            container_io,
61            pidfile,
62            args,
63            runtime: config.runtime().to_path_buf(),
64        })
65    }
66
67    /// Read the output while the runtime runs, see
68    /// [`ContainerIO::collect_stdio`].
69    pub(crate) fn collect_stdio(&self, stdout: bool, stderr: bool) -> Result<()> {
70        self.container_io.collect_stdio(stdout, stderr)
71    }
72
73    /// The stdout and stderr channels of the process, see
74    /// [`ContainerIO::stdio`].
75    pub(crate) fn stdio(&self) -> Result<(Receiver<Message>, Receiver<Message>)> {
76        self.container_io.stdio()
77    }
78
79    /// The terminal of the process, none with pipes, see
80    /// [`ContainerIO::terminal`].
81    pub(crate) fn terminal(&self) -> Option<&Terminal> {
82        self.container_io.terminal()
83    }
84
85    /// Run the runtime, with `stdin` for the process, and kill it at the
86    /// `deadline` if it hangs, see [`ChildReaper::create_child`]. The caller
87    /// reports a failure with [`ExecStart::runtime_error`].
88    pub(crate) async fn run(
89        &mut self,
90        reaper: &ChildReaper,
91        stdin: bool,
92        deadline: Instant,
93    ) -> Result<StartedChild> {
94        let call = RuntimeCall {
95            command_start: Some(self.args.command_start),
96            stdin,
97            ..RuntimeCall::new(
98                &self.runtime,
99                &self.args.args,
100                self.pidfile.path(),
101                deadline,
102            )
103        };
104        reaper.create_child(call, &mut self.container_io).await
105    }
106
107    /// The error of a runtime which failed to start the process, with its
108    /// stderr, see [`ContainerIO::runtime_error`].
109    pub(crate) async fn runtime_error(&self, e: anyhow::Error) -> anyhow::Error {
110        self.container_io.runtime_error(e).await
111    }
112
113    /// Register the process the runtime `started` under the `id` of its
114    /// container, the process gets killed at `timeout`, see
115    /// [`ChildReaper::watch_exec`].
116    pub(crate) fn watch(
117        self,
118        reaper: &ChildReaper,
119        id: Box<str>,
120        started: StartedChild,
121        timeout: Option<Instant>,
122    ) -> Result<WatchedExec> {
123        // The runtime wrote the PID, the file served its purpose.
124        drop(self.pidfile);
125        reaper.watch_exec(id, started, timeout, self.container_io)
126    }
127}