Skip to main content

libcontainer/workload/
default.rs

1use std::ffi::CString;
2use std::path::{Path, PathBuf};
3
4use nix::unistd;
5use oci_spec::runtime::Spec;
6
7use super::{Executor, ExecutorError, ExecutorValidationError};
8
9#[derive(Clone)]
10pub struct DefaultExecutor {}
11
12impl Executor for DefaultExecutor {
13    fn exec(&self, spec: &Spec) -> Result<(), ExecutorError> {
14        tracing::debug!("executing workload with default handler");
15        let args = spec
16            .process()
17            .as_ref()
18            .and_then(|p| p.args().as_ref())
19            .ok_or_else(|| {
20                tracing::error!("no arguments provided to execute");
21                ExecutorError::InvalidArg
22            })?;
23
24        let executable = args[0].as_str();
25        let cstring_path = CString::new(executable.as_bytes()).map_err(|err| {
26            tracing::error!("failed to convert path {executable:?} to cstring: {}", err,);
27            ExecutorError::InvalidArg
28        })?;
29        let a: Vec<CString> = args
30            .iter()
31            .map(|s| CString::new(s.as_bytes()).unwrap_or_default())
32            .collect();
33        unistd::execvp(&cstring_path, &a).map_err(|err| {
34            tracing::error!(?err, filename = ?cstring_path, args = ?a, "failed to execvp");
35            ExecutorError::Execution(
36                format!(
37                    "error '{}' executing '{:?}' with args '{:?}'",
38                    err, cstring_path, a
39                )
40                .into(),
41            )
42        })?;
43
44        // After execvp is called, the process is replaced with the container
45        // payload through execvp, so it should never reach here.
46        unreachable!();
47    }
48
49    fn validate(&self, spec: &Spec) -> Result<(), ExecutorValidationError> {
50        let proc = spec
51            .process()
52            .as_ref()
53            .ok_or(ExecutorValidationError::ArgValidationError(
54                "spec did not contain process".into(),
55            ))?;
56
57        if let Some(args) = proc.args() {
58            let envs: Vec<String> = proc.env().as_ref().unwrap_or(&vec![]).clone();
59            let path_vars: Vec<&String> = envs.iter().filter(|&e| e.starts_with("PATH=")).collect();
60            if path_vars.is_empty() {
61                tracing::error!("PATH environment variable is not set");
62                Err(ExecutorValidationError::ArgValidationError(
63                    "PATH environment variable is not set".into(),
64                ))?;
65            }
66            let path_var = path_vars[0].trim_start_matches("PATH=");
67            match get_executable_path(&args[0], path_var) {
68                None => {
69                    tracing::error!(
70                        executable = ?args[0],
71                        "executable for container process not found in PATH",
72                    );
73                    Err(ExecutorValidationError::ArgValidationError(format!(
74                        "executable '{}' not found in $PATH",
75                        args[0]
76                    )))?;
77                }
78                Some(path) => match is_executable(&path) {
79                    Ok(true) => {
80                        tracing::debug!(executable = ?path, "found executable in executor");
81                    }
82                    Ok(false) => {
83                        tracing::error!(
84                            executable = ?path,
85                            "executable does not have the correct permission set",
86                        );
87                        Err(ExecutorValidationError::ArgValidationError(format!(
88                            "executable '{}' at path '{:?}' does not have correct permissions",
89                            args[0], path
90                        )))?;
91                    }
92                    Err(err) => {
93                        tracing::error!(
94                            executable = ?path,
95                            ?err,
96                            "failed to check permissions for executable",
97                        );
98                        Err(ExecutorValidationError::ArgValidationError(format!(
99                            "failed to check permissions for executable '{}' at path '{:?}' : {}",
100                            args[0], path, err
101                        )))?;
102                    }
103                },
104            }
105        }
106
107        Ok(())
108    }
109}
110
111pub fn get_executor() -> Box<dyn Executor> {
112    Box::new(DefaultExecutor {})
113}
114
115fn get_executable_path(name: &str, path_var: &str) -> Option<PathBuf> {
116    // if path has / in it, we have to assume absolute path, as per runc impl
117    if name.contains('/') && PathBuf::from(name).exists() {
118        return Some(PathBuf::from(name));
119    }
120    for path in path_var.split(':') {
121        let potential_path = PathBuf::from(path).join(name);
122        if potential_path.exists() {
123            return Some(potential_path);
124        }
125    }
126    None
127}
128
129fn is_executable(path: &Path) -> std::result::Result<bool, std::io::Error> {
130    use std::os::unix::fs::PermissionsExt;
131    let metadata = path.metadata()?;
132    let permissions = metadata.permissions();
133    // we have to check if the path is file and the execute bit
134    // is set. In case of directories, the execute bit is also set,
135    // so have to check if this is a file or not
136    Ok(metadata.is_file() && permissions.mode() & 0o001 != 0)
137}
138
139#[cfg(test)]
140mod tests {
141    use std::collections::HashMap;
142    use std::env;
143
144    use serial_test::serial;
145
146    use super::*;
147
148    #[test]
149    fn test_get_executable_path() {
150        let non_existing_abs_path = "/some/non/existent/absolute/path";
151        let existing_abs_path = "/usr/bin/sh";
152        let existing_binary = "sh";
153        let non_existing_binary = "non-existent";
154        let path_value = "/usr/bin:/bin";
155
156        assert_eq!(
157            get_executable_path(existing_abs_path, path_value),
158            Some(PathBuf::from(existing_abs_path))
159        );
160        assert_eq!(get_executable_path(non_existing_abs_path, path_value), None);
161
162        assert_eq!(
163            get_executable_path(existing_binary, path_value),
164            Some(PathBuf::from("/usr/bin/sh"))
165        );
166
167        assert_eq!(get_executable_path(non_existing_binary, path_value), None);
168    }
169
170    #[test]
171    fn test_is_executable() {
172        let tmp = tempfile::tempdir().expect("create temp directory for test");
173        let executable_path = PathBuf::from("/bin/sh");
174        let directory_path = tmp.path();
175        let non_executable_path = directory_path.join("non_executable_file");
176        let non_existent_path = PathBuf::from("/some/non/existent/path");
177
178        std::fs::File::create(non_executable_path.as_path()).unwrap();
179
180        assert!(is_executable(&non_existent_path).is_err());
181        assert!(is_executable(&executable_path).unwrap());
182        assert!(!is_executable(&non_executable_path).unwrap());
183        assert!(!is_executable(directory_path).unwrap());
184    }
185
186    #[test]
187    #[serial]
188    fn test_executor_set_envs() {
189        // Store original environment variables to restore later
190        let original_envs: HashMap<String, String> = env::vars().collect();
191
192        // Test setting environment variables
193        {
194            let executor = get_executor();
195            let envs = HashMap::from([
196                ("FOO".to_owned(), "hoge".to_owned()),
197                ("BAR".to_owned(), "fuga".to_owned()),
198                ("BAZ".to_owned(), "piyo".to_owned()),
199            ]);
200            assert!(executor.setup_envs(envs).is_ok());
201
202            // Check if the environment variables are set correctly
203            let current_envs = std::env::vars().collect::<HashMap<String, String>>();
204            assert_eq!(current_envs.get("FOO").unwrap(), "hoge");
205            assert_eq!(current_envs.get("BAR").unwrap(), "fuga");
206            assert_eq!(current_envs.get("BAZ").unwrap(), "piyo");
207            // No other environment variables should be set
208            assert_eq!(current_envs.len(), 3);
209        }
210
211        // Restore original environment variables
212        original_envs.iter().for_each(|(key, value)| {
213            unsafe { env::set_var(key, value) };
214        });
215    }
216}