Skip to main content

windows_spawn/
command.rs

1//! Reusable process-launch intent.
2
3use std::ffi::{OsStr, OsString};
4use std::io;
5use std::os::windows::io::{AsHandle, OwnedHandle};
6use std::path::{Path, PathBuf};
7use std::process::{ExitStatus, Output};
8
9use crate::child::{Child, SuspendedChild};
10use crate::handles::Stdio;
11use crate::options::SpawnOptions;
12use crate::plan::{IoMode, SpawnPlan};
13use crate::sys;
14use crate::transaction::SpawnTransaction;
15
16#[derive(Debug)]
17pub(crate) enum Arg {
18    Text(OsString),
19    Raw(OsString),
20    Handle(OwnedHandle),
21}
22
23#[derive(Debug)]
24pub(crate) enum EnvOp {
25    Set(OsString, EnvValue),
26    Remove(OsString),
27}
28
29#[derive(Debug)]
30pub(crate) enum EnvValue {
31    Text(OsString),
32    Handle(OwnedHandle),
33}
34
35/// A reusable description of a Windows process launch.
36///
37/// Handles embedded by [`Self::arg_handle`] and [`Self::env_handle`] are
38/// privately duplicated when configured. Each spawn duplicates those handles
39/// again into the actual parent process and only then lowers their numeric
40/// values to decimal text.
41///
42/// # Examples
43///
44/// Run a command to completion and capture what it wrote, terminating any
45/// descendants it leaves behind:
46///
47/// ```
48/// use windows_spawn::{Command, DropPolicy, SpawnOptions};
49///
50/// // `.bat` and `.cmd` are rejected, so a shell boundary is always explicit.
51/// let shell = std::env::var_os("COMSPEC").expect("COMSPEC is set on Windows");
52/// let mut command = Command::new(shell);
53/// command.args(["/D", "/S", "/C"]).raw_arg("echo hello");
54///
55/// let output = command.output_with(SpawnOptions::new().drop_policy(DropPolicy::KillTree))?;
56///
57/// assert!(output.status.success());
58/// assert!(String::from_utf8_lossy(&output.stdout).contains("hello"));
59/// # Ok::<(), std::io::Error>(())
60/// ```
61#[derive(Debug)]
62pub struct Command {
63    pub(crate) program: OsString,
64    pub(crate) args: Vec<Arg>,
65    pub(crate) env_clear: bool,
66    pub(crate) env_ops: Vec<EnvOp>,
67    pub(crate) cwd: Option<PathBuf>,
68    pub(crate) stdin: Option<Stdio>,
69    pub(crate) stdout: Option<Stdio>,
70    pub(crate) stderr: Option<Stdio>,
71}
72
73impl Command {
74    /// Creates a command which will execute `program`.
75    #[must_use]
76    pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
77        Self {
78            program: program.as_ref().to_os_string(),
79            args: Vec::new(),
80            env_clear: false,
81            env_ops: Vec::new(),
82            cwd: None,
83            stdin: None,
84            stdout: None,
85            stderr: None,
86        }
87    }
88
89    /// Appends a normally quoted argument.
90    pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
91        self.args.push(Arg::Text(arg.as_ref().to_os_string()));
92        self
93    }
94
95    /// Appends multiple normally quoted arguments.
96    pub fn args<I, S>(&mut self, args: I) -> &mut Self
97    where
98        I: IntoIterator<Item = S>,
99        S: AsRef<OsStr>,
100    {
101        for arg in args {
102            self.arg(arg);
103        }
104        self
105    }
106
107    /// Appends text verbatim to the Windows command line.
108    ///
109    /// The text is separated from the preceding element by one space but is
110    /// otherwise neither quoted nor escaped.
111    pub fn raw_arg<S: AsRef<OsStr>>(&mut self, text: S) -> &mut Self {
112        self.args.push(Arg::Raw(text.as_ref().to_os_string()));
113        self
114    }
115
116    /// Appends a handle argument whose child-table value is lowered at spawn.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the source handle cannot be duplicated.
121    pub fn arg_handle<T: AsHandle>(&mut self, handle: &T) -> io::Result<&mut Self> {
122        self.args.push(Arg::Handle(sys::duplicate_local(
123            handle.as_handle(),
124            false,
125        )?));
126        Ok(self)
127    }
128
129    /// Sets one environment variable.
130    pub fn env<K, V>(&mut self, key: K, value: V) -> &mut Self
131    where
132        K: AsRef<OsStr>,
133        V: AsRef<OsStr>,
134    {
135        self.env_ops.push(EnvOp::Set(
136            key.as_ref().to_os_string(),
137            EnvValue::Text(value.as_ref().to_os_string()),
138        ));
139        self
140    }
141
142    /// Sets multiple environment variables.
143    pub fn envs<I, K, V>(&mut self, vars: I) -> &mut Self
144    where
145        I: IntoIterator<Item = (K, V)>,
146        K: AsRef<OsStr>,
147        V: AsRef<OsStr>,
148    {
149        for (key, value) in vars {
150            self.env(key, value);
151        }
152        self
153    }
154
155    /// Sets an environment variable to a handle's child-table numeric value.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if the source handle cannot be duplicated.
160    pub fn env_handle<K: AsRef<OsStr>, T: AsHandle>(
161        &mut self,
162        key: K,
163        handle: &T,
164    ) -> io::Result<&mut Self> {
165        self.env_ops.push(EnvOp::Set(
166            key.as_ref().to_os_string(),
167            EnvValue::Handle(sys::duplicate_local(handle.as_handle(), false)?),
168        ));
169        Ok(self)
170    }
171
172    /// Removes an environment variable case-insensitively.
173    pub fn env_remove<K: AsRef<OsStr>>(&mut self, key: K) -> &mut Self {
174        self.env_ops
175            .push(EnvOp::Remove(key.as_ref().to_os_string()));
176        self
177    }
178
179    /// Clears the inherited environment and prior recorded modifications.
180    pub fn env_clear(&mut self) -> &mut Self {
181        self.env_clear = true;
182        self.env_ops.clear();
183        self
184    }
185
186    /// Sets the child working directory.
187    pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
188        self.cwd = Some(dir.as_ref().to_path_buf());
189        self
190    }
191
192    /// Configures standard input.
193    pub fn stdin<T: Into<Stdio>>(&mut self, stdio: T) -> &mut Self {
194        self.stdin = Some(stdio.into());
195        self
196    }
197
198    /// Configures standard output.
199    pub fn stdout<T: Into<Stdio>>(&mut self, stdio: T) -> &mut Self {
200        self.stdout = Some(stdio.into());
201        self
202    }
203
204    /// Configures standard error.
205    pub fn stderr<T: Into<Stdio>>(&mut self, stdio: T) -> &mut Self {
206        self.stderr = Some(stdio.into());
207        self
208    }
209
210    /// Returns the originally configured program.
211    #[must_use]
212    pub fn get_program(&self) -> &OsStr {
213        &self.program
214    }
215
216    /// Returns the configured working directory.
217    #[must_use]
218    pub fn get_current_dir(&self) -> Option<&Path> {
219        self.cwd.as_deref()
220    }
221
222    /// Spawns with default options.
223    ///
224    /// # Errors
225    ///
226    /// Returns validation, resource-acquisition, or process-creation errors.
227    pub fn spawn(&mut self) -> io::Result<Child> {
228        self.spawn_with(SpawnOptions::new())
229    }
230
231    /// Spawns using one operation's borrowed capabilities and policy.
232    ///
233    /// # Errors
234    ///
235    /// Returns validation, resource-acquisition, or process-creation errors.
236    pub fn spawn_with(&mut self, options: SpawnOptions<'_>) -> io::Result<Child> {
237        let plan = SpawnPlan::new_running(self, options, IoMode::Spawn)?;
238        Ok(SpawnTransaction::new(&plan)?.commit_child())
239    }
240
241    /// Spawns in the suspended type state with default options.
242    ///
243    /// # Errors
244    ///
245    /// Returns validation, resource-acquisition, or process-creation errors.
246    pub fn spawn_suspended(&mut self) -> io::Result<SuspendedChild> {
247        self.spawn_suspended_with(SpawnOptions::new())
248    }
249
250    /// Spawns in the suspended type state using explicit options.
251    ///
252    /// # Errors
253    ///
254    /// Returns validation, resource-acquisition, or process-creation errors.
255    pub fn spawn_suspended_with(
256        &mut self,
257        options: SpawnOptions<'_>,
258    ) -> io::Result<SuspendedChild> {
259        let plan = SpawnPlan::new_suspended(self, options, IoMode::Spawn)?;
260        Ok(SpawnTransaction::new(&plan)?.commit_suspended())
261    }
262
263    /// Runs the process and waits for its status using default options.
264    ///
265    /// # Errors
266    ///
267    /// Returns an error from spawning, waiting, or retrieving the exit code.
268    pub fn status(&mut self) -> io::Result<ExitStatus> {
269        self.status_with(SpawnOptions::new())
270    }
271
272    /// Runs the process and waits for its status using explicit options.
273    ///
274    /// # Errors
275    ///
276    /// Returns an error from spawning, waiting, or retrieving the exit code.
277    pub fn status_with(&mut self, options: SpawnOptions<'_>) -> io::Result<ExitStatus> {
278        self.spawn_with(options)?.wait()
279    }
280
281    /// Runs the process and captures output using default options.
282    ///
283    /// # Errors
284    ///
285    /// Returns an error from spawning, waiting, reading, or Job termination.
286    pub fn output(&mut self) -> io::Result<Output> {
287        self.output_with(SpawnOptions::new())
288    }
289
290    /// Runs the process and captures output using explicit options.
291    ///
292    /// # Errors
293    ///
294    /// Returns an error from spawning, waiting, reading, or Job termination.
295    pub fn output_with(&mut self, options: SpawnOptions<'_>) -> io::Result<Output> {
296        let plan = SpawnPlan::new_running(self, options, IoMode::Output)?;
297        SpawnTransaction::new(&plan)?
298            .commit_child()
299            .wait_with_output()
300    }
301}