@@ -35,7 +35,31 @@ pub(crate) mod module {
3535 } ;
3636
3737 #[ pyattr]
38- use libc:: { O_BINARY , O_TEMPORARY } ;
38+ use libc:: { O_BINARY , O_NOINHERIT , O_RANDOM , O_SEQUENTIAL , O_TEMPORARY , O_TEXT } ;
39+
40+ // Windows spawn mode constants
41+ #[ pyattr]
42+ const P_WAIT : i32 = 0 ;
43+ #[ pyattr]
44+ const P_NOWAIT : i32 = 1 ;
45+ #[ pyattr]
46+ const P_OVERLAY : i32 = 2 ;
47+ #[ pyattr]
48+ const P_NOWAITO : i32 = 3 ;
49+ #[ pyattr]
50+ const P_DETACH : i32 = 4 ;
51+
52+ // _O_SHORT_LIVED is not in libc, define manually
53+ #[ pyattr]
54+ const O_SHORT_LIVED : i32 = 0x1000 ;
55+
56+ // Exit code constant
57+ #[ pyattr]
58+ const EX_OK : i32 = 0 ;
59+
60+ // Maximum number of temporary files
61+ #[ pyattr]
62+ const TMP_MAX : i32 = i32:: MAX ;
3963
4064 #[ pyattr]
4165 use windows_sys:: Win32 :: System :: LibraryLoader :: {
@@ -138,19 +162,22 @@ pub(crate) mod module {
138162
139163 #[ cfg( target_env = "msvc" ) ]
140164 #[ pyfunction]
141- fn waitpid ( pid : intptr_t , opt : i32 , vm : & VirtualMachine ) -> PyResult < ( intptr_t , i32 ) > {
142- let mut status = 0 ;
165+ fn waitpid ( pid : intptr_t , opt : i32 , vm : & VirtualMachine ) -> PyResult < ( intptr_t , u64 ) > {
166+ let mut status: i32 = 0 ;
143167 let pid = unsafe { suppress_iph ! ( _cwait( & mut status, pid, opt) ) } ;
144168 if pid == -1 {
145169 Err ( errno_err ( vm) )
146170 } else {
147- Ok ( ( pid, status << 8 ) )
171+ // Cast to unsigned to handle large exit codes (like 0xC000013A)
172+ // then shift left by 8 to match POSIX waitpid format
173+ let ustatus = ( status as u32 ) as u64 ;
174+ Ok ( ( pid, ustatus << 8 ) )
148175 }
149176 }
150177
151178 #[ cfg( target_env = "msvc" ) ]
152179 #[ pyfunction]
153- fn wait ( vm : & VirtualMachine ) -> PyResult < ( intptr_t , i32 ) > {
180+ fn wait ( vm : & VirtualMachine ) -> PyResult < ( intptr_t , u64 ) > {
154181 waitpid ( -1 , 0 , vm)
155182 }
156183
@@ -212,12 +239,143 @@ pub(crate) mod module {
212239 #[ cfg( target_env = "msvc" ) ]
213240 unsafe extern "C" {
214241 fn _wexecv ( cmdname : * const u16 , argv : * const * const u16 ) -> intptr_t ;
242+ fn _wexecve (
243+ cmdname : * const u16 ,
244+ argv : * const * const u16 ,
245+ envp : * const * const u16 ,
246+ ) -> intptr_t ;
247+ fn _wspawnv ( mode : i32 , cmdname : * const u16 , argv : * const * const u16 ) -> intptr_t ;
248+ fn _wspawnve (
249+ mode : i32 ,
250+ cmdname : * const u16 ,
251+ argv : * const * const u16 ,
252+ envp : * const * const u16 ,
253+ ) -> intptr_t ;
254+ }
255+
256+ #[ cfg( target_env = "msvc" ) ]
257+ #[ pyfunction]
258+ fn spawnv (
259+ mode : i32 ,
260+ path : OsPath ,
261+ argv : Either < PyListRef , PyTupleRef > ,
262+ vm : & VirtualMachine ,
263+ ) -> PyResult < intptr_t > {
264+ use std:: iter:: once;
265+
266+ let make_widestring =
267+ |s : & str | widestring:: WideCString :: from_os_str ( s) . map_err ( |err| err. to_pyexception ( vm) ) ;
268+
269+ let path = path. to_wide_cstring ( vm) ?;
270+
271+ let argv = vm. extract_elements_with ( argv. as_ref ( ) , |obj| {
272+ let arg = PyStrRef :: try_from_object ( vm, obj) ?;
273+ make_widestring ( arg. as_str ( ) )
274+ } ) ?;
275+
276+ let first = argv
277+ . first ( )
278+ . ok_or_else ( || vm. new_value_error ( "spawnv() arg 3 must not be empty" ) ) ?;
279+
280+ if first. is_empty ( ) {
281+ return Err ( vm. new_value_error ( "spawnv() arg 3 first element cannot be empty" ) ) ;
282+ }
283+
284+ let argv_spawn: Vec < * const u16 > = argv
285+ . iter ( )
286+ . map ( |v| v. as_ptr ( ) )
287+ . chain ( once ( std:: ptr:: null ( ) ) )
288+ . collect ( ) ;
289+
290+ let result = unsafe { suppress_iph ! ( _wspawnv( mode, path. as_ptr( ) , argv_spawn. as_ptr( ) ) ) } ;
291+ if result == -1 {
292+ Err ( errno_err ( vm) )
293+ } else {
294+ Ok ( result)
295+ }
296+ }
297+
298+ #[ cfg( target_env = "msvc" ) ]
299+ #[ pyfunction]
300+ fn spawnve (
301+ mode : i32 ,
302+ path : OsPath ,
303+ argv : Either < PyListRef , PyTupleRef > ,
304+ env : PyDictRef ,
305+ vm : & VirtualMachine ,
306+ ) -> PyResult < intptr_t > {
307+ use std:: iter:: once;
308+
309+ let make_widestring =
310+ |s : & str | widestring:: WideCString :: from_os_str ( s) . map_err ( |err| err. to_pyexception ( vm) ) ;
311+
312+ let path = path. to_wide_cstring ( vm) ?;
313+
314+ let argv = vm. extract_elements_with ( argv. as_ref ( ) , |obj| {
315+ let arg = PyStrRef :: try_from_object ( vm, obj) ?;
316+ make_widestring ( arg. as_str ( ) )
317+ } ) ?;
318+
319+ let first = argv
320+ . first ( )
321+ . ok_or_else ( || vm. new_value_error ( "spawnve() arg 2 cannot be empty" ) ) ?;
322+
323+ if first. is_empty ( ) {
324+ return Err ( vm. new_value_error ( "spawnve() arg 2 first element cannot be empty" ) ) ;
325+ }
326+
327+ let argv_spawn: Vec < * const u16 > = argv
328+ . iter ( )
329+ . map ( |v| v. as_ptr ( ) )
330+ . chain ( once ( std:: ptr:: null ( ) ) )
331+ . collect ( ) ;
332+
333+ // Build environment strings as "KEY=VALUE\0" wide strings
334+ let mut env_strings: Vec < widestring:: WideCString > = Vec :: new ( ) ;
335+ for ( key, value) in env. into_iter ( ) {
336+ let key = PyStrRef :: try_from_object ( vm, key) ?;
337+ let value = PyStrRef :: try_from_object ( vm, value) ?;
338+ let key_str = key. as_str ( ) ;
339+ let value_str = value. as_str ( ) ;
340+
341+ // Validate: no null characters in key or value
342+ if key_str. contains ( '\0' ) || value_str. contains ( '\0' ) {
343+ return Err ( vm. new_value_error ( "embedded null character" ) ) ;
344+ }
345+ // Validate: no '=' in key
346+ if key_str. contains ( '=' ) {
347+ return Err ( vm. new_value_error ( "illegal environment variable name" ) ) ;
348+ }
349+
350+ let env_str = format ! ( "{}={}" , key_str, value_str) ;
351+ env_strings. push ( make_widestring ( & env_str) ?) ;
352+ }
353+
354+ let envp: Vec < * const u16 > = env_strings
355+ . iter ( )
356+ . map ( |s| s. as_ptr ( ) )
357+ . chain ( once ( std:: ptr:: null ( ) ) )
358+ . collect ( ) ;
359+
360+ let result = unsafe {
361+ suppress_iph ! ( _wspawnve(
362+ mode,
363+ path. as_ptr( ) ,
364+ argv_spawn. as_ptr( ) ,
365+ envp. as_ptr( )
366+ ) )
367+ } ;
368+ if result == -1 {
369+ Err ( errno_err ( vm) )
370+ } else {
371+ Ok ( result)
372+ }
215373 }
216374
217375 #[ cfg( target_env = "msvc" ) ]
218376 #[ pyfunction]
219377 fn execv (
220- path : PyStrRef ,
378+ path : OsPath ,
221379 argv : Either < PyListRef , PyTupleRef > ,
222380 vm : & VirtualMachine ,
223381 ) -> PyResult < ( ) > {
@@ -226,7 +384,7 @@ pub(crate) mod module {
226384 let make_widestring =
227385 |s : & str | widestring:: WideCString :: from_os_str ( s) . map_err ( |err| err. to_pyexception ( vm) ) ;
228386
229- let path = make_widestring ( path. as_str ( ) ) ?;
387+ let path = path. to_wide_cstring ( vm ) ?;
230388
231389 let argv = vm. extract_elements_with ( argv. as_ref ( ) , |obj| {
232390 let arg = PyStrRef :: try_from_object ( vm, obj) ?;
@@ -254,6 +412,76 @@ pub(crate) mod module {
254412 }
255413 }
256414
415+ #[ cfg( target_env = "msvc" ) ]
416+ #[ pyfunction]
417+ fn execve (
418+ path : OsPath ,
419+ argv : Either < PyListRef , PyTupleRef > ,
420+ env : PyDictRef ,
421+ vm : & VirtualMachine ,
422+ ) -> PyResult < ( ) > {
423+ use std:: iter:: once;
424+
425+ let make_widestring =
426+ |s : & str | widestring:: WideCString :: from_os_str ( s) . map_err ( |err| err. to_pyexception ( vm) ) ;
427+
428+ let path = path. to_wide_cstring ( vm) ?;
429+
430+ let argv = vm. extract_elements_with ( argv. as_ref ( ) , |obj| {
431+ let arg = PyStrRef :: try_from_object ( vm, obj) ?;
432+ make_widestring ( arg. as_str ( ) )
433+ } ) ?;
434+
435+ let first = argv
436+ . first ( )
437+ . ok_or_else ( || vm. new_value_error ( "execve: argv must not be empty" ) ) ?;
438+
439+ if first. is_empty ( ) {
440+ return Err ( vm. new_value_error ( "execve: argv first element cannot be empty" ) ) ;
441+ }
442+
443+ let argv_execve: Vec < * const u16 > = argv
444+ . iter ( )
445+ . map ( |v| v. as_ptr ( ) )
446+ . chain ( once ( std:: ptr:: null ( ) ) )
447+ . collect ( ) ;
448+
449+ // Build environment strings as "KEY=VALUE\0" wide strings
450+ let mut env_strings: Vec < widestring:: WideCString > = Vec :: new ( ) ;
451+ for ( key, value) in env. into_iter ( ) {
452+ let key = PyStrRef :: try_from_object ( vm, key) ?;
453+ let value = PyStrRef :: try_from_object ( vm, value) ?;
454+ let key_str = key. as_str ( ) ;
455+ let value_str = value. as_str ( ) ;
456+
457+ // Validate: no null characters in key or value
458+ if key_str. contains ( '\0' ) || value_str. contains ( '\0' ) {
459+ return Err ( vm. new_value_error ( "embedded null character" ) ) ;
460+ }
461+ // Validate: no '=' in key
462+ if key_str. contains ( '=' ) {
463+ return Err ( vm. new_value_error ( "illegal environment variable name" ) ) ;
464+ }
465+
466+ let env_str = format ! ( "{}={}" , key_str, value_str) ;
467+ env_strings. push ( make_widestring ( & env_str) ?) ;
468+ }
469+
470+ let envp: Vec < * const u16 > = env_strings
471+ . iter ( )
472+ . map ( |s| s. as_ptr ( ) )
473+ . chain ( once ( std:: ptr:: null ( ) ) )
474+ . collect ( ) ;
475+
476+ if ( unsafe { suppress_iph ! ( _wexecve( path. as_ptr( ) , argv_execve. as_ptr( ) , envp. as_ptr( ) ) ) }
477+ == -1 )
478+ {
479+ Err ( errno_err ( vm) )
480+ } else {
481+ Ok ( ( ) )
482+ }
483+ }
484+
257485 #[ pyfunction]
258486 fn _getfinalpathname ( path : OsPath , vm : & VirtualMachine ) -> PyResult {
259487 let real = path
@@ -464,18 +692,59 @@ pub(crate) mod module {
464692 raw_set_handle_inheritable ( handle, inheritable) . map_err ( |e| e. to_pyexception ( vm) )
465693 }
466694
467- #[ pyfunction]
468- fn mkdir (
695+ #[ derive( FromArgs ) ]
696+ struct MkdirArgs < ' a > {
697+ #[ pyarg( any) ]
469698 path : OsPath ,
470- mode : OptionalArg < i32 > ,
471- dir_fd : DirFd < ' _ , { _os:: MKDIR_DIR_FD as usize } > ,
472- vm : & VirtualMachine ,
473- ) -> PyResult < ( ) > {
474- let mode = mode. unwrap_or ( 0o777 ) ;
475- let [ ] = dir_fd. 0 ;
476- let _ = mode;
477- let wide = path. to_wide_cstring ( vm) ?;
478- let res = unsafe { FileSystem :: CreateDirectoryW ( wide. as_ptr ( ) , std:: ptr:: null_mut ( ) ) } ;
699+ #[ pyarg( any, default = 0o777 ) ]
700+ mode : i32 ,
701+ #[ pyarg( flatten) ]
702+ dir_fd : DirFd < ' a , { _os:: MKDIR_DIR_FD as usize } > ,
703+ }
704+
705+ #[ pyfunction]
706+ fn mkdir ( args : MkdirArgs < ' _ > , vm : & VirtualMachine ) -> PyResult < ( ) > {
707+ use windows_sys:: Win32 :: Foundation :: LocalFree ;
708+ use windows_sys:: Win32 :: Security :: Authorization :: {
709+ ConvertStringSecurityDescriptorToSecurityDescriptorW , SDDL_REVISION_1 ,
710+ } ;
711+ use windows_sys:: Win32 :: Security :: SECURITY_ATTRIBUTES ;
712+
713+ let [ ] = args. dir_fd . 0 ;
714+ let wide = args. path . to_wide_cstring ( vm) ?;
715+
716+ // CPython special case: mode 0o700 sets a protected ACL
717+ let res = if args. mode == 0o700 {
718+ let mut sec_attr = SECURITY_ATTRIBUTES {
719+ nLength : std:: mem:: size_of :: < SECURITY_ATTRIBUTES > ( ) as u32 ,
720+ lpSecurityDescriptor : std:: ptr:: null_mut ( ) ,
721+ bInheritHandle : 0 ,
722+ } ;
723+ // Set a discretionary ACL (D) that is protected (P) and includes
724+ // inheritable (OICI) entries that allow (A) full control (FA) to
725+ // SYSTEM (SY), Administrators (BA), and the owner (OW).
726+ let sddl: Vec < u16 > = "D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW)\0 "
727+ . encode_utf16 ( )
728+ . collect ( ) ;
729+ let convert_result = unsafe {
730+ ConvertStringSecurityDescriptorToSecurityDescriptorW (
731+ sddl. as_ptr ( ) ,
732+ SDDL_REVISION_1 ,
733+ & mut sec_attr. lpSecurityDescriptor ,
734+ std:: ptr:: null_mut ( ) ,
735+ )
736+ } ;
737+ if convert_result == 0 {
738+ return Err ( errno_err ( vm) ) ;
739+ }
740+ let res =
741+ unsafe { FileSystem :: CreateDirectoryW ( wide. as_ptr ( ) , & sec_attr as * const _ as _ ) } ;
742+ unsafe { LocalFree ( sec_attr. lpSecurityDescriptor ) } ;
743+ res
744+ } else {
745+ unsafe { FileSystem :: CreateDirectoryW ( wide. as_ptr ( ) , std:: ptr:: null_mut ( ) ) }
746+ } ;
747+
479748 if res == 0 {
480749 return Err ( errno_err ( vm) ) ;
481750 }
0 commit comments