Skip to main content

liboci_cli/
exec.rs

1use std::error::Error;
2use std::path::PathBuf;
3
4use clap::Args;
5
6/// Execute a process within an existing container
7/// Reference: https://github.com/opencontainers/runc/blob/main/man/runc-exec.8.md
8#[derive(Args, Debug)]
9pub struct Exec {
10    /// Unix socket (file) path , which will receive file descriptor of the writing end of the pseudoterminal
11    #[arg(long)]
12    pub console_socket: Option<PathBuf>,
13    #[arg(long)]
14    /// Current working directory of the container
15    pub cwd: Option<PathBuf>,
16    /// Environment variables that should be set in the container
17    #[arg(short, long, value_parser = parse_env::<String, String>, num_args = 1)]
18    pub env: Vec<(String, String)>,
19    /// Allocate a pseudo-TTY for the process
20    #[arg(short, long)]
21    pub tty: bool,
22    /// Run the command as a user
23    #[arg(short, long, value_parser = parse_user::<u32, u32>)]
24    pub user: Option<(u32, Option<u32>)>,
25    /// Add additional group IDs. Can be specified multiple times
26    #[arg(long, short = 'g', num_args = 1)]
27    pub additional_gids: Vec<u32>,
28    /// Path to process.json
29    #[arg(short, long)]
30    pub process: Option<PathBuf>,
31    /// Detach from the container process
32    #[arg(short, long)]
33    pub detach: bool,
34    #[arg(long)]
35    /// The file to which the pid of the container process should be written to
36    pub pid_file: Option<PathBuf>,
37    /// Set the asm process label for the process commonly used with selinux
38    #[arg(long)]
39    pub process_label: Option<String>,
40    /// Set the apparmor profile for the process
41    #[arg(long)]
42    pub apparmor: Option<String>,
43    /// Prevent the process from gaining additional privileges
44    #[arg(long)]
45    pub no_new_privs: bool,
46    /// Add a capability to the bounding set for the process
47    #[arg(long, num_args = 1)]
48    pub cap: Vec<String>,
49    /// Pass N additional file descriptors to the container
50    #[arg(long, default_value = "0")]
51    pub preserve_fds: i32,
52    /// Allow exec in a paused container
53    #[arg(long)]
54    pub ignore_paused: bool,
55    /// Execute a process in a sub-cgroup
56    #[arg(long)]
57    pub cgroup: Option<String>,
58    /// Container identifier
59    #[arg(value_parser = clap::builder::NonEmptyStringValueParser::new(), required = true)]
60    pub container_id: String,
61    /// Command that should be executed in the container
62    #[arg(required = false, trailing_var_arg = true)]
63    pub command: Vec<String>,
64}
65
66fn parse_env<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
67where
68    T: std::str::FromStr,
69    T::Err: Error + Send + Sync + 'static,
70    U: std::str::FromStr,
71    U::Err: Error + Send + Sync + 'static,
72{
73    let pos = s
74        .find('=')
75        .ok_or_else(|| format!("invalid VAR=value: no `=` found in `{s}`"))?;
76    Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
77}
78
79fn parse_user<T, U>(s: &str) -> Result<(T, Option<U>), Box<dyn Error + Send + Sync + 'static>>
80where
81    T: std::str::FromStr,
82    T::Err: Error + Send + Sync + 'static,
83    U: std::str::FromStr,
84    U::Err: Error + Send + Sync + 'static,
85{
86    if let Some(pos) = s.find(':') {
87        Ok((s[..pos].parse()?, Some(s[pos + 1..].parse()?)))
88    } else {
89        Ok((s.parse()?, None))
90    }
91}