Skip to main content

sandbox_run/
command.rs

1use std::{
2    ffi::OsStr,
3    io,
4    path::Path,
5    process::{Child, ExitStatus, Output, Stdio},
6};
7
8use crate::{platform, sandbox::SandboxPolicy};
9
10/// A sandboxed process builder, providing fine-grained control over how a new
11/// process should be spawned.
12///
13/// This mirrors the API of `std::process::Command` but adds the ability to
14/// apply a sandbox policy that restricts filesystem access.
15///
16/// # Example
17///
18/// ```no_run
19/// use std::path::Path;
20/// use sandbox_run::{Command, Sandbox, ProtectSystem};
21///
22/// let sandbox = Sandbox::new()
23///     .protect_system(ProtectSystem::Strict)
24///     .read_write_paths([Path::new("/tmp")])
25///     .build()
26///     .unwrap();
27///
28/// let output = Command::new("ls")
29///     .arg("/usr")
30///     .sandbox(sandbox)
31///     .output()
32///     .expect("failed to execute process");
33/// ```
34pub struct Command {
35    inner: std::process::Command,
36    sandbox: Option<SandboxPolicy>,
37}
38
39impl Command {
40    /// Constructs a new `Command` for launching the program at path `program`.
41    pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
42        Self {
43            inner: std::process::Command::new(program),
44            sandbox: None,
45        }
46    }
47
48    /// Adds an argument to pass to the program.
49    pub fn arg<S: AsRef<OsStr>>(mut self, arg: S) -> Self {
50        self.inner.arg(arg);
51        self
52    }
53
54    /// Adds multiple arguments to pass to the program.
55    pub fn args<I, S>(mut self, args: I) -> Self
56    where
57        I: IntoIterator<Item = S>,
58        S: AsRef<OsStr>,
59    {
60        self.inner.args(args);
61        self
62    }
63
64    /// Inserts or updates an environment variable mapping.
65    pub fn env<K, V>(mut self, key: K, val: V) -> Self
66    where
67        K: AsRef<OsStr>,
68        V: AsRef<OsStr>,
69    {
70        self.inner.env(key, val);
71        self
72    }
73
74    /// Adds or updates multiple environment variable mappings.
75    pub fn envs<I, K, V>(mut self, vars: I) -> Self
76    where
77        I: IntoIterator<Item = (K, V)>,
78        K: AsRef<OsStr>,
79        V: AsRef<OsStr>,
80    {
81        self.inner.envs(vars);
82        self
83    }
84
85    /// Removes an environment variable mapping.
86    pub fn env_remove<K: AsRef<OsStr>>(mut self, key: K) -> Self {
87        self.inner.env_remove(key);
88        self
89    }
90
91    /// Clears the entire environment map for the child process.
92    pub fn env_clear(mut self) -> Self {
93        self.inner.env_clear();
94        self
95    }
96
97    /// Sets the working directory for the child process.
98    pub fn current_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
99        self.inner.current_dir(dir);
100        self
101    }
102
103    /// Configuration for the child process's standard input (stdin) handle.
104    pub fn stdin<T: Into<Stdio>>(mut self, cfg: T) -> Self {
105        self.inner.stdin(cfg);
106        self
107    }
108
109    /// Configuration for the child process's standard output (stdout) handle.
110    pub fn stdout<T: Into<Stdio>>(mut self, cfg: T) -> Self {
111        self.inner.stdout(cfg);
112        self
113    }
114
115    /// Configuration for the child process's standard error (stderr) handle.
116    pub fn stderr<T: Into<Stdio>>(mut self, cfg: T) -> Self {
117        self.inner.stderr(cfg);
118        self
119    }
120
121    /// Apply a sandbox policy to this command.
122    ///
123    /// The sandbox will be applied to the child process after fork but before exec,
124    /// restricting the process according to the policy.
125    pub fn sandbox(mut self, policy: SandboxPolicy) -> Self {
126        self.sandbox = Some(policy);
127        self
128    }
129
130    /// Executes the command as a child process, returning a handle to it.
131    pub fn spawn(mut self) -> io::Result<Child> {
132        if let Some(policy) = self.sandbox.take() {
133            self.apply_sandbox_pre_exec(policy)?;
134        }
135        self.inner.spawn()
136    }
137
138    /// Executes the command as a child process, waiting for it to finish and
139    /// collecting all of its output.
140    pub fn output(mut self) -> io::Result<Output> {
141        if let Some(policy) = self.sandbox.take() {
142            self.apply_sandbox_pre_exec(policy)?;
143        }
144        self.inner.output()
145    }
146
147    /// Executes a command as a child process, waiting for it to finish and
148    /// collecting its status.
149    pub fn status(mut self) -> io::Result<ExitStatus> {
150        if let Some(policy) = self.sandbox.take() {
151            self.apply_sandbox_pre_exec(policy)?;
152        }
153        self.inner.status()
154    }
155
156    #[cfg(unix)]
157    fn apply_sandbox_pre_exec(&mut self, policy: SandboxPolicy) -> io::Result<()> {
158        use std::os::unix::process::CommandExt;
159
160        // SAFETY: The pre_exec closure runs in the child process after fork
161        // but before exec. We apply the sandbox here so the executed program
162        // runs under the sandbox restrictions.
163        unsafe {
164            self.inner
165                .pre_exec(move || platform::apply_sandbox(&policy).map_err(io::Error::other));
166        }
167        Ok(())
168    }
169
170    #[cfg(target_os = "windows")]
171    fn apply_sandbox_pre_exec(&mut self, _policy: SandboxPolicy) -> io::Result<()> {
172        // Silently skip sandboxing on Windows.
173        Ok(())
174    }
175
176    #[cfg(not(any(unix, target_os = "windows")))]
177    fn apply_sandbox_pre_exec(&mut self, _policy: SandboxPolicy) -> io::Result<()> {
178        Err(io::Error::new(
179            io::ErrorKind::Unsupported,
180            "Sandboxing is only supported on Unix platforms (Linux and macOS)",
181        ))
182    }
183}