@@ -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 . CanRead ? stream . CanWrite ? FileAccess . ReadWrite : FileAccess . Read : FileAccess . Write ;
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 ) {
@@ -869,7 +885,16 @@ public static void mkdir(CodeContext context, object? path, [ParamDictionary, No
869885
870886 private const int DefaultBufferSize = 4096 ;
871887
872- [ Documentation ( "open(path, flags, mode=511, *, dir_fd=None)" ) ]
888+ [ Documentation ( """
889+ open(path, flags, mode=511, *, dir_fd=None)
890+
891+ Open a file for low level IO. Returns a file descriptor (integer).
892+
893+ If dir_fd is not None, it should be a file descriptor open to a directory,
894+ and path should be relative; path will then be relative to that directory.
895+ dir_fd may not be implemented on your platform.
896+ If it is unavailable, using it will raise a NotImplementedError.
897+ """ ) ]
873898 public static object open ( CodeContext /*!*/ context , [ NotNone ] string path , int flags , [ ParamDictionary , NotNone ] IDictionary < string , object > kwargs , [ NotNone ] params object [ ] args ) {
874899 var numArgs = args . Length ;
875900 CheckOptionalArgsCount ( numRegParms : 2 , numOptPosParms : 1 , numKwParms : 1 , numArgs , kwargs . Count ) ;
@@ -889,12 +914,23 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
889914 }
890915 }
891916
917+ if ( ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) && ! ClrModule . IsMono ) {
918+ // Use PosixFileStream to operate on fd directly
919+ // On Mono, we must use FileStream due to limitations in MemoryMappedFile
920+ Stream s = PosixFileStream . Open ( path , flags , unchecked ( ( uint ) mode ) , out int fd ) ;
921+ //Stream s = PythonIOModule.FileIO.OpenFilePosix(path, flags, mode, out int fd);
922+ if ( ( flags & O_APPEND ) != 0 ) {
923+ s . Seek ( 0L , SeekOrigin . End ) ;
924+ }
925+ return context . LanguageContext . FileManager . Add ( fd , new ( s ) ) ;
926+ }
927+
892928 try {
893929 FileMode fileMode = FileModeFromFlags ( flags ) ;
894930 FileAccess access = FileAccessFromFlags ( flags ) ;
895931 FileOptions options = FileOptionsFromFlags ( flags ) ;
896932 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)
933+ FileStream ? fs ; // downcast of s if s is FileStream
898934 Stream ? rs = null ; // secondary read stream if needed, otherwise same as s
899935 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) && IsNulFile ( path ) ) {
900936 fs = null ;
@@ -1436,6 +1472,13 @@ private static object statUnix(string path) {
14361472 return LightExceptions . Throw ( GetLastUnixError ( path ) ) ;
14371473 }
14381474
1475+ private static object fstatUnix ( int fd ) {
1476+ if ( Mono . Unix . Native . Syscall . fstat ( fd , out Mono . Unix . Native . Stat buf ) == 0 ) {
1477+ return new stat_result ( buf ) ;
1478+ }
1479+ return LightExceptions . Throw ( GetLastUnixError ( ) ) ;
1480+ }
1481+
14391482 private const int OPEN_EXISTING = 3 ;
14401483 private const int FILE_ATTRIBUTE_NORMAL = 0x00000080 ;
14411484 private const int FILE_READ_ATTRIBUTES = 0x0080 ;
@@ -1669,8 +1712,27 @@ public static void truncate(CodeContext context, object? path, BigInteger length
16691712 public static void truncate ( CodeContext context , int fd , BigInteger length )
16701713 => ftruncate ( context , fd , length ) ;
16711714
1672- public static void ftruncate ( CodeContext context , int fd , BigInteger length )
1673- => context . LanguageContext . FileManager . GetStreams ( fd ) . Truncate ( ( long ) length ) ;
1715+ public static void ftruncate ( CodeContext context , int fd , BigInteger length ) {
1716+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
1717+ ftruncateUnix ( fd , ( long ) length ) ;
1718+ } else {
1719+ context . LanguageContext . FileManager . GetStreams ( fd ) . Truncate ( ( long ) length ) ;
1720+ }
1721+ }
1722+
1723+
1724+ [ SupportedOSPlatform ( "linux" ) , SupportedOSPlatform ( "osx" ) ]
1725+ internal static void ftruncateUnix ( int fd , long length ) {
1726+ int result ;
1727+ Mono . Unix . Native . Errno errno ;
1728+ do {
1729+ result = Mono . Unix . Native . Syscall . ftruncate ( fd , length ) ;
1730+ } while ( Mono . Unix . UnixMarshal . ShouldRetrySyscall ( result , out errno ) ) ;
1731+
1732+ if ( errno != 0 )
1733+ throw GetOsError ( Mono . Unix . Native . NativeConvert . FromErrno ( errno ) ) ;
1734+ }
1735+
16741736
16751737#if FEATURE_FILESYSTEM
16761738 public static object times ( ) {
0 commit comments