Skip to content

Commit 01cd08c

Browse files
committed
feat(fspy): wire blocking callbacks into the Windows supervisor
When Command::on_file_event is set, spawn now binds a WindowsCallbackServer on a uniquely named pipe, threads its name plus the access-mode mask into the Payload, and (after the child has been spawned suspended but before ResumeThread) hands the child's process handle to the server so DuplicateHandle on incoming callbacks can pull the target's raw HANDLE into this process. The wait task drains the callback server before locking the IPC channel so every in-flight callback completes.
1 parent 73fc362 commit 01cd08c

1 file changed

Lines changed: 32 additions & 6 deletions

File tree

crates/fspy/src/windows/mod.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88

99
use fspy_detours_sys::{DetourCopyPayloadToProcess, DetourUpdateProcessWithDll};
1010
use fspy_shared::{
11-
ipc::{PathAccess, channel::channel},
11+
ipc::{AccessMode, PathAccess, channel::channel},
1212
windows::{PAYLOAD_ID, Payload},
1313
};
1414
use futures_util::FutureExt;
@@ -22,6 +22,7 @@ use winsafe::co::{CP, WC};
2222

2323
use crate::{
2424
ChildTermination, TrackedChild,
25+
callback::windows::WindowsCallbackServer,
2526
command::Command,
2627
error::SpawnError,
2728
ipc::{OwnedReceiverLockGuard, SHM_CAPACITY},
@@ -39,11 +40,6 @@ impl PathAccessIterable {
3940
}
4041
}
4142

42-
// pub struct TracedProcess {
43-
// pub child: Child,
44-
// pub path_access_stream: PathAccessIter,
45-
// }
46-
4743
#[derive(Debug, Clone)]
4844
pub struct SpyImpl {
4945
ansi_dll_path_with_nul: Arc<CStr>,
@@ -73,6 +69,7 @@ impl SpyImpl {
7369
cancellation_token: CancellationToken,
7470
) -> Result<TrackedChild, SpawnError> {
7571
let ansi_dll_path_with_nul = Arc::clone(&self.ansi_dll_path_with_nul);
72+
let file_callback = command.file_callback.take();
7673
command.env("FSPY", "1");
7774
let mut command = command.into_tokio_command();
7875

@@ -81,8 +78,18 @@ impl SpyImpl {
8178
let (channel_conf, receiver) =
8279
channel(SHM_CAPACITY).map_err(SpawnError::ChannelCreation)?;
8380

81+
// Supervisor side of the optional blocking open/close callback.
82+
let callback_server = file_callback
83+
.as_ref()
84+
.map(|callback| WindowsCallbackServer::new(Arc::clone(&callback.callback)))
85+
.transpose()
86+
.map_err(SpawnError::CallbackChannelCreation)?;
87+
let callback_mask =
88+
file_callback.as_ref().map_or_else(AccessMode::empty, |callback| callback.mask);
89+
8490
let mut spawn_success = false;
8591
let spawn_success = &mut spawn_success;
92+
let callback_server_ref = callback_server.as_ref();
8693
let mut child = command
8794
.spawn_with(|std_command| {
8895
let std_child = std_command.spawn()?;
@@ -101,6 +108,9 @@ impl SpyImpl {
101108
let payload = Payload {
102109
channel_conf: channel_conf.clone(),
103110
ansi_dll_path_with_nul: ansi_dll_path_with_nul.to_bytes(),
111+
callback_pipe_name: callback_server_ref
112+
.map_or(&[][..], WindowsCallbackServer::pipe_name_bytes),
113+
callback_mask,
104114
};
105115
let payload_bytes = wincode::serialize(&payload).unwrap();
106116
// SAFETY: process_handle is valid, PAYLOAD_ID is a static GUID,
@@ -117,6 +127,18 @@ impl SpyImpl {
117127
return Err(io::Error::last_os_error());
118128
}
119129

130+
// Hand the child's process handle to the callback server
131+
// before resuming it, so handle duplication works as soon as
132+
// the child starts issuing callbacks.
133+
if let Some(server) = callback_server_ref {
134+
use std::os::windows::io::BorrowedHandle;
135+
// SAFETY: the child was just spawned; its raw handle is valid here.
136+
let borrowed = unsafe { BorrowedHandle::borrow_raw(std_child.as_raw_handle()) };
137+
if let Ok(owned) = borrowed.try_clone_to_owned() {
138+
server.set_process_handle(owned);
139+
}
140+
}
141+
120142
let main_thread_handle = std_child.main_thread_handle();
121143
// SAFETY: main_thread_handle is a valid thread handle from the spawned child
122144
let resume_thread_ret =
@@ -159,6 +181,10 @@ impl SpyImpl {
159181
child.wait().await?
160182
}
161183
};
184+
// Drain in-flight blocking callbacks before locking the channel.
185+
if let Some(callback_server) = callback_server {
186+
callback_server.shutdown().await;
187+
}
162188
// Lock the ipc channel after the child has exited.
163189
// We are not interested in path accesses from descendants after the main child has exited.
164190
let ipc_receiver_lock_guard = OwnedReceiverLockGuard::lock_async(receiver).await?;

0 commit comments

Comments
 (0)