@@ -297,10 +297,7 @@ mod unix {
297297 clear_errno ( ) ;
298298 let entry = unsafe { libc:: readdir ( guard. 0 ) } ;
299299 if entry. is_null ( ) {
300- let errno = current_errno ( ) ;
301- if errno != 0 {
302- return Err ( map_io ( "fs::enumerate" , io:: Error :: from_raw_os_error ( errno) ) ) ;
303- }
300+ classify_readdir_end ( errno_abi ( ) ) ?;
304301 break ;
305302 }
306303 let name = unsafe { CStr :: from_ptr ( ( * entry) . d_name . as_ptr ( ) ) } ;
@@ -370,16 +367,66 @@ mod unix {
370367 unsafe {
371368 * libc:: __errno_location ( ) = 0 ;
372369 }
370+ #[ cfg( any(
371+ target_os = "dragonfly" ,
372+ target_os = "freebsd" ,
373+ target_os = "ios" ,
374+ target_os = "macos" ,
375+ target_os = "netbsd" ,
376+ target_os = "openbsd"
377+ ) ) ]
378+ unsafe {
379+ * libc:: __error ( ) = 0 ;
380+ }
373381 }
374382
375- fn current_errno ( ) -> i32 {
383+ enum ErrnoAbi {
384+ Known ( i32 ) ,
385+ #[ allow( dead_code) ]
386+ Unsupported ,
387+ }
388+
389+ fn errno_abi ( ) -> ErrnoAbi {
376390 #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
377391 {
378- unsafe { * libc:: __errno_location ( ) }
379- }
380- #[ cfg( not( any( target_os = "linux" , target_os = "android" ) ) ) ]
392+ ErrnoAbi :: Known ( unsafe { * libc:: __errno_location ( ) } )
393+ }
394+ #[ cfg( any(
395+ target_os = "dragonfly" ,
396+ target_os = "freebsd" ,
397+ target_os = "ios" ,
398+ target_os = "macos" ,
399+ target_os = "netbsd" ,
400+ target_os = "openbsd"
401+ ) ) ]
381402 {
382- 0
403+ ErrnoAbi :: Known ( unsafe { * libc:: __error ( ) } )
404+ }
405+ #[ cfg( not( any(
406+ target_os = "linux" ,
407+ target_os = "android" ,
408+ target_os = "dragonfly" ,
409+ target_os = "freebsd" ,
410+ target_os = "ios" ,
411+ target_os = "macos" ,
412+ target_os = "netbsd" ,
413+ target_os = "openbsd"
414+ ) ) ) ]
415+ {
416+ ErrnoAbi :: Unsupported
417+ }
418+ }
419+
420+ fn classify_readdir_end ( errno : ErrnoAbi ) -> Result < ( ) , CapabilityError > {
421+ match errno {
422+ ErrnoAbi :: Known ( 0 ) => Ok ( ( ) ) ,
423+ ErrnoAbi :: Known ( code) => {
424+ Err ( map_io ( "fs::enumerate" , io:: Error :: from_raw_os_error ( code) ) )
425+ }
426+ ErrnoAbi :: Unsupported => Err ( CapabilityError :: new (
427+ "unsupported_platform" ,
428+ "readdir errno is unavailable on this target" ,
429+ ) ) ,
383430 }
384431 }
385432
@@ -567,4 +614,73 @@ mod unix {
567614 } ;
568615 CapabilityError :: new ( code, format ! ( "{operation}: {error}" ) )
569616 }
617+
618+ #[ cfg( test) ]
619+ mod errno_tests {
620+ use super :: * ;
621+
622+ #[ test]
623+ fn readdir_end_never_treats_unknown_errno_abi_as_eof ( ) {
624+ assert ! ( classify_readdir_end( ErrnoAbi :: Known ( 0 ) ) . is_ok( ) ) ;
625+ let error = classify_readdir_end ( ErrnoAbi :: Known ( 5 ) )
626+ . expect_err ( "nonzero errno must not be treated as EOF" ) ;
627+ assert_ne ! ( error. code( ) , "unsupported_platform" ) ;
628+ let unsupported = classify_readdir_end ( ErrnoAbi :: Unsupported )
629+ . expect_err ( "missing errno ABI must fail closed" ) ;
630+ assert_eq ! ( unsupported. code( ) , "unsupported_platform" ) ;
631+ }
632+
633+ #[ cfg( any( target_os = "linux" , target_os = "android" ) ) ]
634+ #[ test]
635+ fn linux_errno_location_clears_and_reads_zero ( ) {
636+ clear_errno ( ) ;
637+ assert ! ( matches!( errno_abi( ) , ErrnoAbi :: Known ( 0 ) ) ) ;
638+ }
639+
640+ #[ cfg( any(
641+ target_os = "dragonfly" ,
642+ target_os = "freebsd" ,
643+ target_os = "ios" ,
644+ target_os = "macos" ,
645+ target_os = "netbsd" ,
646+ target_os = "openbsd"
647+ ) ) ]
648+ #[ test]
649+ fn bsd_errno_error_clears_and_reads_zero ( ) {
650+ clear_errno ( ) ;
651+ assert ! ( matches!( errno_abi( ) , ErrnoAbi :: Known ( 0 ) ) ) ;
652+ }
653+
654+ #[ test]
655+ fn current_target_does_not_report_unsupported_when_accessor_exists ( ) {
656+ match errno_abi ( ) {
657+ ErrnoAbi :: Known ( _) => {
658+ #[ cfg( not( any(
659+ target_os = "linux" ,
660+ target_os = "android" ,
661+ target_os = "dragonfly" ,
662+ target_os = "freebsd" ,
663+ target_os = "ios" ,
664+ target_os = "macos" ,
665+ target_os = "netbsd" ,
666+ target_os = "openbsd"
667+ ) ) ) ]
668+ panic ! ( "unsupported Unix target must fail closed" ) ;
669+ }
670+ ErrnoAbi :: Unsupported => {
671+ #[ cfg( any(
672+ target_os = "linux" ,
673+ target_os = "android" ,
674+ target_os = "dragonfly" ,
675+ target_os = "freebsd" ,
676+ target_os = "ios" ,
677+ target_os = "macos" ,
678+ target_os = "netbsd" ,
679+ target_os = "openbsd"
680+ ) ) ]
681+ panic ! ( "supported target must expose a real errno accessor" ) ;
682+ }
683+ }
684+ }
685+ }
570686}
0 commit comments