@@ -228,7 +228,7 @@ pub(crate) mod _signal {
228228 }
229229
230230 #[ pyfunction]
231- fn set_wakeup_fd ( args : SetWakeupFdArgs , vm : & VirtualMachine ) -> PyResult < WakeupFdRaw > {
231+ fn set_wakeup_fd ( args : SetWakeupFdArgs , vm : & VirtualMachine ) -> PyResult < i64 > {
232232 // TODO: implement warn_on_full_buffer
233233 let _ = args. warn_on_full_buffer ;
234234 #[ cfg( windows) ]
@@ -264,6 +264,15 @@ pub(crate) mod _signal {
264264 if err. raw_os_error ( ) != Some ( WinSock :: WSAENOTSOCK ) {
265265 return Err ( err. into_pyexception ( vm) ) ;
266266 }
267+ // Validate that fd is a valid file descriptor using fstat
268+ // First check if SOCKET can be safely cast to i32 (file descriptor)
269+ let fd_i32 =
270+ i32:: try_from ( fd) . map_err ( |_| vm. new_value_error ( "invalid fd" . to_owned ( ) ) ) ?;
271+ // Verify the fd is valid by trying to fstat it
272+ let borrowed_fd =
273+ unsafe { crate :: common:: crt_fd:: Borrowed :: try_borrow_raw ( fd_i32) }
274+ . map_err ( |e| e. into_pyexception ( vm) ) ?;
275+ crate :: common:: fileutils:: fstat ( borrowed_fd) . map_err ( |e| e. into_pyexception ( vm) ) ?;
267276 }
268277 is_socket
269278 } else {
@@ -287,7 +296,18 @@ pub(crate) mod _signal {
287296 #[ cfg( windows) ]
288297 WAKEUP_IS_SOCKET . store ( is_socket, Ordering :: Relaxed ) ;
289298
290- Ok ( old_fd)
299+ #[ cfg( windows) ]
300+ {
301+ if old_fd == INVALID_WAKEUP {
302+ Ok ( -1 )
303+ } else {
304+ Ok ( old_fd as i64 )
305+ }
306+ }
307+ #[ cfg( not( windows) ) ]
308+ {
309+ Ok ( old_fd as i64 )
310+ }
291311 }
292312
293313 #[ cfg( all( unix, not( target_os = "redox" ) ) ) ]
@@ -302,6 +322,116 @@ pub(crate) mod _signal {
302322 }
303323 }
304324
325+ /// CPython: signal_raise_signal (signalmodule.c)
326+ #[ cfg( any( unix, windows) ) ]
327+ #[ pyfunction]
328+ fn raise_signal ( signalnum : i32 , vm : & VirtualMachine ) -> PyResult < ( ) > {
329+ signal:: assert_in_range ( signalnum, vm) ?;
330+
331+ // On Windows, only certain signals are supported
332+ #[ cfg( windows) ]
333+ {
334+ use crate :: convert:: IntoPyException ;
335+ // Windows supports: SIGINT(2), SIGILL(4), SIGFPE(8), SIGSEGV(11), SIGTERM(15), SIGABRT(22)
336+ const VALID_SIGNALS : & [ i32 ] = & [
337+ libc:: SIGINT ,
338+ libc:: SIGILL ,
339+ libc:: SIGFPE ,
340+ libc:: SIGSEGV ,
341+ libc:: SIGTERM ,
342+ libc:: SIGABRT ,
343+ ] ;
344+ if !VALID_SIGNALS . contains ( & signalnum) {
345+ return Err ( std:: io:: Error :: from_raw_os_error ( libc:: EINVAL ) . into_pyexception ( vm) ) ;
346+ }
347+ }
348+
349+ let res = unsafe { libc:: raise ( signalnum) } ;
350+ if res != 0 {
351+ return Err ( vm. new_os_error ( format ! ( "raise_signal failed for signal {}" , signalnum) ) ) ;
352+ }
353+
354+ // Check if a signal was triggered and handle it
355+ signal:: check_signals ( vm) ?;
356+
357+ Ok ( ( ) )
358+ }
359+
360+ /// CPython: signal_strsignal (signalmodule.c)
361+ #[ cfg( unix) ]
362+ #[ pyfunction]
363+ fn strsignal ( signalnum : i32 , vm : & VirtualMachine ) -> PyResult < Option < String > > {
364+ if signalnum < 1 || signalnum >= signal:: NSIG as i32 {
365+ return Err ( vm. new_value_error ( format ! ( "signal number {} out of range" , signalnum) ) ) ;
366+ }
367+ let s = unsafe { libc:: strsignal ( signalnum) } ;
368+ if s. is_null ( ) {
369+ Ok ( None )
370+ } else {
371+ let cstr = unsafe { std:: ffi:: CStr :: from_ptr ( s) } ;
372+ Ok ( Some ( cstr. to_string_lossy ( ) . into_owned ( ) ) )
373+ }
374+ }
375+
376+ #[ cfg( windows) ]
377+ #[ pyfunction]
378+ fn strsignal ( signalnum : i32 , vm : & VirtualMachine ) -> PyResult < Option < String > > {
379+ if signalnum < 1 || signalnum >= signal:: NSIG as i32 {
380+ return Err ( vm. new_value_error ( format ! ( "signal number {} out of range" , signalnum) ) ) ;
381+ }
382+ // Windows doesn't have strsignal(), provide our own mapping
383+ let name = match signalnum {
384+ libc:: SIGINT => "Interrupt" ,
385+ libc:: SIGILL => "Illegal instruction" ,
386+ libc:: SIGFPE => "Floating-point exception" ,
387+ libc:: SIGSEGV => "Segmentation fault" ,
388+ libc:: SIGTERM => "Terminated" ,
389+ libc:: SIGABRT => "Aborted" ,
390+ _ => return Ok ( None ) ,
391+ } ;
392+ Ok ( Some ( name. to_owned ( ) ) )
393+ }
394+
395+ /// CPython: signal_valid_signals (signalmodule.c)
396+ #[ pyfunction]
397+ fn valid_signals ( vm : & VirtualMachine ) -> PyResult {
398+ use crate :: PyPayload ;
399+ use crate :: builtins:: PySet ;
400+ let set = PySet :: default ( ) . into_ref ( & vm. ctx ) ;
401+ #[ cfg( unix) ]
402+ {
403+ // On Unix, most signals 1..NSIG are valid
404+ for signum in 1 ..signal:: NSIG {
405+ // Skip signals that cannot be caught
406+ #[ cfg( not( target_os = "wasi" ) ) ]
407+ if signum == libc:: SIGKILL as usize || signum == libc:: SIGSTOP as usize {
408+ continue ;
409+ }
410+ set. add ( vm. ctx . new_int ( signum as i32 ) . into ( ) , vm) ?;
411+ }
412+ }
413+ #[ cfg( windows) ]
414+ {
415+ // Windows only supports a limited set of signals
416+ for & signum in & [
417+ libc:: SIGINT ,
418+ libc:: SIGILL ,
419+ libc:: SIGFPE ,
420+ libc:: SIGSEGV ,
421+ libc:: SIGTERM ,
422+ libc:: SIGABRT ,
423+ ] {
424+ set. add ( vm. ctx . new_int ( signum) . into ( ) , vm) ?;
425+ }
426+ }
427+ #[ cfg( not( any( unix, windows) ) ) ]
428+ {
429+ // Empty set for platforms without signal support (e.g., WASM)
430+ let _ = & set;
431+ }
432+ Ok ( set. into ( ) )
433+ }
434+
305435 #[ cfg( any( unix, windows) ) ]
306436 pub extern "C" fn run_signal ( signum : i32 ) {
307437 signal:: TRIGGERS [ signum as usize ] . store ( true , Ordering :: Relaxed ) ;
0 commit comments