@@ -351,19 +351,24 @@ public static int dup(CodeContext/*!*/ context, int fd) {
351351 PythonFileManager fileManager = context . LanguageContext . FileManager ;
352352
353353 StreamBox streams = fileManager . GetStreams ( fd ) ; // OSError if fd not valid
354- fileManager . EnsureRefStreams ( streams ) ;
355- fileManager . AddRefStreams ( streams ) ;
356354 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
355+ fileManager . EnsureRefStreams ( streams ) ;
356+ fileManager . AddRefStreams ( streams ) ;
357357 return fileManager . Add ( new ( streams ) ) ;
358358 } else {
359- return fileManager . Add ( UnixDup ( fd ) , new ( streams ) ) ;
360- }
361-
362- // Isolate Mono.Unix from the rest of the method so that we don't try to load the Mono.Unix assembly on Windows.
363- static int UnixDup ( int fd ) {
364- int res = Mono . Unix . Native . Syscall . dup ( fd ) ;
365- if ( res < 0 ) throw GetLastUnixError ( ) ;
366- return res ;
359+ if ( ! streams . IsSingleStream && fd is 1 or 2 ) {
360+ // If there is a separate write stream, dupping over stout or sderr uses write stream's file descriptor
361+ fd = streams . WriteStream is FileStream fs ? fs . SafeFileHandle . DangerousGetHandle ( ) . ToInt32 ( ) : fd ;
362+ }
363+ int fd2 = UnixDup ( fd , - 1 , out Stream ? dupstream ) ;
364+ if ( dupstream is not null ) {
365+ return fileManager . Add ( fd2 , new ( dupstream ) ) ;
366+ } else {
367+ // Share the same set of streams between the original and the dupped descriptor
368+ fileManager . EnsureRefStreams ( streams ) ;
369+ fileManager . AddRefStreams ( streams ) ;
370+ return fileManager . Add ( fd2 , new ( streams ) ) ;
371+ }
367372 }
368373 }
369374
@@ -384,22 +389,45 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) {
384389 close ( context , fd2 ) ;
385390 }
386391
387- // TODO: race condition: `open` or `dup` on another thread may occupy fd2
392+ // TODO: race condition: `open` or `dup` on another thread may occupy fd2 (simulated desctiptors only)
388393
389- fileManager . EnsureRefStreams ( streams ) ;
390- fileManager . AddRefStreams ( streams ) ;
391394 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
395+ fileManager . EnsureRefStreams ( streams ) ;
396+ fileManager . AddRefStreams ( streams ) ;
392397 return fileManager . Add ( fd2 , new ( streams ) ) ;
393398 } else {
394- return fileManager . Add ( UnixDup2 ( fd , fd2 ) , new ( streams ) ) ;
399+ if ( ! streams . IsSingleStream && fd is 1 or 2 ) {
400+ // If there is a separate write stream, dupping over stout or sderr uses write stream's file descriptor
401+ fd = streams . WriteStream is FileStream fs ? fs . SafeFileHandle . DangerousGetHandle ( ) . ToInt32 ( ) : fd ;
402+ }
403+ fd2 = UnixDup ( fd , fd2 , out Stream ? dupstream ) ; // closes fd2 atomically if reopened in the meantime
404+ fileManager . Remove ( fd2 ) ;
405+ if ( dupstream is not null ) {
406+ return fileManager . Add ( fd2 , new ( dupstream ) ) ;
407+ } else {
408+ // Share the same set of streams between the original and the dupped descriptor
409+ fileManager . EnsureRefStreams ( streams ) ;
410+ fileManager . AddRefStreams ( streams ) ;
411+ return fileManager . Add ( fd2 , new ( streams ) ) ;
412+ }
395413 }
414+ }
396415
397- // Isolate Mono.Unix from the rest of the method so that we don't try to load the Mono.Unix assembly on Windows.
398- static int UnixDup2 ( int fd , int fd2 ) {
399- int res = Mono . Unix . Native . Syscall . dup2 ( fd , fd2 ) ;
400- if ( res < 0 ) throw GetLastUnixError ( ) ;
401- return res ;
416+
417+ private static int UnixDup ( int fd , int fd2 , out Stream ? stream ) {
418+ int res = fd2 < 0 ? Mono . Unix . Native . Syscall . dup ( fd ) : Mono . Unix . Native . Syscall . dup2 ( fd , fd2 ) ;
419+ if ( res < 0 ) throw GetLastUnixError ( ) ;
420+ if ( ClrModule . IsMono ) {
421+ // This does not work on .NET, probably because .NET FileStream is not aware of Mono.Unix.UnxiStream
422+ stream = new Mono . Unix . UnixStream ( res , ownsHandle : true ) ;
423+ } else {
424+ // This does not work 100% correctly on .NET, probably because each FileStream has its own read/write cursor
425+ // (it should be shared between dupped descriptors)
426+ //stream = new FileStream(new SafeFileHandle((IntPtr)res, ownsHandle: true), FileAccess.ReadWrite);
427+ // Accidentaly, this would also not work on Mono: https://github.com/mono/mono/issues/12783
428+ stream = null ; // Handle stream sharing in PythonFileManager
402429 }
430+ return res ;
403431 }
404432
405433#if FEATURE_PROCESS
@@ -849,25 +877,27 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
849877 FileMode fileMode = FileModeFromFlags ( flags ) ;
850878 FileAccess access = FileAccessFromFlags ( flags ) ;
851879 FileOptions options = FileOptionsFromFlags ( flags ) ;
852- Stream fs ;
880+ Stream s ;
881+ FileStream ? fs ;
853882 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) && IsNulFile ( path ) ) {
854- fs = Stream . Null ;
883+ fs = null ;
884+ s = Stream . Null ;
855885 } else if ( access == FileAccess . Read && ( fileMode == FileMode . CreateNew || fileMode == FileMode . Create || fileMode == FileMode . Append ) ) {
856886 // .NET doesn't allow Create/CreateNew w/ access == Read, so create the file, then close it, then
857887 // open it again w/ just read access.
858888 fs = new FileStream ( path , fileMode , FileAccess . Write , FileShare . None ) ;
859889 fs . Close ( ) ;
860- fs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , DefaultBufferSize , options ) ;
890+ s = fs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , DefaultBufferSize , options ) ;
861891 } else if ( access == FileAccess . ReadWrite && fileMode == FileMode . Append ) {
862- fs = new FileStream ( path , FileMode . Append , FileAccess . Write , FileShare . ReadWrite , DefaultBufferSize , options ) ;
892+ s = fs = new FileStream ( path , FileMode . Append , FileAccess . Write , FileShare . ReadWrite , DefaultBufferSize , options ) ;
863893 } else {
864- fs = new FileStream ( path , fileMode , access , FileShare . ReadWrite , DefaultBufferSize , options ) ;
894+ s = fs = new FileStream ( path , fileMode , access , FileShare . ReadWrite , DefaultBufferSize , options ) ;
865895 }
866896
867897 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
868- return context . LanguageContext . FileManager . Add ( new ( fs ) ) ;
898+ return context . LanguageContext . FileManager . Add ( new ( s ) ) ;
869899 } else {
870- return context . LanguageContext . FileManager . Add ( ( int ) fs . SafeFileHandle . DangerousGetHandle ( ) , new ( fs ) ) ;
900+ return context . LanguageContext . FileManager . Add ( ( int ) fs ! . SafeFileHandle . DangerousGetHandle ( ) , new ( s ) ) ;
871901 }
872902 } catch ( Exception e ) {
873903 throw ToPythonException ( e , path ) ;
@@ -916,14 +946,14 @@ public static PythonTuple pipe(CodeContext context) {
916946 } else {
917947 var pipeStreams = CreatePipeStreamsUnix ( ) ;
918948 return PythonTuple . MakeTuple (
919- manager . Add ( ( int ) pipeStreams . Item1 . SafeFileHandle . DangerousGetHandle ( ) , new ( pipeStreams . Item1 ) ) ,
920- manager . Add ( ( int ) pipeStreams . Item2 . SafeFileHandle . DangerousGetHandle ( ) , new ( pipeStreams . Item2 ) )
949+ manager . Add ( pipeStreams . Item1 , new ( pipeStreams . Item2 ) ) ,
950+ manager . Add ( pipeStreams . Item3 , new ( pipeStreams . Item4 ) )
921951 ) ;
922952 }
923953
924- static Tuple < Stream , Stream > CreatePipeStreamsUnix ( ) {
954+ static Tuple < int , Stream , int , Stream > CreatePipeStreamsUnix ( ) {
925955 Mono . Unix . UnixPipes pipes = Mono . Unix . UnixPipes . CreatePipes ( ) ;
926- return Tuple . Create < Stream , Stream > ( pipes . Reading , pipes . Writing ) ;
956+ return Tuple . Create < int , Stream , int , Stream > ( pipes . Reading . Handle , pipes . Reading , pipes . Writing . Handle , pipes . Writing ) ;
927957 }
928958 }
929959#endif
0 commit comments