@@ -352,10 +352,6 @@ public static int dup(CodeContext/*!*/ context, int fd) {
352352
353353 StreamBox streams = fileManager . GetStreams ( fd ) ; // OSError if fd not valid
354354 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
355- if ( ! streams . IsSingleStream && fd is 1 or 2 ) {
356- // If there is a separate write stream, dupping over stout or sderr uses write stream's file descriptor
357- fd = streams . WriteStream is FileStream fs ? fs . SafeFileHandle . DangerousGetHandle ( ) . ToInt32 ( ) : fd ;
358- }
359355 int fd2 = UnixDup ( fd , - 1 , out Stream ? dupstream ) ;
360356 if ( dupstream is not null ) {
361357 return fileManager . Add ( fd2 , new ( dupstream ) ) ;
@@ -389,16 +385,14 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) {
389385 close ( context , fd2 ) ;
390386 }
391387
392- // TODO: race condition: `open` or `dup` on another thread may occupy fd2 (simulated descriptors only)
388+ // TODO: race condition: `open` or `dup` on another thread may occupy fd2
393389
394- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
395- fileManager . EnsureRefStreams ( streams ) ;
396- fileManager . AddRefStreams ( streams ) ;
397- return fileManager . Add ( fd2 , new ( streams ) ) ;
398- } else {
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 ;
390+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
391+ if ( ! streams . IsSingleStream && fd2 is 0 && streams . ReadStream is FileStream fs ) {
392+ // If there is a separate read stream, dupping over stdin uses read stream's file descriptor as source
393+ long pos = fs . Position ;
394+ fd = fs . SafeFileHandle . DangerousGetHandle ( ) . ToInt32 ( ) ;
395+ fs . Seek ( pos , SeekOrigin . Begin ) ;
402396 }
403397 fd2 = UnixDup ( fd , fd2 , out Stream ? dupstream ) ; // closes fd2 atomically if reopened in the meantime
404398 fileManager . Remove ( fd2 ) ;
@@ -410,6 +404,10 @@ public static int dup2(CodeContext/*!*/ context, int fd, int fd2) {
410404 fileManager . AddRefStreams ( streams ) ;
411405 return fileManager . Add ( fd2 , new ( streams ) ) ;
412406 }
407+ } else {
408+ fileManager . EnsureRefStreams ( streams ) ;
409+ fileManager . AddRefStreams ( streams ) ;
410+ return fileManager . Add ( fd2 , new ( streams ) ) ;
413411 }
414412 }
415413
@@ -877,8 +875,9 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
877875 FileMode fileMode = FileModeFromFlags ( flags ) ;
878876 FileAccess access = FileAccessFromFlags ( flags ) ;
879877 FileOptions options = FileOptionsFromFlags ( flags ) ;
880- Stream s ;
881- FileStream ? fs ;
878+ Stream s ; // the stream opened to acces the file
879+ FileStream ? fs ; // downcast of s if s is FileStream (this is always the case on POSIX)
880+ Stream ? rs = null ; // secondary read stream if needed, otherwise same as s
882881 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) && IsNulFile ( path ) ) {
883882 fs = null ;
884883 s = Stream . Null ;
@@ -889,15 +888,28 @@ public static object open(CodeContext/*!*/ context, [NotNone] string path, int f
889888 fs . Close ( ) ;
890889 s = fs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , DefaultBufferSize , options ) ;
891890 } else if ( access == FileAccess . ReadWrite && fileMode == FileMode . Append ) {
891+ // .NET doesn't allow Append w/ access != Write, so open the file w/ Write
892+ // and a secondary stream w/ Read, then seek to the end.
892893 s = fs = new FileStream ( path , FileMode . Append , FileAccess . Write , FileShare . ReadWrite , DefaultBufferSize , options ) ;
894+ rs = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . ReadWrite , DefaultBufferSize , options ) ;
895+ rs . Seek ( 0L , SeekOrigin . End ) ;
893896 } else {
894897 s = fs = new FileStream ( path , fileMode , access , FileShare . ReadWrite , DefaultBufferSize , options ) ;
895898 }
899+ rs ??= s ;
896900
897- if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) ) {
898- return context . LanguageContext . FileManager . Add ( new ( s ) ) ;
901+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) || RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) ) {
902+ int fd = fs ! . SafeFileHandle . DangerousGetHandle ( ) . ToInt32 ( ) ;
903+ // accessing SafeFileHandle may reset file position
904+ if ( fileMode == FileMode . Append ) {
905+ fs . Seek ( 0L , SeekOrigin . End ) ;
906+ }
907+ if ( ! ReferenceEquals ( fs , rs ) ) {
908+ rs . Seek ( fs . Position , SeekOrigin . Begin ) ;
909+ }
910+ return context . LanguageContext . FileManager . Add ( fd , new ( rs , s ) ) ;
899911 } else {
900- return context . LanguageContext . FileManager . Add ( ( int ) fs ! . SafeFileHandle . DangerousGetHandle ( ) , new ( s ) ) ;
912+ return context . LanguageContext . FileManager . Add ( new ( rs , s ) ) ;
901913 }
902914 } catch ( Exception e ) {
903915 throw ToPythonException ( e , path ) ;
0 commit comments