browser_automation_cli/
win_job.rs1#![allow(dead_code)]
14
15pub fn platform_supports_job_objects() -> bool {
17 cfg!(windows)
18}
19
20pub fn capability_summary() -> &'static str {
22 if platform_supports_job_objects() {
23 "windows_job_object:available (KILL_ON_JOB_CLOSE)"
24 } else {
25 "windows_job_object:stub (non-windows host)"
26 }
27}
28
29#[cfg(windows)]
36#[cfg_attr(docsrs, doc(cfg(windows)))]
37pub fn create_and_assign(pid: u32) -> usize {
38 use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
39 use windows_sys::Win32::System::Threading::{
40 AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation, OpenProcess,
41 SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
42 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
43 };
44
45 if pid == 0 {
46 return 0;
47 }
48
49 let job: HANDLE = unsafe { CreateJobObjectW(std::ptr::null(), std::ptr::null()) };
52 if job.is_null() {
53 return 0;
54 }
55
56 let set_ok = unsafe {
59 let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
60 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
61 SetInformationJobObject(
62 job,
63 JobObjectExtendedLimitInformation,
64 &info as *const _ as *const _,
65 std::mem::size_of_val(&info) as u32,
66 )
67 };
68 if set_ok == 0 {
69 unsafe {
71 let _ = CloseHandle(job);
72 }
73 return 0;
74 }
75
76 let proc: HANDLE =
80 unsafe { OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid) };
81 if proc.is_null() {
82 unsafe {
83 let _ = CloseHandle(job);
84 }
85 return 0;
86 }
87
88 let assigned = unsafe { AssignProcessToJobObject(job, proc) };
91 unsafe {
93 let _ = CloseHandle(proc);
94 }
95 if assigned == 0 {
96 unsafe {
97 let _ = CloseHandle(job);
98 }
99 return 0;
100 }
101 job as usize
102}
103
104#[cfg(windows)]
106#[cfg_attr(docsrs, doc(cfg(windows)))]
107pub fn terminate_job(handle: usize) {
108 use windows_sys::Win32::System::Threading::TerminateJobObject;
109 if handle == 0 {
110 return;
111 }
112 unsafe {
116 let _ = TerminateJobObject(handle as _, 1);
117 }
118}
119
120#[cfg(windows)]
122#[cfg_attr(docsrs, doc(cfg(windows)))]
123pub fn close_job(handle: usize) {
124 use windows_sys::Win32::Foundation::CloseHandle;
125 if handle == 0 {
126 return;
127 }
128 unsafe {
131 let _ = CloseHandle(handle as _);
132 }
133}
134
135#[cfg(windows)]
137#[cfg_attr(docsrs, doc(cfg(windows)))]
138pub fn terminate_pid(pid: u32) {
139 use windows_sys::Win32::Foundation::CloseHandle;
140 use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
141 if pid == 0 {
142 return;
143 }
144 let proc = unsafe { OpenProcess(PROCESS_TERMINATE, 0, pid) };
147 if proc.is_null() {
148 return;
149 }
150 unsafe {
153 let _ = TerminateProcess(proc, 1);
154 }
155 unsafe {
157 let _ = CloseHandle(proc);
158 }
159}
160
161#[cfg(windows)]
167#[cfg_attr(docsrs, doc(cfg(windows)))]
168pub fn validate_assign_current_process() -> Result<usize, String> {
169 use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
170 use windows_sys::Win32::System::Threading::{
171 AssignProcessToJobObject, CreateJobObjectW, GetCurrentProcess, GetCurrentProcessId,
172 OpenProcess, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
173 };
174
175 let pid = unsafe { GetCurrentProcessId() };
178 unsafe {
184 let job: HANDLE = CreateJobObjectW(std::ptr::null(), std::ptr::null());
185 if job.is_null() {
186 return Err("CreateJobObjectW failed".into());
187 }
188 let proc: HANDLE = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid);
190 if proc.is_null() {
191 let cur = GetCurrentProcess();
193 let assigned = AssignProcessToJobObject(job, cur);
194 if assigned == 0 {
195 let _ = CloseHandle(job);
196 return Err("AssignProcessToJobObject(current) failed".into());
197 }
198 return Ok(job as usize);
199 }
200 let assigned = AssignProcessToJobObject(job, proc);
201 let _ = CloseHandle(proc);
202 if assigned == 0 {
203 let _ = CloseHandle(job);
204 return Err("AssignProcessToJobObject failed".into());
205 }
206 Ok(job as usize)
207 }
208}
209
210#[cfg(not(windows))]
212#[cfg_attr(docsrs, doc(cfg(not(windows))))]
213pub fn create_and_assign(_pid: u32) -> usize {
214 0
215}
216
217#[cfg(not(windows))]
219#[cfg_attr(docsrs, doc(cfg(not(windows))))]
220pub fn terminate_job(_handle: usize) {}
221
222#[cfg(not(windows))]
224#[cfg_attr(docsrs, doc(cfg(not(windows))))]
225pub fn close_job(_handle: usize) {}
226
227#[cfg(not(windows))]
229#[cfg_attr(docsrs, doc(cfg(not(windows))))]
230pub fn terminate_pid(_pid: u32) {}
231
232#[cfg(not(windows))]
234#[cfg_attr(docsrs, doc(cfg(not(windows))))]
235pub fn validate_assign_current_process() -> Result<usize, String> {
236 Err("job objects unavailable on non-windows".into())
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn capability_summary_is_non_empty() {
245 assert!(!capability_summary().is_empty());
246 }
247
248 #[test]
249 fn stub_or_create_does_not_panic() {
250 let h = create_and_assign(0);
251 assert_eq!(h, 0, "pid 0 must never produce a job handle");
252 terminate_job(0);
253 close_job(0);
254 terminate_pid(0);
255 }
256
257 #[test]
258 fn platform_flag_matches_cfg() {
259 assert_eq!(platform_supports_job_objects(), cfg!(windows));
260 }
261
262 #[test]
263 fn validate_current_process_behavior() {
264 match validate_assign_current_process() {
265 Ok(h) => {
266 assert_ne!(h, 0);
268 close_job(h);
269 }
270 Err(e) => {
271 #[cfg(windows)]
272 assert!(!e.is_empty());
273 #[cfg(not(windows))]
274 assert!(e.contains("non-windows"));
275 }
276 }
277 }
278}