@@ -431,22 +431,41 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) {
431431 }
432432
433433
434+ [ SupportedOSPlatform ( "linux" ) , SupportedOSPlatform ( "osx" ) ]
434435 private static int UnixDup ( int fd , int fd2 , out Stream ? stream ) {
435436 int res = fd2 < 0 ? Mono . Unix . Native . Syscall . dup ( fd ) : Mono . Unix . Native . Syscall . dup2 ( fd , fd2 ) ;
436437 if ( res < 0 ) throw GetLastUnixError ( ) ;
437438 if ( ClrModule . IsMono ) {
438- // This does not work on .NET, probably because .NET FileStream is not aware of Mono.Unix.UnixStream
439- stream = new Mono . Unix . UnixStream ( res , ownsHandle : true ) ;
439+ // Elaborate workaround on Mono to avoid UnixStream as out
440+ stream = new Mono . Unix . UnixStream ( res , ownsHandle : false ) ;
441+ FileAccess fileAccess = stream . CanWrite ? stream . CanRead ? FileAccess . ReadWrite : FileAccess . Write : FileAccess . Read ;
442+ stream . Dispose ( ) ;
443+ try {
444+ // FileStream on Mono created with a file descriptor might not work: https://github.com/mono/mono/issues/12783
445+ // Test if it does, without closing the handle if it doesn't
446+ var sfh = new SafeFileHandle ( ( IntPtr ) res , ownsHandle : false ) ;
447+ stream = new FileStream ( sfh , fileAccess ) ;
448+ // No exception? Great! We can use FileStream.
449+ stream . Dispose ( ) ;
450+ sfh . Dispose ( ) ;
451+ stream = null ; // Create outside of try block
452+ } catch ( IOException ) {
453+ // Fall back to UnixStream
454+ stream = new Mono . Unix . UnixStream ( res , ownsHandle : true ) ;
455+ }
456+ if ( stream is null ) {
457+ // FileStream is safe
458+ var sfh = new SafeFileHandle ( ( IntPtr ) res , ownsHandle : true ) ;
459+ stream = new FileStream ( sfh , fileAccess ) ;
460+ }
440461 } else {
441- // This does not work 100% correctly on .NET, probably because each FileStream has its own read/write cursor
442- // (it should be shared between dupped descriptors)
443- //stream = new FileStream(new SafeFileHandle((IntPtr)res, ownsHandle: true), FileAccess.ReadWrite);
444- // Accidentaly, this would also not work on Mono: https://github.com/mono/mono/issues/12783
445- stream = null ; // Handle stream sharing in PythonFileManager
462+ // normal case
463+ stream = new PosixFileStream ( res ) ;
446464 }
447465 return res ;
448466 }
449467
468+
450469#if FEATURE_PROCESS
451470 /// <summary>
452471 /// single instance of environment dictionary is shared between multiple runtimes because the environment
@@ -470,6 +489,9 @@ public static object fstat(CodeContext/*!*/ context, int fd) {
470489 PythonFileManager fileManager = context . LanguageContext . FileManager ;
471490
472491 if ( fileManager . TryGetStreams ( fd , out StreamBox ? streams ) ) {
492+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
493+ return fstatUnix ( fd ) ;
494+ }
473495 if ( streams . IsConsoleStream ( ) ) return new stat_result ( 0x2000 ) ;
474496 if ( streams . IsStandardIOStream ( ) ) return new stat_result ( 0x1000 ) ;
475497 if ( StatStream ( streams . ReadStream ) is not null and var res ) return res ;
@@ -483,15 +505,9 @@ public static object fstat(CodeContext/*!*/ context, int fd) {
483505#endif
484506 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
485507 if ( ReferenceEquals ( stream , Stream . Null ) ) return new stat_result ( 0x2000 ) ;
486- } else if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
487- if ( IsUnixStream ( stream ) ) return new stat_result ( 0x1000 ) ;
488508 }
489509 return null ;
490510 }
491-
492- static bool IsUnixStream ( Stream stream ) {
493- return stream is Mono . Unix . UnixStream ;
494- }
495511 }
496512
497513 public static void fsync ( CodeContext context , int fd ) {
@@ -867,9 +883,16 @@ public static void mkdir(CodeContext context, [NotNone] Bytes path, [ParamDictio
867883 public static void mkdir ( CodeContext context , object ? path , [ ParamDictionary , NotNone ] IDictionary < string , object > kwargs , [ NotNone ] params object [ ] args )
868884 => mkdir ( ConvertToFsString ( context , path , nameof ( path ) ) , kwargs , args ) ;
869885
870- private const int DefaultBufferSize = 4096 ;
886+ [ Documentation ( """
887+ open(path, flags, mode=511, *, dir_fd=None)
888+
889+ Open a file for low level IO. Returns a file descriptor (integer).
871890
872- [ Documentation ( "open(path, flags, mode=511, *, dir_fd=None)" ) ]
891+ If dir_fd is not None, it should be a file descriptor open to a directory,
892+ and path should be relative; path will then be relative to that directory.
893+ dir_fd may not be implemented on your platform.
894+ If it is unavailable, using it will raise a NotImplementedError.
895+ """ ) ]
873896 public static object open ( CodeContext /*!*/ context , [ NotNone ] string path , int flags , [ ParamDictionary , NotNone ] IDictionary < string , object > kwargs , [ NotNone ] params object [ ] args ) {
874897 var numArgs = args . Length ;
875898 CheckOptionalArgsCount ( numRegParms : 2 , numOptPosParms : 1 , numKwParms : 1 , numArgs , kwargs . Count ) ;
@@ -889,12 +912,28 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
889912 }
890913 }
891914
915+ if ( ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) && ! ClrModule . IsMono ) {
916+ // Use PosixFileStream to operate on fd directly
917+ // On Mono, we must use FileStream due to limitations in MemoryMappedFile
918+ Stream s = PosixFileStream . Open ( path , flags , unchecked ( ( uint ) mode ) , out int fd ) ;
919+ if ( ( flags & O_APPEND ) != 0 ) {
920+ s . Seek ( 0L , SeekOrigin . End ) ;
921+ }
922+ return context . LanguageContext . FileManager . Add ( fd , new ( s ) ) ;
923+ }
924+
892925 try {
926+ // FileStream buffer size must be >= 0 on .NET, and >= 1 on .NET Framework and Mono.
927+ // On .NET, buffer size 0 or 1 disables buffering.
928+ // On .NET Framework, buffer size 1 disables buffering.
929+ // On Mono, buffer size 1 makes writes of length >= 2 bypass the buffer.
930+ const int NoBuffering = 1 ;
931+
893932 FileMode fileMode = FileModeFromFlags ( flags ) ;
894933 FileAccess access = FileAccessFromFlags ( flags ) ;
895934 FileOptions options = FileOptionsFromFlags ( flags ) ;
896935 Stream s ; // the stream opened to acces the file
897- FileStream ? fs ; // downcast of s if s is FileStream (this is always the case on POSIX)
936+ FileStream ? fs ; // downcast of s if s is FileStream
898937 Stream ? rs = null ; // secondary read stream if needed, otherwise same as s
899938 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) && IsNulFile ( path ) ) {
900939 fs = null ;
@@ -904,15 +943,15 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
904943 // open it again w/ just read access.
905944 fs = new FileStream ( path , fileMode , FileAccess . Write , FileShare . None ) ;
906945 fs . Close ( ) ;
907- s = fs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , DefaultBufferSize , options ) ;
946+ s = fs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , NoBuffering , options ) ;
908947 } else if ( access == FileAccess . ReadWrite && fileMode == FileMode . Append ) {
909948 // .NET doesn't allow Append w/ access != Write, so open the file w/ Write
910949 // and a secondary stream w/ Read, then seek to the end.
911- s = fs = new FileStream ( path , FileMode . Append , FileAccess . Write , FileShare . ReadWrite , DefaultBufferSize , options ) ;
912- rs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , DefaultBufferSize , options ) ;
950+ s = fs = new FileStream ( path , FileMode . Append , FileAccess . Write , FileShare . ReadWrite , NoBuffering , options ) ;
951+ rs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , NoBuffering , options ) ;
913952 rs . Seek ( 0L , SeekOrigin . End ) ;
914953 } else {
915- s = fs = new FileStream ( path , fileMode , access , FileShare . ReadWrite , DefaultBufferSize , options ) ;
954+ s = fs = new FileStream ( path , fileMode , access , FileShare . ReadWrite , NoBuffering , options ) ;
916955 }
917956 rs ??= s ;
918957
@@ -1436,6 +1475,13 @@ private static object statUnix(string path) {
14361475 return LightExceptions . Throw ( GetLastUnixError ( path ) ) ;
14371476 }
14381477
1478+ private static object fstatUnix ( int fd ) {
1479+ if ( Mono . Unix . Native . Syscall . fstat ( fd , out Mono . Unix . Native . Stat buf ) == 0 ) {
1480+ return new stat_result ( buf ) ;
1481+ }
1482+ return LightExceptions . Throw ( GetLastUnixError ( ) ) ;
1483+ }
1484+
14391485 private const int OPEN_EXISTING = 3 ;
14401486 private const int FILE_ATTRIBUTE_NORMAL = 0x00000080 ;
14411487 private const int FILE_READ_ATTRIBUTES = 0x0080 ;
@@ -1669,8 +1715,27 @@ public static void truncate(CodeContext context, object? path, BigInteger length
16691715 public static void truncate ( CodeContext context , int fd , BigInteger length )
16701716 => ftruncate ( context , fd , length ) ;
16711717
1672- public static void ftruncate ( CodeContext context , int fd , BigInteger length )
1673- => context . LanguageContext . FileManager . GetStreams ( fd ) . Truncate ( ( long ) length ) ;
1718+ public static void ftruncate ( CodeContext context , int fd , BigInteger length ) {
1719+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
1720+ ftruncateUnix ( fd , ( long ) length ) ;
1721+ } else {
1722+ context . LanguageContext . FileManager . GetStreams ( fd ) . Truncate ( ( long ) length ) ;
1723+ }
1724+ }
1725+
1726+
1727+ [ SupportedOSPlatform ( "linux" ) , SupportedOSPlatform ( "osx" ) ]
1728+ internal static void ftruncateUnix ( int fd , long length ) {
1729+ int result ;
1730+ Mono . Unix . Native . Errno errno ;
1731+ do {
1732+ result = Mono . Unix . Native . Syscall . ftruncate ( fd , length ) ;
1733+ } while ( Mono . Unix . UnixMarshal . ShouldRetrySyscall ( result , out errno ) ) ;
1734+
1735+ if ( errno != 0 )
1736+ throw GetOsError ( Mono . Unix . Native . NativeConvert . FromErrno ( errno ) ) ;
1737+ }
1738+
16741739
16751740#if FEATURE_FILESYSTEM
16761741 public static object times ( ) {
@@ -2351,7 +2416,7 @@ private static Exception DirectoryExistsError(string? filename) {
23512416
23522417#if FEATURE_NATIVE
23532418
2354- private static Exception GetLastUnixError ( string ? filename = null , string ? filename2 = null )
2419+ internal static Exception GetLastUnixError ( string ? filename = null , string ? filename2 = null )
23552420 => GetOsError ( Mono . Unix . Native . NativeConvert . FromErrno ( Mono . Unix . Native . Syscall . GetLastError ( ) ) , filename , filename2 ) ;
23562421
23572422#endif
0 commit comments