@@ -7,7 +7,7 @@ use crate::{
77 convert:: { IntoPyException , ToPyException , ToPyObject } ,
88 function:: { ArgumentError , FromArgs , FuncArgs } ,
99} ;
10- use std:: { ffi , fs, io, path:: Path } ;
10+ use std:: { fs, io, path:: Path } ;
1111
1212pub ( crate ) fn fs_metadata < P : AsRef < Path > > (
1313 path : P ,
@@ -112,7 +112,8 @@ pub(super) struct FollowSymlinks(
112112 #[ pyarg( named, name = "follow_symlinks" , default = true ) ] pub bool ,
113113) ;
114114
115- fn bytes_as_os_str < ' a > ( b : & ' a [ u8 ] , vm : & VirtualMachine ) -> PyResult < & ' a ffi:: OsStr > {
115+ #[ cfg( not( windows) ) ]
116+ fn bytes_as_os_str < ' a > ( b : & ' a [ u8 ] , vm : & VirtualMachine ) -> PyResult < & ' a std:: ffi:: OsStr > {
116117 rustpython_common:: os:: bytes_as_os_str ( b)
117118 . map_err ( |_| vm. new_unicode_decode_error ( "can't decode path for utf-8" ) )
118119}
@@ -160,7 +161,7 @@ pub(super) mod _os {
160161 suppress_iph,
161162 } ,
162163 convert:: { IntoPyException , ToPyObject } ,
163- function:: { ArgBytesLike , Either , FsPath , FuncArgs , OptionalArg } ,
164+ function:: { ArgBytesLike , FsPath , FuncArgs , OptionalArg } ,
164165 ospath:: { IOErrorBuilder , OsPath , OsPathOrFd , OutputMode } ,
165166 protocol:: PyIterReturn ,
166167 recursion:: ReprGuard ,
@@ -171,7 +172,7 @@ pub(super) mod _os {
171172 use crossbeam_utils:: atomic:: AtomicCell ;
172173 use itertools:: Itertools ;
173174 use std:: {
174- env, ffi , fs,
175+ env, fs,
175176 fs:: OpenOptions ,
176177 io,
177178 path:: PathBuf ,
@@ -403,7 +404,7 @@ pub(super) mod _os {
403404 b"." | b".." => None ,
404405 _ => Some (
405406 OutputMode :: String
406- . process_path ( ffi:: OsStr :: from_bytes ( fname) , vm) ,
407+ . process_path ( std :: ffi:: OsStr :: from_bytes ( fname) , vm) ,
407408 ) ,
408409 }
409410 } )
@@ -415,31 +416,62 @@ pub(super) mod _os {
415416 Ok ( list)
416417 }
417418
418- fn env_bytes_as_bytes ( obj : & Either < PyStrRef , PyBytesRef > ) -> & [ u8 ] {
419+ #[ cfg( not( windows) ) ]
420+ fn env_bytes_as_bytes ( obj : & crate :: function:: Either < PyStrRef , PyBytesRef > ) -> & [ u8 ] {
419421 match obj {
420- Either :: A ( s) => s. as_bytes ( ) ,
421- Either :: B ( b) => b. as_bytes ( ) ,
422+ crate :: function :: Either :: A ( s) => s. as_bytes ( ) ,
423+ crate :: function :: Either :: B ( b) => b. as_bytes ( ) ,
422424 }
423425 }
424426
425- /// Check if environment variable length exceeds Windows limit.
426- /// size should be key.len() + value.len() + 2 (for '=' and null terminator)
427427 #[ cfg( windows) ]
428- fn check_env_var_len ( size : usize , vm : & VirtualMachine ) -> PyResult < ( ) > {
428+ unsafe extern "C" {
429+ fn _wputenv ( envstring : * const u16 ) -> libc:: c_int ;
430+ }
431+
432+ /// Check if wide string length exceeds Windows environment variable limit.
433+ #[ cfg( windows) ]
434+ fn check_env_var_len ( wide_len : usize , vm : & VirtualMachine ) -> PyResult < ( ) > {
429435 use crate :: common:: windows:: _MAX_ENV;
430- if size > _MAX_ENV {
436+ if wide_len > _MAX_ENV + 1 {
431437 return Err ( vm. new_value_error ( format ! (
432- "the environment variable is longer than {} characters" ,
433- _MAX_ENV
438+ "the environment variable is longer than {_MAX_ENV} characters" ,
434439 ) ) ) ;
435440 }
436441 Ok ( ( ) )
437442 }
438443
444+ #[ cfg( windows) ]
445+ #[ pyfunction]
446+ fn putenv ( key : PyStrRef , value : PyStrRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
447+ let key_str = key. as_str ( ) ;
448+ let value_str = value. as_str ( ) ;
449+ // Search from index 1 because on Windows starting '=' is allowed for
450+ // defining hidden environment variables.
451+ if key_str. is_empty ( )
452+ || key_str. get ( 1 ..) . is_some_and ( |s| s. contains ( '=' ) )
453+ || key_str. contains ( '\0' )
454+ || value_str. contains ( '\0' )
455+ {
456+ return Err ( vm. new_value_error ( "illegal environment variable name" ) ) ;
457+ }
458+ let env_str = format ! ( "{}={}" , key_str, value_str) ;
459+ let wide = env_str. to_wide_with_nul ( ) ;
460+ check_env_var_len ( wide. len ( ) , vm) ?;
461+
462+ // Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ
463+ let result = unsafe { suppress_iph ! ( _wputenv( wide. as_ptr( ) ) ) } ;
464+ if result != 0 {
465+ return Err ( vm. new_last_errno_error ( ) ) ;
466+ }
467+ Ok ( ( ) )
468+ }
469+
470+ #[ cfg( not( windows) ) ]
439471 #[ pyfunction]
440472 fn putenv (
441- key : Either < PyStrRef , PyBytesRef > ,
442- value : Either < PyStrRef , PyBytesRef > ,
473+ key : crate :: function :: Either < PyStrRef , PyBytesRef > ,
474+ value : crate :: function :: Either < PyStrRef , PyBytesRef > ,
443475 vm : & VirtualMachine ,
444476 ) -> PyResult < ( ) > {
445477 let key = env_bytes_as_bytes ( & key) ;
@@ -450,17 +482,44 @@ pub(super) mod _os {
450482 if key. is_empty ( ) || key. contains ( & b'=' ) {
451483 return Err ( vm. new_value_error ( "illegal environment variable name" ) ) ;
452484 }
453- #[ cfg( windows) ]
454- check_env_var_len ( key. len ( ) + value. len ( ) + 2 , vm) ?;
455485 let key = super :: bytes_as_os_str ( key, vm) ?;
456486 let value = super :: bytes_as_os_str ( value, vm) ?;
457487 // SAFETY: requirements forwarded from the caller
458488 unsafe { env:: set_var ( key, value) } ;
459489 Ok ( ( ) )
460490 }
461491
492+ #[ cfg( windows) ]
493+ #[ pyfunction]
494+ fn unsetenv ( key : PyStrRef , vm : & VirtualMachine ) -> PyResult < ( ) > {
495+ let key_str = key. as_str ( ) ;
496+ // Search from index 1 because on Windows starting '=' is allowed for
497+ // defining hidden environment variables.
498+ if key_str. is_empty ( )
499+ || key_str. get ( 1 ..) . is_some_and ( |s| s. contains ( '=' ) )
500+ || key_str. contains ( '\0' )
501+ {
502+ return Err ( vm. new_value_error ( "illegal environment variable name" ) ) ;
503+ }
504+ // "key=" to unset (empty value removes the variable)
505+ let env_str = format ! ( "{}=" , key_str) ;
506+ let wide = env_str. to_wide_with_nul ( ) ;
507+ check_env_var_len ( wide. len ( ) , vm) ?;
508+
509+ // Use _wputenv like CPython (not SetEnvironmentVariableW) to update CRT environ
510+ let result = unsafe { suppress_iph ! ( _wputenv( wide. as_ptr( ) ) ) } ;
511+ if result != 0 {
512+ return Err ( vm. new_last_errno_error ( ) ) ;
513+ }
514+ Ok ( ( ) )
515+ }
516+
517+ #[ cfg( not( windows) ) ]
462518 #[ pyfunction]
463- fn unsetenv ( key : Either < PyStrRef , PyBytesRef > , vm : & VirtualMachine ) -> PyResult < ( ) > {
519+ fn unsetenv (
520+ key : crate :: function:: Either < PyStrRef , PyBytesRef > ,
521+ vm : & VirtualMachine ,
522+ ) -> PyResult < ( ) > {
464523 let key = env_bytes_as_bytes ( & key) ;
465524 if key. contains ( & b'\0' ) {
466525 return Err ( vm. new_value_error ( "embedded null byte" ) ) ;
@@ -474,9 +533,6 @@ pub(super) mod _os {
474533 ) ,
475534 ) ) ;
476535 }
477- // For unsetenv, size is key + '=' (no value, just clearing)
478- #[ cfg( windows) ]
479- check_env_var_len ( key. len ( ) + 1 , vm) ?;
480536 let key = super :: bytes_as_os_str ( key, vm) ?;
481537 // SAFETY: requirements forwarded from the caller
482538 unsafe { env:: remove_var ( key) } ;
@@ -984,7 +1040,7 @@ pub(super) mod _os {
9841040 OsPathOrFd :: Path ( path) => {
9851041 use rustpython_common:: os:: ffi:: OsStrExt ;
9861042 let path = path. as_ref ( ) . as_os_str ( ) . as_bytes ( ) ;
987- let path = match ffi:: CString :: new ( path) {
1043+ let path = match std :: ffi:: CString :: new ( path) {
9881044 Ok ( x) => x,
9891045 Err ( _) => return Ok ( None ) ,
9901046 } ;
@@ -1483,7 +1539,7 @@ pub(super) mod _os {
14831539
14841540 #[ pyfunction]
14851541 fn strerror ( e : i32 ) -> String {
1486- unsafe { ffi:: CStr :: from_ptr ( libc:: strerror ( e) ) }
1542+ unsafe { std :: ffi:: CStr :: from_ptr ( libc:: strerror ( e) ) }
14871543 . to_string_lossy ( )
14881544 . into_owned ( )
14891545 }
@@ -1587,7 +1643,7 @@ pub(super) mod _os {
15871643 if encoding. is_null( ) || encoding. read( ) == '\0' as libc:: c_char {
15881644 "UTF-8" . to_owned( )
15891645 } else {
1590- ffi:: CStr :: from_ptr( encoding) . to_string_lossy( ) . into_owned( )
1646+ std :: ffi:: CStr :: from_ptr( encoding) . to_string_lossy( ) . into_owned( )
15911647 }
15921648 } ;
15931649
0 commit comments