Skip to main content

conmonrs/
runtime_args.rs

1//! The command line of the OCI runtime for a create, a restore or an exec.
2
3use crate::{
4    config::{Config, LogLevel},
5    conmon_capnp::conmon::CgroupManager,
6    container_io::ContainerIO,
7};
8use anyhow::{Result, bail};
9use std::{fmt, path::Path};
10use tracing::debug;
11
12/// Check a container ID, which the runtimes take as a positional argument.
13/// One which starts with a dash would be taken for an option. The spans of
14/// the requests show the ID in every log line about the container, one with
15/// control characters, like a line break or an escape sequence, could forge
16/// lines there. The requests check it before they set anything up.
17pub(crate) fn check_id(id: &str) -> Result<()> {
18    if id.is_empty() || id.starts_with('-') || id.chars().any(char::is_control) {
19        bail!("invalid container ID {id:?}")
20    }
21    Ok(())
22}
23
24/// What the command line of the OCI runtime gets generated from.
25#[derive(Clone, Copy)]
26pub(crate) struct GenerateRuntimeArgs<'a> {
27    pub(crate) config: &'a Config,
28    pub(crate) id: &'a str,
29    pub(crate) container_io: &'a ContainerIO,
30    pub(crate) pidfile: &'a Path,
31    pub(crate) cgroup_manager: CgroupManager,
32}
33
34impl GenerateRuntimeArgs<'_> {
35    const SYSTEMD_CGROUP_ARG: &'static str = "--systemd-cgroup";
36    const LOG_LEVEL_FLAG_CRUN: &'static str = "--log-level";
37
38    /// Generate the OCI runtime CLI arguments of a create, or of a restore
39    /// from the checkpoint at `restore_path`, like conmon with `--restore`:
40    /// the `restore` command takes the place of `create`, with `--detach`,
41    /// as the container runs once it got restored, the checkpoint as
42    /// `--image-path` and the bundle as `--work-path`, where CRIU runs and
43    /// writes its logs. The bundle and the PID file stay the same, and so do
44    /// the `command_args`, which carry the options of the restore, like the
45    /// `--runtime-opt` of conmon.
46    pub(crate) fn create_args(
47        &self,
48        bundle_path: &Path,
49        restore_path: Option<&Path>,
50        command_args: impl IntoIterator<Item = impl AsRef<str>>,
51    ) -> Result<Vec<String>> {
52        let mut args = Vec::with_capacity(16);
53        args.extend(self.default_args());
54
55        args.push(if restore_path.is_some() {
56            "restore".into()
57        } else {
58            "create".into()
59        });
60        args.push("--bundle".into());
61        args.push(bundle_path.display().to_string());
62        args.push("--pid-file".into());
63        args.push(self.pidfile.display().to_string());
64
65        if let Some(restore_path) = restore_path {
66            args.push("--detach".into());
67            args.push("--image-path".into());
68            args.push(restore_path.display().to_string());
69            args.push("--work-path".into());
70            args.push(bundle_path.display().to_string());
71        }
72
73        args.extend(command_args.into_iter().map(|arg| arg.as_ref().into()));
74
75        if let Some(terminal) = self.container_io.terminal() {
76            args.push(format!("--console-socket={}", terminal.path().display()));
77        }
78
79        args.push(self.checked_id()?.into());
80
81        debug!("Runtime args {args:?}");
82        Ok(args)
83    }
84
85    /// Generate the OCI runtime CLI arguments of an exec, of an exec_sync or
86    /// a streaming one alike. They are not logged here, the command may carry
87    /// secrets, see [`LoggedArgs`].
88    pub(crate) fn exec_args(
89        &self,
90        command: impl IntoIterator<Item = impl AsRef<str>>,
91    ) -> Result<ExecArgs> {
92        let mut args = Vec::with_capacity(12);
93        args.extend(self.default_args());
94
95        args.push("exec".into());
96        args.push("-d".into());
97
98        if let Some(terminal) = self.container_io.terminal() {
99            args.push(format!("--console-socket={}", terminal.path().display()));
100            args.push("--tty".into());
101        }
102
103        args.push(format!("--pid-file={}", self.pidfile.display()));
104        // Ends the options of the runtime: the ID and the command follow as
105        // positional arguments, also a command which starts with a dash,
106        // which a runtime would take for an option of its own otherwise.
107        args.push("--".into());
108        args.push(self.checked_id()?.into());
109        let command_start = args.len();
110        args.extend(command.into_iter().map(|arg| arg.as_ref().into()));
111
112        Ok(ExecArgs {
113            args,
114            command_start,
115        })
116    }
117
118    /// The container ID, which the runtimes take as a positional argument,
119    /// see [`check_id`].
120    fn checked_id(&self) -> Result<&str> {
121        check_id(self.id)?;
122        Ok(self.id)
123    }
124
125    /// Build the default arguments for any provided runtime.
126    fn default_args(&self) -> Vec<String> {
127        let mut args = vec![];
128
129        if self.config.runtime_is_crun() {
130            args.push(format!("--log=journald:{}", self.id));
131
132            // crun takes no trace or info level, trace gets the most output
133            // it has.
134            let level = match self.config.log_level() {
135                LogLevel::Trace | LogLevel::Debug => Some("debug"),
136                LogLevel::Warn => Some("warning"),
137                LogLevel::Error => Some("error"),
138                LogLevel::Info | LogLevel::Off => None,
139            };
140            if let Some(level) = level {
141                args.push(format!("{}={level}", Self::LOG_LEVEL_FLAG_CRUN));
142            }
143        }
144
145        if let Some(rr) = self.config.runtime_root() {
146            args.push(format!("--root={}", rr.display()));
147        }
148
149        if self.cgroup_manager == CgroupManager::Systemd {
150            args.push(Self::SYSTEMD_CGROUP_ARG.into());
151        }
152
153        args
154    }
155}
156
157/// The arguments of an exec, which end with the command of the user.
158pub(crate) struct ExecArgs {
159    pub(crate) args: Vec<String>,
160
161    /// Where the command starts in `args`, see [`LoggedArgs`].
162    pub(crate) command_start: usize,
163}
164
165/// The arguments of a runtime call as they get logged. Those of an exec end
166/// with the command of the user, which may carry secrets, so only the number
167/// of its arguments gets logged.
168pub(crate) struct LoggedArgs<'a> {
169    pub(crate) args: &'a [String],
170
171    /// Where the command of an exec starts in `args`.
172    pub(crate) command_start: Option<usize>,
173}
174
175impl fmt::Debug for LoggedArgs<'_> {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match self.command_start {
178            Some(start) => {
179                let (args, command) = self.args.split_at(start.min(self.args.len()));
180                write!(f, "{args:?} with {} command args", command.len())
181            }
182            None => write!(f, "{:?}", self.args),
183        }
184    }
185}
186
187#[cfg(test)]
188mod tests;