Welcome to mirror list, hosted at ThFree Co, Russian Federation.

FileStream.Unix.cs « IO « System « shared « System.Private.CoreLib « src - github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: ae4b709ea110422ab681f8976aeb07a5656092de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace System.IO
{
    /// <summary>Provides an implementation of a file stream for Unix files.</summary>
    public partial class FileStream : Stream
    {
        /// <summary>File mode.</summary>
        private FileMode _mode;

        /// <summary>Advanced options requested when opening the file.</summary>
        private FileOptions _options;

        /// <summary>If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1.</summary>
        private long _appendStart = -1;

        /// <summary>
        /// Extra state used by the file stream when _useAsyncIO is true.  This includes
        /// the semaphore used to serialize all operation, the buffer/offset/count provided by the
        /// caller for ReadAsync/WriteAsync operations, and the last successful task returned
        /// synchronously from ReadAsync which can be reused if the count matches the next request.
        /// Only initialized when <see cref="_useAsyncIO"/> is true.
        /// </summary>
        private AsyncState _asyncState;

        /// <summary>Lazily-initialized value for whether the file supports seeking.</summary>
        private bool? _canSeek;

        private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
        {
            // FileStream performs most of the general argument validation.  We can assume here that the arguments
            // are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
            // Store the arguments
            _mode = mode;
            _options = options;

            if (_useAsyncIO)
                _asyncState = new AsyncState();

            // Translate the arguments into arguments for an open call.
            Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, share, options);

            // If the file gets created a new, we'll select the permissions for it.  Most Unix utilities by default use 666 (read and 
            // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
            // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
            // actual permissions will typically be less than what we select here.
            const Interop.Sys.Permissions OpenPermissions =
                Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
                Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
                Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;

            // Open the file and store the safe handle.
            return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions);
        }

        private static bool GetDefaultIsAsync(SafeFileHandle handle) => handle.IsAsync ?? DefaultIsAsync;

        /// <summary>Initializes a stream for reading or writing a Unix file.</summary>
        /// <param name="mode">How the file should be opened.</param>
        /// <param name="share">What other access to the file should be allowed.  This is currently ignored.</param>
        private void Init(FileMode mode, FileShare share, string originalPath)
        {
            _fileHandle.IsAsync = _useAsyncIO;

            // Lock the file if requested via FileShare.  This is only advisory locking. FileShare.None implies an exclusive 
            // lock on the file and all other modes use a shared lock.  While this is not as granular as Windows, not mandatory, 
            // and not atomic with file opening, it's better than nothing.
            Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
            if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
            {
                // The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
                // else and we would block trying to access it.  Other errors, such as ENOTSUP (locking isn't supported) or
                // EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
                // given again that this is only advisory / best-effort.
                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
                {
                    throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
                }
            }

            // These provide hints around how the file will be accessed.  Specifying both RandomAccess
            // and Sequential together doesn't make sense as they are two competing options on the same spectrum,
            // so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
            Interop.Sys.FileAdvice fadv =
                (_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
                (_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
                0;
            if (fadv != 0)
            {
                CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
                    ignoreNotSupported: true); // just a hint.
            }

            if (_mode == FileMode.Append)
            {
                // Jump to the end of the file if opened as Append.
                _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End);
            }
            else if (mode == FileMode.Create || mode == FileMode.Truncate)
            {
                // Truncate the file now if the file mode requires it. This ensures that the file only will be truncated
                // if opened successfully.
                CheckFileCall(Interop.Sys.FTruncate(_fileHandle, 0));
            }
        }

        /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
        private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO)
        {
            if (useAsyncIO)
                _asyncState = new AsyncState();

            if (CanSeekCore(handle)) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
                SeekCore(handle, 0, SeekOrigin.Current);
        }

        /// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
        /// <param name="mode">The FileMode provided to the stream's constructor.</param>
        /// <param name="access">The FileAccess provided to the stream's constructor</param>
        /// <param name="share">The FileShare provided to the stream's constructor</param>
        /// <param name="options">The FileOptions provided to the stream's constructor</param>
        /// <returns>The flags value to be passed to the open system call.</returns>
        private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileShare share, FileOptions options)
        {
            // Translate FileMode.  Most of the values map cleanly to one or more options for open.
            Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags);
            switch (mode)
            {
                default:
                case FileMode.Open: // Open maps to the default behavior for open(...).  No flags needed.
                case FileMode.Truncate: // We truncate the file after getting the lock
                    break;

                case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
                case FileMode.OpenOrCreate:
                case FileMode.Create: // We truncate the file after getting the lock
                    flags |= Interop.Sys.OpenFlags.O_CREAT;
                    break;

                case FileMode.CreateNew:
                    flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
                    break;
            }

            // Translate FileAccess.  All possible values map cleanly to corresponding values for open.
            switch (access)
            {
                case FileAccess.Read:
                    flags |= Interop.Sys.OpenFlags.O_RDONLY;
                    break;

                case FileAccess.ReadWrite:
                    flags |= Interop.Sys.OpenFlags.O_RDWR;
                    break;

                case FileAccess.Write:
                    flags |= Interop.Sys.OpenFlags.O_WRONLY;
                    break;
            }

            // Handle Inheritable, other FileShare flags are handled by Init
            if ((share & FileShare.Inheritable) == 0)
            {
                flags |= Interop.Sys.OpenFlags.O_CLOEXEC;
            }

            // Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
            // - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
            // - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
            // - Encrypted: No equivalent on Unix and is ignored
            // - RandomAccess: Implemented after open if posix_fadvise is available
            // - SequentialScan: Implemented after open if posix_fadvise is available
            // - WriteThrough: Handled here
            if ((options & FileOptions.WriteThrough) != 0)
            {
                flags |= Interop.Sys.OpenFlags.O_SYNC;
            }

            return flags;
        }

        /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
        public override bool CanSeek => CanSeekCore(_fileHandle);

        /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
        /// <remarks>
        /// Separated out of CanSeek to enable making non-virtual call to this logic.
        /// We also pass in the file handle to allow the constructor to use this before it stashes the handle.
        /// </remarks>
        private bool CanSeekCore(SafeFileHandle fileHandle)
        {
            if (fileHandle.IsClosed)
            {
                return false;
            }

            if (!_canSeek.HasValue)
            {
                // Lazily-initialize whether we're able to seek, tested by seeking to our current location.
                _canSeek = Interop.Sys.LSeek(fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
            }

            return _canSeek.Value;
        }

        private long GetLengthInternal()
        {
            // Get the length of the file as reported by the OS
            Interop.Sys.FileStatus status;
            CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
            long length = status.Size;

            // But we may have buffered some data to be written that puts our length
            // beyond what the OS is aware of.  Update accordingly.
            if (_writePos > 0 && _filePosition + _writePos > length)
            {
                length = _writePos + _filePosition;
            }

            return length;
        }

        /// <summary>Releases the unmanaged resources used by the stream.</summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                if (_fileHandle != null && !_fileHandle.IsClosed)
                {
                    // Flush any remaining data in the file
                    try
                    {
                        FlushWriteBuffer();
                    }
                    catch (Exception e) when (IsIoRelatedException(e) && !disposing)
                    {
                        // On finalization, ignore failures from trying to flush the write buffer,
                        // e.g. if this stream is wrapping a pipe and the pipe is now broken.
                    }

                    // If DeleteOnClose was requested when constructed, delete the file now.
                    // (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
                    if (_path != null && (_options & FileOptions.DeleteOnClose) != 0)
                    {
                        // Since we still have the file open, this will end up deleting
                        // it (assuming we're the only link to it) once it's closed, but the
                        // name will be removed immediately.
                        Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
                    }
                }
            }
            finally
            {
                if (_fileHandle != null && !_fileHandle.IsClosed)
                {
                    _fileHandle.Dispose();
                }
                base.Dispose(disposing);
            }
        }

        /// <summary>Flushes the OS buffer.  This does not flush the internal read/write buffer.</summary>
        private void FlushOSBuffer()
        {
            if (Interop.Sys.FSync(_fileHandle) < 0)
            {
                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                switch (errorInfo.Error)
                {
                    case Interop.Error.EROFS:
                    case Interop.Error.EINVAL:
                    case Interop.Error.ENOTSUP:
                        // Ignore failures due to the FileStream being bound to a special file that
                        // doesn't support synchronization.  In such cases there's nothing to flush.
                        break;
                    default:
                        throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
                }
            }
        }

        private void FlushWriteBufferForWriteByte()
        {
            _asyncState?.Wait();
            try { FlushWriteBuffer(); }
            finally { _asyncState?.Release(); }
        }

        /// <summary>Writes any data in the write buffer to the underlying stream and resets the buffer.</summary>
        private void FlushWriteBuffer()
        {
            AssertBufferInvariants();
            if (_writePos > 0)
            {
                WriteNative(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos));
                _writePos = 0;
            }
        }

        /// <summary>Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.</summary>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A task that represents the asynchronous flush operation.</returns>
        private Task FlushAsyncInternal(CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }
            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }

            // As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
            try
            {
                FlushInternalBuffer();
            }
            catch (Exception e)
            {
                return Task.FromException(e);
            }

            // We then separately flush to disk asynchronously.  This is only 
            // necessary if we support writing; otherwise, we're done.
            if (CanWrite)
            {
                return Task.Factory.StartNew(
                    state => ((FileStream)state).FlushOSBuffer(),
                    this,
                    cancellationToken,
                    TaskCreationOptions.DenyChildAttach,
                    TaskScheduler.Default);
            }
            else
            {
                return Task.CompletedTask;
            }
        }

        /// <summary>Sets the length of this stream to the given value.</summary>
        /// <param name="value">The new length of the stream.</param>
        private void SetLengthInternal(long value)
        {
            FlushInternalBuffer();

            if (_appendStart != -1 && value < _appendStart)
            {
                throw new IOException(SR.IO_SetLengthAppendTruncate);
            }

            long origPos = _filePosition;

            VerifyOSHandlePosition();

            if (_filePosition != value)
            {
                SeekCore(_fileHandle, value, SeekOrigin.Begin);
            }

            CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value));

            // Return file pointer to where it was before setting length
            if (origPos != value)
            {
                if (origPos < value)
                {
                    SeekCore(_fileHandle, origPos, SeekOrigin.Begin);
                }
                else
                {
                    SeekCore(_fileHandle, 0, SeekOrigin.End);
                }
            }
        }

        /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
        private int ReadSpan(Span<byte> destination)
        {
            PrepareForReading();

            // Are there any bytes available in the read buffer? If yes,
            // we can just return from the buffer.  If the buffer is empty
            // or has no more available data in it, we can either refill it
            // (and then read from the buffer into the user's buffer) or
            // we can just go directly into the user's buffer, if they asked
            // for more data than we'd otherwise buffer.
            int numBytesAvailable = _readLength - _readPos;
            bool readFromOS = false;
            if (numBytesAvailable == 0)
            {
                // If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
                // a read buffer), in which case we don't want to use a read buffer.  Similarly, if
                // the user has asked for more data than we can buffer, we also want to skip the buffer.
                if (!CanSeek || (destination.Length >= _bufferLength))
                {
                    // Read directly into the user's buffer
                    _readPos = _readLength = 0;
                    return ReadNative(destination);
                }
                else
                {
                    // Read into our buffer.
                    _readLength = numBytesAvailable = ReadNative(GetBuffer());
                    _readPos = 0;
                    if (numBytesAvailable == 0)
                    {
                        return 0;
                    }

                    // Note that we did an OS read as part of this Read, so that later
                    // we don't try to do one again if what's in the buffer doesn't
                    // meet the user's request.
                    readFromOS = true;
                }
            }

            // Now that we know there's data in the buffer, read from it into the user's buffer.
            Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
            int bytesRead = Math.Min(numBytesAvailable, destination.Length);
            new Span<byte>(GetBuffer(), _readPos, bytesRead).CopyTo(destination);
            _readPos += bytesRead;

            // We may not have had enough data in the buffer to completely satisfy the user's request.
            // While Read doesn't require that we return as much data as the user requested (any amount
            // up to the requested count is fine), FileStream on Windows tries to do so by doing a 
            // subsequent read from the file if we tried to satisfy the request with what was in the 
            // buffer but the buffer contained less than the requested count. To be consistent with that 
            // behavior, we do the same thing here on Unix.  Note that we may still get less the requested 
            // amount, as the OS may give us back fewer than we request, either due to reaching the end of 
            // file, or due to its own whims.
            if (!readFromOS && bytesRead < destination.Length)
            {
                Debug.Assert(_readPos == _readLength, "bytesToRead should only be < destination.Length if numBytesAvailable < destination.Length");
                _readPos = _readLength = 0; // no data left in the read buffer
                bytesRead += ReadNative(destination.Slice(bytesRead));
            }

            return bytesRead;
        }

        /// <summary>Unbuffered, reads a block of bytes from the file handle into the given buffer.</summary>
        /// <param name="buffer">The buffer into which data from the file is read.</param>
        /// <returns>
        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
        /// </returns>
        private unsafe int ReadNative(Span<byte> buffer)
        {
            FlushWriteBuffer(); // we're about to read; dump the write buffer

            VerifyOSHandlePosition();

            int bytesRead;
            fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
            {
                bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr, buffer.Length));
                Debug.Assert(bytesRead <= buffer.Length);
            }
            _filePosition += bytesRead;
            return bytesRead;
        }

        /// <summary>
        /// Asynchronously reads a sequence of bytes from the current stream and advances
        /// the position within the stream by the number of bytes read.
        /// </summary>
        /// <param name="destination">The buffer to write the data into.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <param name="synchronousResult">If the operation completes synchronously, the number of bytes read.</param>
        /// <returns>A task that represents the asynchronous read operation.</returns>
        private Task<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult)
        {
            Debug.Assert(_useAsyncIO);

            if (!CanRead) // match Windows behavior; this gets thrown synchronously
            {
                throw Error.GetReadNotSupported();
            }

            // Serialize operations using the semaphore.
            Task waitTask = _asyncState.WaitAsync();

            // If we got ownership immediately, and if there's enough data in our buffer
            // to satisfy the full request of the caller, hand back the buffered data.
            // While it would be a legal implementation of the Read contract, we don't
            // hand back here less than the amount requested so as to match the behavior
            // in ReadCore that will make a native call to try to fulfill the remainder
            // of the request.
            if (waitTask.Status == TaskStatus.RanToCompletion)
            {
                int numBytesAvailable = _readLength - _readPos;
                if (numBytesAvailable >= destination.Length)
                {
                    try
                    {
                        PrepareForReading();

                        new Span<byte>(GetBuffer(), _readPos, destination.Length).CopyTo(destination.Span);
                        _readPos += destination.Length;

                        synchronousResult = destination.Length;
                        return null;
                    }
                    catch (Exception exc)
                    {
                        synchronousResult = 0;
                        return Task.FromException<int>(exc);
                    }
                    finally
                    {
                        _asyncState.Release();
                    }
                }
            }

            // Otherwise, issue the whole request asynchronously.
            synchronousResult = 0;
            _asyncState.Memory = destination;
            return waitTask.ContinueWith((t, s) =>
            {
                // The options available on Unix for writing asynchronously to an arbitrary file 
                // handle typically amount to just using another thread to do the synchronous write, 
                // which is exactly  what this implementation does. This does mean there are subtle
                // differences in certain FileStream behaviors between Windows and Unix when multiple 
                // asynchronous operations are issued against the stream to execute concurrently; on 
                // Unix the operations will be serialized due to the usage of a semaphore, but the 
                // position /length information won't be updated until after the write has completed, 
                // whereas on Windows it may happen before the write has completed.

                Debug.Assert(t.Status == TaskStatus.RanToCompletion);
                var thisRef = (FileStream)s;
                try
                {
                    Memory<byte> memory = thisRef._asyncState.Memory;
                    thisRef._asyncState.Memory = default(Memory<byte>);
                    return thisRef.ReadSpan(memory.Span);
                }
                finally { thisRef._asyncState.Release(); }
            }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
        }

        /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary>
        private int FillReadBufferForReadByte()
        {
            _asyncState?.Wait();
            try { return ReadNative(_buffer); }
            finally { _asyncState?.Release(); }
        }

        /// <summary>Writes a block of bytes to the file stream.</summary>
        /// <param name="source">The buffer containing data to write to the stream.</param>
        private void WriteSpan(ReadOnlySpan<byte> source)
        {
            PrepareForWriting();

            // If no data is being written, nothing more to do.
            if (source.Length == 0)
            {
                return;
            }

            // If there's already data in our write buffer, then we need to go through
            // our buffer to ensure data isn't corrupted.
            if (_writePos > 0)
            {
                // If there's space remaining in the buffer, then copy as much as
                // we can from the user's buffer into ours.
                int spaceRemaining = _bufferLength - _writePos;
                if (spaceRemaining >= source.Length)
                {
                    source.CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
                    _writePos += source.Length;
                    return;
                }
                else if (spaceRemaining > 0)
                {
                    source.Slice(0, spaceRemaining).CopyTo(new Span<byte>(GetBuffer()).Slice(_writePos));
                    _writePos += spaceRemaining;
                    source = source.Slice(spaceRemaining);
                }

                // At this point, the buffer is full, so flush it out.
                FlushWriteBuffer();
            }

            // Our buffer is now empty.  If using the buffer would slow things down (because
            // the user's looking to write more data than we can store in the buffer),
            // skip the buffer.  Otherwise, put the remaining data into the buffer.
            Debug.Assert(_writePos == 0);
            if (source.Length >= _bufferLength)
            {
                WriteNative(source);
            }
            else
            {
                source.CopyTo(new Span<byte>(GetBuffer()));
                _writePos = source.Length;
            }
        }

        /// <summary>Unbuffered, writes a block of bytes to the file stream.</summary>
        /// <param name="source">The buffer containing data to write to the stream.</param>
        private unsafe void WriteNative(ReadOnlySpan<byte> source)
        {
            VerifyOSHandlePosition();

            fixed (byte* bufPtr = &MemoryMarshal.GetReference(source))
            {
                int offset = 0;
                int count = source.Length;
                while (count > 0)
                {
                    int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
                    _filePosition += bytesWritten;
                    offset += bytesWritten;
                    count -= bytesWritten;
                }
            }
        }

        /// <summary>
        /// Asynchronously writes a sequence of bytes to the current stream, advances
        /// the current position within this stream by the number of bytes written, and
        /// monitors cancellation requests.
        /// </summary>
        /// <param name="source">The buffer to write data from.</param>
        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
        /// <returns>A task that represents the asynchronous write operation.</returns>
        private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
        {
            Debug.Assert(_useAsyncIO);

            if (cancellationToken.IsCancellationRequested)
                return new ValueTask(Task.FromCanceled(cancellationToken));

            if (_fileHandle.IsClosed)
                throw Error.GetFileNotOpen();

            if (!CanWrite) // match Windows behavior; this gets thrown synchronously
            {
                throw Error.GetWriteNotSupported();
            }

            // Serialize operations using the semaphore.
            Task waitTask = _asyncState.WaitAsync();

            // If we got ownership immediately, and if there's enough space in our buffer
            // to buffer the entire write request, then do so and we're done.
            if (waitTask.Status == TaskStatus.RanToCompletion)
            {
                int spaceRemaining = _bufferLength - _writePos;
                if (spaceRemaining >= source.Length)
                {
                    try
                    {
                        PrepareForWriting();

                        source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length));
                        _writePos += source.Length;

                        return default;
                    }
                    catch (Exception exc)
                    {
                        return new ValueTask(Task.FromException(exc));
                    }
                    finally
                    {
                        _asyncState.Release();
                    }
                }
            }

            // Otherwise, issue the whole request asynchronously.
            _asyncState.ReadOnlyMemory = source;
            return new ValueTask(waitTask.ContinueWith((t, s) =>
            {
                // The options available on Unix for writing asynchronously to an arbitrary file 
                // handle typically amount to just using another thread to do the synchronous write, 
                // which is exactly  what this implementation does. This does mean there are subtle
                // differences in certain FileStream behaviors between Windows and Unix when multiple 
                // asynchronous operations are issued against the stream to execute concurrently; on 
                // Unix the operations will be serialized due to the usage of a semaphore, but the 
                // position/length information won't be updated until after the write has completed, 
                // whereas on Windows it may happen before the write has completed.

                Debug.Assert(t.Status == TaskStatus.RanToCompletion);
                var thisRef = (FileStream)s;
                try
                {
                    ReadOnlyMemory<byte> readOnlyMemory = thisRef._asyncState.ReadOnlyMemory;
                    thisRef._asyncState.ReadOnlyMemory = default(ReadOnlyMemory<byte>);
                    thisRef.WriteSpan(readOnlyMemory.Span);
                }
                finally { thisRef._asyncState.Release(); }
            }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default));
        }

        /// <summary>Sets the current position of this stream to the given value.</summary>
        /// <param name="offset">The point relative to origin from which to begin seeking. </param>
        /// <param name="origin">
        /// Specifies the beginning, the end, or the current position as a reference 
        /// point for offset, using a value of type SeekOrigin.
        /// </param>
        /// <returns>The new position in the stream.</returns>
        public override long Seek(long offset, SeekOrigin origin)
        {
            if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
            {
                throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
            }
            if (_fileHandle.IsClosed)
            {
                throw Error.GetFileNotOpen();
            }
            if (!CanSeek)
            {
                throw Error.GetSeekNotSupported();
            }

            VerifyOSHandlePosition();

            // Flush our write/read buffer.  FlushWrite will output any write buffer we have and reset _bufferWritePos.
            // We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're 
            // about to seek and update our position, we can simply update the offset as necessary and reset our read 
            // position and length to 0. (In the future, for some simple cases we could potentially add an optimization 
            // here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.)
            FlushWriteBuffer();
            if (origin == SeekOrigin.Current)
            {
                offset -= (_readLength - _readPos);
            }
            _readPos = _readLength = 0;

            // Keep track of where we were, in case we're in append mode and need to verify
            long oldPos = 0;
            if (_appendStart >= 0)
            {
                oldPos = SeekCore(_fileHandle, 0, SeekOrigin.Current);
            }

            // Jump to the new location
            long pos = SeekCore(_fileHandle, offset, origin);

            // Prevent users from overwriting data in a file that was opened in append mode.
            if (_appendStart != -1 && pos < _appendStart)
            {
                SeekCore(_fileHandle, oldPos, SeekOrigin.Begin);
                throw new IOException(SR.IO_SeekAppendOverwrite);
            }

            // Return the new position
            return pos;
        }

        /// <summary>Sets the current position of this stream to the given value.</summary>
        /// <param name="offset">The point relative to origin from which to begin seeking. </param>
        /// <param name="origin">
        /// Specifies the beginning, the end, or the current position as a reference 
        /// point for offset, using a value of type SeekOrigin.
        /// </param>
        /// <returns>The new position in the stream.</returns>
        private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin)
        {
            Debug.Assert(!fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeekCore(fileHandle))); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor)
            Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);

            long pos = CheckFileCall(Interop.Sys.LSeek(fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
            _filePosition = pos;
            return pos;
        }

        private long CheckFileCall(long result, bool ignoreNotSupported = false)
        {
            if (result < 0)
            {
                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
                if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
                {
                    throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
                }
            }

            return result;
        }

        private int CheckFileCall(int result, bool ignoreNotSupported = false)
        {
            CheckFileCall((long)result, ignoreNotSupported);

            return result;
        }

        /// <summary>State used when the stream is in async mode.</summary>
        private sealed class AsyncState : SemaphoreSlim
        {
            internal ReadOnlyMemory<byte> ReadOnlyMemory;
            internal Memory<byte> Memory;

            /// <summary>Initialize the AsyncState.</summary>
            internal AsyncState() : base(initialCount: 1, maxCount: 1) { }
        }
    }
}