1use std::fmt;
4use std::marker::PhantomData;
5use std::ops::{BitOr, BitOrAssign};
6
7use windows_sys::Win32::System::Threading::{
8 CREATE_BREAKAWAY_FROM_JOB, CREATE_DEFAULT_ERROR_MODE, CREATE_NEW_CONSOLE,
9 CREATE_NEW_PROCESS_GROUP, CREATE_NO_WINDOW, CREATE_PRESERVE_CODE_AUTHZ_LEVEL, DETACHED_PROCESS,
10 INHERIT_PARENT_AFFINITY,
11};
12
13use crate::handles::{AsPseudoConsole, Job, ParentProcess};
14use crate::mitigation::MitigationPolicy;
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
18pub enum DropPolicy {
19 #[default]
21 Detach,
22 KillTree,
24}
25
26#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
31pub struct CreationFlags(u32);
32
33impl CreationFlags {
34 #[must_use]
36 pub const fn new() -> Self {
37 Self(0)
38 }
39
40 pub const DETACHED_PROCESS: Self = Self(DETACHED_PROCESS);
42 pub const NEW_CONSOLE: Self = Self(CREATE_NEW_CONSOLE);
44 pub const NEW_PROCESS_GROUP: Self = Self(CREATE_NEW_PROCESS_GROUP);
46 pub const INHERIT_PARENT_AFFINITY: Self = Self(INHERIT_PARENT_AFFINITY);
48 pub const BREAKAWAY_FROM_JOB: Self = Self(CREATE_BREAKAWAY_FROM_JOB);
50 pub const PRESERVE_CODE_AUTHZ_LEVEL: Self = Self(CREATE_PRESERVE_CODE_AUTHZ_LEVEL);
52 pub const DEFAULT_ERROR_MODE: Self = Self(CREATE_DEFAULT_ERROR_MODE);
54 pub const NO_WINDOW: Self = Self(CREATE_NO_WINDOW);
56
57 pub(crate) const fn bits(self) -> u32 {
58 self.0
59 }
60
61 pub(crate) const fn contains(self, other: Self) -> bool {
62 self.0 & other.0 == other.0
63 }
64}
65
66struct PseudoConsole<'a> {
67 raw: isize,
68 _borrow: PhantomData<&'a dyn AsPseudoConsole>,
69}
70
71impl<'a> PseudoConsole<'a> {
72 fn new<T: AsPseudoConsole>(pseudoconsole: &'a T) -> Self {
73 Self {
74 raw: pseudoconsole.raw_pseudoconsole(),
75 _borrow: PhantomData,
76 }
77 }
78}
79
80impl BitOr for CreationFlags {
81 type Output = Self;
82
83 fn bitor(self, rhs: Self) -> Self::Output {
84 Self(self.0 | rhs.0)
85 }
86}
87
88impl BitOrAssign for CreationFlags {
89 fn bitor_assign(&mut self, rhs: Self) {
90 self.0 |= rhs.0;
91 }
92}
93
94pub struct SpawnOptions<'a> {
113 pub(crate) jobs: Vec<&'a Job>,
114 pub(crate) parent: Option<&'a ParentProcess>,
115 pub(crate) mitigation: MitigationPolicy,
116 pseudoconsole: Option<PseudoConsole<'a>>,
117 pub(crate) creation_flags: CreationFlags,
118 pub(crate) drop_policy: DropPolicy,
119}
120
121impl fmt::Debug for SpawnOptions<'_> {
122 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123 formatter
124 .debug_struct("SpawnOptions")
125 .field("jobs", &self.jobs)
126 .field("parent", &self.parent)
127 .field("mitigation", &self.mitigation)
128 .field(
129 "pseudoconsole",
130 &self.pseudoconsole.as_ref().map(|_| "borrowed"),
131 )
132 .field("creation_flags", &self.creation_flags)
133 .field("drop_policy", &self.drop_policy)
134 .finish()
135 }
136}
137
138impl Default for SpawnOptions<'_> {
139 fn default() -> Self {
140 Self {
141 jobs: Vec::new(),
142 parent: None,
143 mitigation: MitigationPolicy::new(),
144 pseudoconsole: None,
145 creation_flags: CreationFlags::new(),
146 drop_policy: DropPolicy::Detach,
147 }
148 }
149}
150
151impl<'a> SpawnOptions<'a> {
152 #[must_use]
154 pub fn new() -> Self {
155 Self::default()
156 }
157
158 #[must_use]
160 pub fn job(mut self, job: &'a Job) -> Self {
161 self.jobs.push(job);
162 self
163 }
164
165 #[must_use]
167 pub fn parent_process(mut self, parent: &'a ParentProcess) -> Self {
168 self.parent = Some(parent);
169 self
170 }
171
172 #[must_use]
174 pub const fn mitigation(mut self, mitigation: MitigationPolicy) -> Self {
175 self.mitigation = mitigation;
176 self
177 }
178
179 #[must_use]
181 pub fn pseudoconsole<T: AsPseudoConsole>(mut self, pseudoconsole: &'a T) -> Self {
182 self.pseudoconsole = Some(PseudoConsole::new(pseudoconsole));
183 self
184 }
185
186 #[must_use]
188 pub const fn creation_flags(mut self, flags: CreationFlags) -> Self {
189 self.creation_flags = flags;
190 self
191 }
192
193 #[must_use]
195 pub const fn drop_policy(mut self, policy: DropPolicy) -> Self {
196 self.drop_policy = policy;
197 self
198 }
199
200 pub(crate) fn pseudoconsole_raw(&self) -> Option<isize> {
201 self.pseudoconsole
202 .as_ref()
203 .map(|pseudoconsole| pseudoconsole.raw)
204 }
205}
206
207#[cfg(test)]
208#[allow(unsafe_code)]
209mod tests {
210 use std::cell::Cell;
211
212 use super::*;
213
214 struct TestPseudoConsole(Cell<isize>);
215
216 unsafe impl AsPseudoConsole for TestPseudoConsole {
218 fn raw_pseudoconsole(&self) -> isize {
219 self.0.get()
220 }
221 }
222
223 #[test]
224 fn creation_flags_combine_idempotently_and_options_keep_job_order() {
225 let combined = CreationFlags::NEW_PROCESS_GROUP | CreationFlags::DEFAULT_ERROR_MODE;
226 assert_eq!(combined.bits(), 0x0400_0200);
227 assert_eq!(
228 (CreationFlags::NEW_PROCESS_GROUP | CreationFlags::NEW_PROCESS_GROUP).bits(),
229 CreationFlags::NEW_PROCESS_GROUP.bits()
230 );
231 let mut assigned = CreationFlags::NEW_PROCESS_GROUP;
232 assigned |= CreationFlags::DEFAULT_ERROR_MODE;
233 assert_eq!(assigned.bits(), combined.bits());
234
235 let outer = Job::create().unwrap();
236 let inner = Job::create().unwrap();
237 let options = SpawnOptions::new().job(&outer).job(&inner);
238 assert_eq!(options.jobs.len(), 2);
239 assert!(std::ptr::eq(options.jobs[0], &outer));
240 assert!(std::ptr::eq(options.jobs[1], &inner));
241 }
242
243 #[test]
244 fn pseudoconsole_builder_snapshots_the_raw_value() {
245 let pseudoconsole = TestPseudoConsole(Cell::new(42));
246 let options = SpawnOptions::new().pseudoconsole(&pseudoconsole);
247 pseudoconsole.0.set(99);
248 assert_eq!(options.pseudoconsole_raw(), Some(42));
249 assert!(format!("{options:?}").contains("borrowed"));
250 }
251}