soma_infra/
host_exec_policy.rs1use std::path::{Path, PathBuf};
2
3#[cfg(any(feature = "process-driver", test))]
4use crate::HostExecRequest;
5use crate::{FileReadPolicy, InfraError, InfraResult};
6
7const MAX_EXEC_ROOTS: usize = 32;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct HostExecPolicy {
12 files: FileReadPolicy,
13}
14
15impl HostExecPolicy {
16 pub fn new<I, P>(roots: I) -> InfraResult<Self>
18 where
19 I: IntoIterator<Item = P>,
20 P: Into<PathBuf>,
21 {
22 let roots = roots.into_iter().map(Into::into).collect::<Vec<_>>();
23 if roots.is_empty() || roots.len() > MAX_EXEC_ROOTS {
24 return Err(invalid(format!(
25 "host execution requires 1-{MAX_EXEC_ROOTS} read roots"
26 )));
27 }
28 Ok(Self {
29 files: FileReadPolicy::new(roots)?,
30 })
31 }
32
33 pub fn roots(&self) -> impl Iterator<Item = &Path> {
35 self.files.roots()
36 }
37
38 #[cfg(any(feature = "process-driver", test))]
39 pub(crate) fn launcher_plan(&self, request: &HostExecRequest) -> InfraResult<LauncherPlan> {
40 let path_indices =
41 crate::host_exec_argv::filesystem_operand_indices(request.command(), request.args())?;
42 for index in &path_indices {
43 let path = validate_operand_path(&request.args()[*index])?;
44 self.files.resolve(path)?;
45 }
46 if let Some(path) = request.working_dir() {
47 self.files.resolve(path)?;
48 }
49 let roots = self
50 .files
51 .roots()
52 .map(|root| root.to_string_lossy().into_owned())
53 .collect();
54 Ok(LauncherPlan {
55 path_indices,
56 roots,
57 working_dir: request
58 .working_dir()
59 .map(|path| path.to_string_lossy().into_owned()),
60 })
61 }
62}
63
64#[cfg(any(feature = "process-driver", test))]
65pub(crate) struct LauncherPlan {
66 pub(crate) path_indices: Vec<usize>,
67 pub(crate) roots: Vec<String>,
68 pub(crate) working_dir: Option<String>,
69}
70
71#[cfg(any(feature = "process-driver", test))]
72fn validate_operand_path(value: &str) -> InfraResult<&Path> {
73 let path = Path::new(value);
74 if !path.is_absolute()
75 || path.components().any(|part| {
76 matches!(
77 part,
78 std::path::Component::ParentDir | std::path::Component::CurDir
79 )
80 })
81 || value.chars().any(char::is_control)
82 {
83 Err(invalid(format!(
84 "filesystem command operands must be absolute and normalized: {value:?}"
85 )))
86 } else {
87 Ok(path)
88 }
89}
90
91fn invalid(message: impl Into<String>) -> InfraError {
92 InfraError::InvalidRequest {
93 domain: "host-exec",
94 message: message.into(),
95 }
96}
97
98#[cfg(test)]
99#[path = "host_exec_policy_tests.rs"]
100mod tests;