Skip to main content

windows_spawn/
handles.rs

1//! Owned capability wrappers used by process creation.
2
3use std::fmt;
4use std::fs::File;
5use std::io;
6use std::os::windows::io::{AsHandle, BorrowedHandle, OwnedHandle};
7
8use crate::child::Child;
9use crate::sys;
10
11/// Describes a standard stream source while keeping any supplied handle owned.
12pub struct Stdio {
13    pub(crate) inner: StdioInner,
14}
15
16pub(crate) enum StdioInner {
17    Inherit,
18    Null,
19    Piped,
20    Owned(OwnedHandle),
21}
22
23impl fmt::Debug for Stdio {
24    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
25        let name = match self.inner {
26            StdioInner::Inherit => "Inherit",
27            StdioInner::Null => "Null",
28            StdioInner::Piped => "Piped",
29            StdioInner::Owned(_) => "Owned",
30        };
31        formatter.debug_tuple("Stdio").field(&name).finish()
32    }
33}
34
35impl Stdio {
36    /// Inherits the corresponding standard stream from the caller.
37    #[must_use]
38    pub const fn inherit() -> Self {
39        Self {
40            inner: StdioInner::Inherit,
41        }
42    }
43
44    /// Connects the stream to the Windows null device.
45    #[must_use]
46    pub const fn null() -> Self {
47        Self {
48            inner: StdioInner::Null,
49        }
50    }
51
52    /// Creates an anonymous pipe and returns the caller's end on [`Child`].
53    #[must_use]
54    pub const fn piped() -> Self {
55        Self {
56            inner: StdioInner::Piped,
57        }
58    }
59
60    /// Duplicates a borrowed handle into private, non-inheritable ownership.
61    ///
62    /// The original handle may be closed immediately after this call.
63    ///
64    /// # Errors
65    ///
66    /// Returns the operating-system error if duplication fails.
67    pub fn from_borrowed<T: AsHandle>(source: &T) -> io::Result<Self> {
68        Ok(Self::from(sys::duplicate_local(source.as_handle(), false)?))
69    }
70}
71
72impl From<OwnedHandle> for Stdio {
73    fn from(handle: OwnedHandle) -> Self {
74        Self {
75            inner: StdioInner::Owned(handle),
76        }
77    }
78}
79
80impl From<File> for Stdio {
81    fn from(file: File) -> Self {
82        Self::from(OwnedHandle::from(file))
83    }
84}
85
86/// A process handle validated for use as `PROC_THREAD_ATTRIBUTE_PARENT_PROCESS`.
87#[derive(Debug)]
88pub struct ParentProcess {
89    handle: OwnedHandle,
90}
91
92impl ParentProcess {
93    /// Opens a process with process-creation and handle-duplication rights.
94    ///
95    /// # Errors
96    ///
97    /// Returns an error if the PID cannot be opened with the required rights.
98    pub fn open(pid: u32) -> io::Result<Self> {
99        Ok(Self {
100            handle: sys::open_parent_process(pid)?,
101        })
102    }
103
104    /// Adopts and validates an existing process handle.
105    ///
106    /// # Errors
107    ///
108    /// Returns an error if the handle does not identify a process.
109    pub fn from_handle(handle: OwnedHandle) -> io::Result<Self> {
110        sys::validate_process_handle(handle.as_handle())?;
111        Ok(Self { handle })
112    }
113}
114
115impl AsHandle for ParentProcess {
116    fn as_handle(&self) -> BorrowedHandle<'_> {
117        self.handle.as_handle()
118    }
119}
120
121/// An owned Windows Job object.
122#[derive(Debug)]
123pub struct Job {
124    handle: OwnedHandle,
125}
126
127impl Job {
128    /// Creates an unnamed Job object.
129    ///
130    /// # Errors
131    ///
132    /// Returns the operating-system error if Job creation fails.
133    pub fn create() -> io::Result<Self> {
134        Ok(Self {
135            handle: sys::create_job()?,
136        })
137    }
138
139    /// Adopts an existing handle after verifying it is a Job handle.
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if Job limit information cannot be queried.
144    pub fn from_handle(handle: OwnedHandle) -> io::Result<Self> {
145        sys::validate_job_handle(handle.as_handle())?;
146        Ok(Self { handle })
147    }
148
149    /// Creates an independent duplicate of this Job handle.
150    ///
151    /// # Errors
152    ///
153    /// Returns the operating-system error if duplication fails.
154    pub fn duplicate(&self) -> io::Result<Self> {
155        Ok(Self {
156            handle: sys::duplicate_local(self.handle.as_handle(), false)?,
157        })
158    }
159
160    /// Assigns an existing child to the Job.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error when Windows rejects the Job assignment.
165    pub fn assign(&self, child: &Child) -> io::Result<()> {
166        sys::assign_job(self.handle.as_handle(), child.process_handle())
167    }
168
169    /// Terminates every process in the Job with `exit_code`.
170    ///
171    /// # Errors
172    ///
173    /// Returns the operating-system error if Job termination fails.
174    pub fn terminate(&self, exit_code: u32) -> io::Result<()> {
175        sys::terminate_job(self.handle.as_handle(), exit_code)
176    }
177
178    /// Enables or disables kill-on-close without overwriting other Job limits.
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if querying or updating Job limits fails.
183    pub fn set_kill_on_close(&self, enable: bool) -> io::Result<()> {
184        sys::set_job_kill_on_close(self.handle.as_handle(), enable)
185    }
186}
187
188impl AsHandle for Job {
189    fn as_handle(&self) -> BorrowedHandle<'_> {
190        self.handle.as_handle()
191    }
192}
193
194/// A borrowed pseudoconsole capability.
195///
196/// # Safety
197///
198/// Implementations must return a valid, nonzero `HPCON` and keep it open and
199/// unchanged for the full lifetime of every borrow passed to
200/// [`crate::SpawnOptions::pseudoconsole`]. The implementation retains
201/// ownership: windows-spawn borrows the value for process creation and never closes
202/// or releases it.
203#[allow(unsafe_code)]
204pub unsafe trait AsPseudoConsole {
205    /// Returns the borrowed raw `HPCON` value.
206    ///
207    /// Library implementations use this method to bridge their owned
208    /// pseudoconsole type to windows-spawn. Applications should normally pass the
209    /// implementing object to [`crate::SpawnOptions::pseudoconsole`] instead
210    /// of reading the numeric value.
211    ///
212    /// The returned value must satisfy the trait's safety contract. Calling
213    /// this method does not transfer ownership.
214    fn raw_pseudoconsole(&self) -> isize;
215}
216
217#[cfg(test)]
218mod tests {
219    use std::os::windows::io::AsRawHandle;
220
221    use super::*;
222
223    #[test]
224    fn owned_handle_adoption_validates_resource_kind() {
225        let mut host = std::process::Command::new("cmd.exe")
226            .args(["/D", "/C", "ping -n 5 127.0.0.1 >nul"])
227            .spawn()
228            .unwrap();
229        let parent = ParentProcess::open(host.id()).unwrap();
230        assert!(format!("{parent:?}").contains("ParentProcess"));
231        let adopted_parent =
232            ParentProcess::from_handle(sys::duplicate_local(host.as_handle(), false).unwrap())
233                .unwrap();
234        assert_ne!(
235            adopted_parent.as_handle().as_raw_handle(),
236            std::ptr::null_mut()
237        );
238
239        let job = Job::create().unwrap();
240        let duplicate = job.duplicate().unwrap();
241        let adopted_job = Job::from_handle(duplicate.handle).unwrap();
242        adopted_job.set_kill_on_close(true).unwrap();
243        adopted_job.set_kill_on_close(false).unwrap();
244
245        let file = File::open("NUL").unwrap();
246        let not_process = sys::duplicate_local(file.as_handle(), false).unwrap();
247        assert!(ParentProcess::from_handle(not_process).is_err());
248        let not_job = sys::duplicate_local(file.as_handle(), false).unwrap();
249        assert!(Job::from_handle(not_job).is_err());
250        let _ = host.kill();
251        let _ = host.wait();
252    }
253}