evalbox_sandbox/
resolve.rs1use std::path::{Path, PathBuf};
4
5use thiserror::Error;
6
7use crate::plan::Mount;
8use crate::sysinfo::{SystemPaths, SystemType, SYSTEM_PATHS};
9
10#[derive(Debug, Clone)]
11pub struct ResolvedBinary {
12 pub path: PathBuf,
13 pub required_mounts: Vec<Mount>,
14}
15
16#[derive(Debug, Error)]
17pub enum ResolveError {
18 #[error("command not found: {0}")]
19 NotFound(String),
20}
21
22pub fn resolve_binary(cmd: &str) -> Result<ResolvedBinary, ResolveError> {
24 let path = if cmd.starts_with('/') {
25 let p = PathBuf::from(cmd);
26 if !p.exists() && !cmd.starts_with("/work/") {
27 return Err(ResolveError::NotFound(cmd.to_string()));
28 }
29 p
30 } else {
31 which::which(cmd).map_err(|_| ResolveError::NotFound(cmd.to_string()))?
32 };
33
34 let sys_paths = &*SYSTEM_PATHS;
35 let required_mounts = detect_mounts(&path, sys_paths);
36
37 Ok(ResolvedBinary { path, required_mounts })
38}
39
40fn detect_mounts(binary: &Path, sys_paths: &SystemPaths) -> Vec<Mount> {
41 let path_str = binary.to_string_lossy();
42 let mut mounts = Vec::new();
43
44 for mount_path in &sys_paths.readonly_mounts {
45 mounts.push(Mount::ro(mount_path));
46 }
47
48 if sys_paths.system_type == SystemType::Fhs {
49 if path_str.starts_with("/usr") {
50 add_if_missing(&mut mounts, "/usr");
51 } else if path_str.starts_with("/bin") || path_str.starts_with("/sbin") {
52 if Path::new("/bin").is_symlink() {
53 add_if_missing(&mut mounts, "/usr");
54 } else {
55 add_if_missing(&mut mounts, "/bin");
56 }
57 }
58 }
59
60 mounts
61}
62
63fn add_if_missing(mounts: &mut Vec<Mount>, path: &str) {
64 let path_buf = PathBuf::from(path);
65 if !mounts.iter().any(|m| m.source == path_buf) && path_buf.exists() {
66 mounts.push(Mount::ro(path_buf));
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn resolve_echo() {
76 let resolved = resolve_binary("echo").unwrap();
77 assert!(resolved.path.exists());
78 assert!(resolved.path.is_absolute());
79 }
80
81 #[test]
82 fn resolve_nonexistent() {
83 assert!(resolve_binary("nonexistent_binary_12345").is_err());
84 }
85
86 #[test]
87 fn detect_nix_mounts() {
88 let sys_paths = &*SYSTEM_PATHS;
89 let mounts = detect_mounts(Path::new("/nix/store/abc123/bin/echo"), sys_paths);
90
91 if sys_paths.system_type == SystemType::NixOS {
92 assert!(mounts.iter().any(|m| m.source == Path::new("/nix/store")));
93 }
94 }
95
96 #[test]
97 fn detect_fhs_mounts() {
98 let sys_paths = &*SYSTEM_PATHS;
99 let mounts = detect_mounts(Path::new("/usr/bin/echo"), sys_paths);
100
101 if sys_paths.system_type == SystemType::Fhs {
102 assert!(mounts.iter().any(|m| m.source == Path::new("/usr")));
103 }
104 }
105
106 #[test]
107 fn resolve_has_system_mounts() {
108 let resolved = resolve_binary("sh").unwrap();
109 assert!(!resolved.required_mounts.is_empty());
110 }
111}