browser_automation_cli/
win_job.rs1#![allow(dead_code)]
8
9pub fn platform_supports_job_objects() -> bool {
11 cfg!(windows)
12}
13
14pub fn capability_summary() -> &'static str {
16 if platform_supports_job_objects() {
17 "windows_job_object:available (KILL_ON_JOB_CLOSE)"
18 } else {
19 "windows_job_object:stub (non-windows host)"
20 }
21}
22
23#[cfg(windows)]
25pub fn create_and_assign(pid: u32) -> usize {
26 use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
27 use windows_sys::Win32::System::Threading::{
28 AssignProcessToJobObject, CreateJobObjectW, JobObjectExtendedLimitInformation, OpenProcess,
29 SetInformationJobObject, JOBOBJECT_EXTENDED_LIMIT_INFORMATION,
30 JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
31 };
32
33 if pid == 0 {
34 return 0;
35 }
36
37 unsafe {
38 let job: HANDLE = CreateJobObjectW(std::ptr::null(), std::ptr::null());
39 if job.is_null() {
40 return 0;
41 }
42 let mut info: JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
43 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
44 let ok = SetInformationJobObject(
45 job,
46 JobObjectExtendedLimitInformation,
47 &info as *const _ as *const _,
48 std::mem::size_of_val(&info) as u32,
49 );
50 if ok == 0 {
51 let _ = CloseHandle(job);
52 return 0;
53 }
54 let proc: HANDLE = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid);
55 if proc.is_null() {
56 let _ = CloseHandle(job);
57 return 0;
58 }
59 let assigned = AssignProcessToJobObject(job, proc);
60 let _ = CloseHandle(proc);
61 if assigned == 0 {
62 let _ = CloseHandle(job);
63 return 0;
64 }
65 job as usize
66 }
67}
68
69#[cfg(windows)]
71pub fn terminate_job(handle: usize) {
72 use windows_sys::Win32::System::Threading::TerminateJobObject;
73 if handle == 0 {
74 return;
75 }
76 unsafe {
77 let _ = TerminateJobObject(handle as _, 1);
78 }
79}
80
81#[cfg(windows)]
83pub fn close_job(handle: usize) {
84 use windows_sys::Win32::Foundation::CloseHandle;
85 if handle == 0 {
86 return;
87 }
88 unsafe {
89 let _ = CloseHandle(handle as _);
90 }
91}
92
93#[cfg(windows)]
95pub fn terminate_pid(pid: u32) {
96 use windows_sys::Win32::Foundation::CloseHandle;
97 use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
98 if pid == 0 {
99 return;
100 }
101 unsafe {
102 let proc = OpenProcess(PROCESS_TERMINATE, 0, pid);
103 if !proc.is_null() {
104 let _ = TerminateProcess(proc, 1);
105 let _ = CloseHandle(proc);
106 }
107 }
108}
109
110#[cfg(windows)]
116pub fn validate_assign_current_process() -> Result<usize, String> {
117 use windows_sys::Win32::Foundation::{CloseHandle, HANDLE};
118 use windows_sys::Win32::System::Threading::{
119 AssignProcessToJobObject, CreateJobObjectW, GetCurrentProcess, GetCurrentProcessId,
120 OpenProcess, PROCESS_SET_QUOTA, PROCESS_TERMINATE,
121 };
122
123 let pid = unsafe { GetCurrentProcessId() };
124 unsafe {
125 let job: HANDLE = CreateJobObjectW(std::ptr::null(), std::ptr::null());
126 if job.is_null() {
127 return Err("CreateJobObjectW failed".into());
128 }
129 let proc: HANDLE = OpenProcess(PROCESS_SET_QUOTA | PROCESS_TERMINATE, 0, pid);
131 if proc.is_null() {
132 let cur = GetCurrentProcess();
134 let assigned = AssignProcessToJobObject(job, cur);
135 if assigned == 0 {
136 let _ = CloseHandle(job);
137 return Err("AssignProcessToJobObject(current) failed".into());
138 }
139 return Ok(job as usize);
140 }
141 let assigned = AssignProcessToJobObject(job, proc);
142 let _ = CloseHandle(proc);
143 if assigned == 0 {
144 let _ = CloseHandle(job);
145 return Err("AssignProcessToJobObject failed".into());
146 }
147 Ok(job as usize)
148 }
149}
150
151#[cfg(not(windows))]
152pub fn create_and_assign(_pid: u32) -> usize {
154 0
155}
156
157#[cfg(not(windows))]
158pub fn terminate_job(_handle: usize) {}
160
161#[cfg(not(windows))]
162pub fn close_job(_handle: usize) {}
164
165#[cfg(not(windows))]
166pub fn terminate_pid(_pid: u32) {}
168
169#[cfg(not(windows))]
170pub fn validate_assign_current_process() -> Result<usize, String> {
172 Err("job objects unavailable on non-windows".into())
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn capability_summary_is_non_empty() {
181 assert!(!capability_summary().is_empty());
182 }
183
184 #[test]
185 fn stub_or_create_does_not_panic() {
186 let h = create_and_assign(0);
187 assert_eq!(h, 0, "pid 0 must never produce a job handle");
188 terminate_job(0);
189 close_job(0);
190 terminate_pid(0);
191 }
192
193 #[test]
194 fn platform_flag_matches_cfg() {
195 assert_eq!(platform_supports_job_objects(), cfg!(windows));
196 }
197
198 #[test]
199 fn validate_current_process_behavior() {
200 match validate_assign_current_process() {
201 Ok(h) => {
202 assert_ne!(h, 0);
204 close_job(h);
205 }
206 Err(e) => {
207 #[cfg(windows)]
208 assert!(!e.is_empty());
209 #[cfg(not(windows))]
210 assert!(e.contains("non-windows"));
211 }
212 }
213 }
214}