File: src\libraries\System.Private.CoreLib\src\System\IO\RandomAccess.Unix.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Strategies;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
 
namespace System.IO
{
    public static partial class RandomAccess
    {
        // IovStackThreshold matches Linux's UIO_FASTIOV, which is the number of 'struct iovec'
        // that get stackalloced in the Linux kernel.
        private const int IovStackThreshold = 8;
 
        internal static unsafe void SetFileLength(SafeFileHandle handle, long length) =>
            FileStreamHelpers.CheckFileCall(Interop.Sys.FTruncate(handle, length), handle.Path);
 
        internal static unsafe int ReadAtOffset(SafeFileHandle handle, Span<byte> buffer, long fileOffset)
        {
            fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
            {
                // The Windows implementation uses ReadFile, which ignores the offset if the handle
                // isn't seekable.  We do the same manually with PRead vs Read, in order to enable
                // the function to be used by FileStream for all the same situations.
                int result;
                if (handle.SupportsRandomAccess)
                {
                    // Try pread for seekable files.
                    result = Interop.Sys.PRead(handle, bufPtr, buffer.Length, fileOffset);
                    if (result == -1)
                    {
                        // We need to fallback to the non-offset version for certain file types
                        // e.g: character devices (such as /dev/tty), pipes, and sockets.
                        Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
 
                        if (errorInfo.Error == Interop.Error.ENXIO ||
                            errorInfo.Error == Interop.Error.ESPIPE)
                        {
                            handle.SupportsRandomAccess = false;
                            result = Interop.Sys.Read(handle, bufPtr, buffer.Length);
                        }
                    }
                }
                else
                {
                    result = Interop.Sys.Read(handle, bufPtr, buffer.Length);
                }
 
                FileStreamHelpers.CheckFileCall(result, handle.Path);
                return result;
            }
        }
 
        internal static unsafe long ReadScatterAtOffset(SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers, long fileOffset)
        {
            MemoryHandle[] handles = new MemoryHandle[buffers.Count];
            Span<Interop.Sys.IOVector> vectors = buffers.Count <= IovStackThreshold ? stackalloc Interop.Sys.IOVector[IovStackThreshold] : new Interop.Sys.IOVector[buffers.Count];
 
            long result;
            try
            {
                int buffersCount = buffers.Count;
                for (int i = 0; i < buffersCount; i++)
                {
                    Memory<byte> buffer = buffers[i];
                    MemoryHandle memoryHandle = buffer.Pin();
                    vectors[i] = new Interop.Sys.IOVector { Base = (byte*)memoryHandle.Pointer, Count = (UIntPtr)buffer.Length };
                    handles[i] = memoryHandle;
                }
 
                fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors))
                {
                    result = Interop.Sys.PReadV(handle, pinnedVectors, buffers.Count, fileOffset);
                }
            }
            finally
            {
                foreach (MemoryHandle memoryHandle in handles)
                {
                    memoryHandle.Dispose();
                }
            }
 
            return FileStreamHelpers.CheckFileCall(result, handle.Path);
        }
 
        internal static ValueTask<int> ReadAtOffsetAsync(SafeFileHandle handle, Memory<byte> buffer, long fileOffset, CancellationToken cancellationToken, OSFileStreamStrategy? strategy = null)
            => handle.GetThreadPoolValueTaskSource().QueueRead(buffer, fileOffset, cancellationToken, strategy);
 
        private static ValueTask<long> ReadScatterAtOffsetAsync(SafeFileHandle handle, IReadOnlyList<Memory<byte>> buffers, long fileOffset, CancellationToken cancellationToken)
            => handle.GetThreadPoolValueTaskSource().QueueReadScatter(buffers, fileOffset, cancellationToken);
 
        internal static unsafe void WriteAtOffset(SafeFileHandle handle, ReadOnlySpan<byte> buffer, long fileOffset)
        {
            while (!buffer.IsEmpty)
            {
                fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer))
                {
                    // The Windows implementation uses WriteFile, which ignores the offset if the handle
                    // isn't seekable.  We do the same manually with PWrite vs Write, in order to enable
                    // the function to be used by FileStream for all the same situations.
                    int bytesToWrite = GetNumberOfBytesToWrite(buffer.Length);
                    int bytesWritten;
                    if (handle.SupportsRandomAccess)
                    {
                        bytesWritten = Interop.Sys.PWrite(handle, bufPtr, bytesToWrite, fileOffset);
                        if (bytesWritten == -1)
                        {
                            // We need to fallback to the non-offset version for certain file types
                            // e.g: character devices (such as /dev/tty), pipes, and sockets.
                            Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
 
                            if (errorInfo.Error == Interop.Error.ENXIO ||
                                errorInfo.Error == Interop.Error.ESPIPE)
                            {
                                handle.SupportsRandomAccess = false;
                                bytesWritten = Interop.Sys.Write(handle, bufPtr, bytesToWrite);
                            }
                        }
                    }
                    else
                    {
                        bytesWritten = Interop.Sys.Write(handle, bufPtr, bytesToWrite);
                    }
 
                    FileStreamHelpers.CheckFileCall(bytesWritten, handle.Path);
                    if (bytesWritten == buffer.Length)
                    {
                        break;
                    }
 
                    // The write completed successfully but for fewer bytes than requested.
                    // We need to try again for the remainder.
                    buffer = buffer.Slice(bytesWritten);
                    fileOffset += bytesWritten;
                }
            }
        }
 
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int GetNumberOfBytesToWrite(int byteCount)
        {
#if DEBUG
            // In debug only, to assist with testing, simulate writing fewer than the requested number of bytes.
            if (byteCount > 1 &&  // ensure we don't turn the read into a zero-byte read
                byteCount < 512)  // avoid on larger buffers that might have a length used to meet an alignment requirement
            {
                byteCount /= 2;
            }
#endif
            return byteCount;
        }
 
        internal static unsafe void WriteGatherAtOffset(SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers, long fileOffset)
        {
            int buffersCount = buffers.Count;
            if (buffersCount == 0)
            {
                return;
            }
 
            var handles = new MemoryHandle[buffersCount];
            Span<Interop.Sys.IOVector> vectors = buffersCount <= IovStackThreshold ?
                stackalloc Interop.Sys.IOVector[IovStackThreshold] :
                new Interop.Sys.IOVector[buffersCount];
 
            try
            {
                int buffersOffset = 0, firstBufferOffset = 0;
                while (true)
                {
                    long totalBytesToWrite = 0;
 
                    for (int i = buffersOffset; i < buffersCount; i++)
                    {
                        ReadOnlyMemory<byte> buffer = buffers[i];
                        totalBytesToWrite += buffer.Length;
 
                        MemoryHandle memoryHandle = buffer.Pin();
                        vectors[i] = new Interop.Sys.IOVector { Base = firstBufferOffset + (byte*)memoryHandle.Pointer, Count = (UIntPtr)(buffer.Length - firstBufferOffset) };
                        handles[i] = memoryHandle;
 
                        firstBufferOffset = 0;
                    }
 
                    if (totalBytesToWrite == 0)
                    {
                        break;
                    }
 
                    long bytesWritten;
                    fixed (Interop.Sys.IOVector* pinnedVectors = &MemoryMarshal.GetReference(vectors))
                    {
                        bytesWritten = Interop.Sys.PWriteV(handle, pinnedVectors, buffersCount, fileOffset);
                    }
 
                    FileStreamHelpers.CheckFileCall(bytesWritten, handle.Path);
                    if (bytesWritten == totalBytesToWrite)
                    {
                        break;
                    }
 
                    // The write completed successfully but for fewer bytes than requested.
                    // We need to try again for the remainder.
                    for (int i = 0; i < buffersCount; i++)
                    {
                        int n = buffers[i].Length;
                        if (n <= bytesWritten)
                        {
                            buffersOffset++;
                            bytesWritten -= n;
                            if (bytesWritten == 0)
                            {
                                break;
                            }
                        }
                        else
                        {
                            firstBufferOffset = (int)(bytesWritten - n);
                            break;
                        }
                    }
                }
            }
            finally
            {
                foreach (MemoryHandle memoryHandle in handles)
                {
                    memoryHandle.Dispose();
                }
            }
        }
 
        internal static ValueTask WriteAtOffsetAsync(SafeFileHandle handle, ReadOnlyMemory<byte> buffer, long fileOffset, CancellationToken cancellationToken, OSFileStreamStrategy? strategy = null)
            => handle.GetThreadPoolValueTaskSource().QueueWrite(buffer, fileOffset, cancellationToken, strategy);
 
        private static ValueTask WriteGatherAtOffsetAsync(SafeFileHandle handle, IReadOnlyList<ReadOnlyMemory<byte>> buffers, long fileOffset, CancellationToken cancellationToken)
            => handle.GetThreadPoolValueTaskSource().QueueWriteGather(buffers, fileOffset, cancellationToken);
    }
}