libcontainer/workload/
default.rs1use 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 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 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 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 let original_envs: HashMap<String, String> = env::vars().collect();
191
192 {
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 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 assert_eq!(current_envs.len(), 3);
209 }
210
211 original_envs.iter().for_each(|(key, value)| {
213 unsafe { env::set_var(key, value) };
214 });
215 }
216}