running_process/broker/server/handoff/
windows.rs1use super::{
9 HandoffAttemptDecision, HandoffAttemptFailure, HandoffFallbackDecision, HandoffFallbackReason,
10 HandoffToken,
11};
12
13pub const DUPLICATE_HANDLE_TRANSPORT_SUPPORTED: bool = cfg!(windows);
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub struct WindowsHandleValue(usize);
19
20impl WindowsHandleValue {
21 pub fn new(value: usize) -> Self {
23 Self(value)
24 }
25
26 pub fn get(self) -> usize {
28 self.0
29 }
30
31 #[cfg(windows)]
32 fn from_handle(handle: windows_sys::Win32::Foundation::HANDLE) -> Self {
33 Self(handle as usize)
34 }
35
36 #[cfg(windows)]
37 fn as_handle(self) -> windows_sys::Win32::Foundation::HANDLE {
38 self.0 as windows_sys::Win32::Foundation::HANDLE
39 }
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct DuplicateHandleAttempt {
45 pub pipe_handle: WindowsHandleValue,
47 pub backend_pid: u32,
49 pub handoff_token: HandoffToken,
51}
52
53impl DuplicateHandleAttempt {
54 pub fn new(
56 pipe_handle: WindowsHandleValue,
57 backend_pid: u32,
58 handoff_token: HandoffToken,
59 ) -> Self {
60 Self {
61 pipe_handle,
62 backend_pid,
63 handoff_token,
64 }
65 }
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct DuplicateHandleSuccess {
71 pub duplicated_handle: WindowsHandleValue,
73 pub backend_pid: u32,
75 pub handoff_token: HandoffToken,
77}
78
79impl DuplicateHandleSuccess {
80 pub fn new(
82 duplicated_handle: WindowsHandleValue,
83 backend_pid: u32,
84 handoff_token: HandoffToken,
85 ) -> Self {
86 Self {
87 duplicated_handle,
88 backend_pid,
89 handoff_token,
90 }
91 }
92}
93
94pub type DuplicateHandleResult = Result<DuplicateHandleSuccess, DuplicateHandleError>;
96
97pub fn try_duplicate_handle(attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
104 platform_try_duplicate_handle(attempt)
105}
106
107#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
109pub enum DuplicateHandleError {
110 #[error("DuplicateHandle handoff transport is unsupported on this platform")]
112 UnsupportedPlatform,
113 #[error("cannot open backend process {backend_pid} for DuplicateHandle")]
115 CannotOpenBackend {
116 backend_pid: u32,
118 },
119 #[error("permission denied duplicating handle into backend process {backend_pid}")]
121 PermissionDenied {
122 backend_pid: u32,
124 },
125 #[error("DuplicateHandle failed for backend process {backend_pid}")]
127 DuplicateFailed {
128 backend_pid: u32,
130 raw_os_error: Option<i32>,
132 },
133 #[error("integrity mismatch duplicating handle into backend process {backend_pid}")]
135 IntegrityMismatch {
136 backend_pid: u32,
138 },
139 #[error("backend process {backend_pid} did not acknowledge duplicated handle")]
141 BackendAckTimeout {
142 backend_pid: u32,
144 },
145}
146
147#[cfg(windows)]
148fn platform_try_duplicate_handle(attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
149 use windows_sys::Win32::Foundation::{
150 CloseHandle, DuplicateHandle, DUPLICATE_SAME_ACCESS, HANDLE,
151 };
152 use windows_sys::Win32::System::Threading::{
153 GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE,
154 };
155
156 let backend_process = unsafe { OpenProcess(PROCESS_DUP_HANDLE, 0, attempt.backend_pid) };
157 if is_invalid_handle(backend_process) {
158 return Err(open_process_error(attempt.backend_pid));
159 }
160
161 let mut duplicated: HANDLE = std::ptr::null_mut();
162 let ok = unsafe {
163 DuplicateHandle(
164 GetCurrentProcess(),
165 attempt.pipe_handle.as_handle(),
166 backend_process,
167 &mut duplicated,
168 0,
169 0,
170 DUPLICATE_SAME_ACCESS,
171 )
172 };
173 let duplicate_error = std::io::Error::last_os_error();
174 unsafe {
175 CloseHandle(backend_process);
176 }
177
178 if ok == 0 || is_invalid_handle(duplicated) {
179 return Err(duplicate_handle_error(
180 attempt.backend_pid,
181 duplicate_error.raw_os_error(),
182 ));
183 }
184
185 Ok(DuplicateHandleSuccess::new(
186 WindowsHandleValue::from_handle(duplicated),
187 attempt.backend_pid,
188 attempt.handoff_token,
189 ))
190}
191
192#[cfg(not(windows))]
193fn platform_try_duplicate_handle(_attempt: &DuplicateHandleAttempt) -> DuplicateHandleResult {
194 Err(DuplicateHandleError::UnsupportedPlatform)
195}
196
197#[cfg(windows)]
198fn is_invalid_handle(handle: windows_sys::Win32::Foundation::HANDLE) -> bool {
199 use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
200
201 handle.is_null() || handle == INVALID_HANDLE_VALUE
202}
203
204#[cfg(windows)]
205fn open_process_error(backend_pid: u32) -> DuplicateHandleError {
206 use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED;
207
208 if std::io::Error::last_os_error().raw_os_error() == Some(ERROR_ACCESS_DENIED as i32) {
209 DuplicateHandleError::PermissionDenied { backend_pid }
210 } else {
211 DuplicateHandleError::CannotOpenBackend { backend_pid }
212 }
213}
214
215#[cfg(windows)]
216fn duplicate_handle_error(backend_pid: u32, raw_os_error: Option<i32>) -> DuplicateHandleError {
217 use windows_sys::Win32::Foundation::ERROR_ACCESS_DENIED;
218
219 if raw_os_error == Some(ERROR_ACCESS_DENIED as i32) {
220 DuplicateHandleError::PermissionDenied { backend_pid }
221 } else {
222 DuplicateHandleError::DuplicateFailed {
223 backend_pid,
224 raw_os_error,
225 }
226 }
227}
228
229#[cfg(all(test, windows))]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn duplicate_handle_into_current_process_returns_backend_owned_handle() {
235 use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
236 use windows_sys::Win32::System::Threading::GetCurrentProcess;
237
238 let token = HandoffToken::from_bytes([7; 16]);
239 let attempt = DuplicateHandleAttempt::new(
240 WindowsHandleValue::new(unsafe { GetCurrentProcess() } as usize),
241 std::process::id(),
242 token,
243 );
244
245 let success = try_duplicate_handle(&attempt).unwrap();
246
247 assert_eq!(success.backend_pid, std::process::id());
248 assert_eq!(success.handoff_token, token);
249 assert_ne!(success.duplicated_handle.get(), 0);
250 assert_ne!(
251 success.duplicated_handle.get(),
252 INVALID_HANDLE_VALUE as usize
253 );
254
255 unsafe {
256 CloseHandle(success.duplicated_handle.get() as HANDLE);
257 }
258 }
259
260 #[test]
261 fn missing_backend_pid_maps_to_fallback_safe_error() {
262 let attempt = DuplicateHandleAttempt::new(
263 WindowsHandleValue::new(unsafe {
264 windows_sys::Win32::System::Threading::GetCurrentProcess()
265 } as usize),
266 u32::MAX,
267 HandoffToken::from_bytes([9; 16]),
268 );
269
270 let err = try_duplicate_handle(&attempt).unwrap_err();
271
272 assert!(matches!(
273 err,
274 DuplicateHandleError::CannotOpenBackend { .. }
275 | DuplicateHandleError::PermissionDenied { .. }
276 ));
277 assert!(err.is_fallback_safe());
278 }
279}
280
281impl DuplicateHandleError {
282 pub fn attempt_failure(&self) -> Option<HandoffAttemptFailure> {
284 match self {
285 Self::UnsupportedPlatform => None,
286 Self::CannotOpenBackend { .. }
287 | Self::PermissionDenied { .. }
288 | Self::DuplicateFailed { .. } => Some(HandoffAttemptFailure::PermissionDenied),
289 Self::IntegrityMismatch { .. } => Some(HandoffAttemptFailure::IntegrityMismatch),
290 Self::BackendAckTimeout { .. } => Some(HandoffAttemptFailure::BackendAckTimeout),
291 }
292 }
293
294 pub fn fallback_reason(&self) -> HandoffFallbackReason {
296 match self.attempt_failure() {
297 Some(failure) => failure.into(),
298 None => HandoffFallbackReason::ServicePolicyDisabled,
299 }
300 }
301
302 pub fn fallback_decision(&self) -> HandoffFallbackDecision {
304 HandoffFallbackDecision::new(self.fallback_reason())
305 }
306
307 pub fn fallback_attempt_decision(&self) -> HandoffAttemptDecision {
309 HandoffAttemptDecision::FallbackToReconnect(self.fallback_decision())
310 }
311
312 pub fn is_fallback_safe(&self) -> bool {
314 let fallback = self.fallback_decision();
315 fallback.uses_backend_reconnect() && !fallback.sends_client_error()
316 }
317}