File: System\IO\MemoryMappedFiles\MemoryMappedView.Unix.cs
Web Access
Project: src\src\libraries\System.IO.MemoryMappedFiles\src\System.IO.MemoryMappedFiles.csproj (System.IO.MemoryMappedFiles)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using Microsoft.Win32.SafeHandles;
 
namespace System.IO.MemoryMappedFiles
{
    internal sealed partial class MemoryMappedView
    {
        public static MemoryMappedView CreateView(
            SafeMemoryMappedFileHandle memMappedFileHandle, MemoryMappedFileAccess access,
            long requestedOffset, long requestedSize)
        {
            ArgumentOutOfRangeException.ThrowIfGreaterThan(requestedOffset, memMappedFileHandle._capacity, "offset");
            if (requestedSize > MaxProcessAddressSpace)
            {
                throw new IOException(SR.ArgumentOutOfRange_CapacityLargerThanLogicalAddressSpaceNotAllowed);
            }
            if (requestedOffset + requestedSize > memMappedFileHandle._capacity)
            {
                throw new UnauthorizedAccessException();
            }
            ObjectDisposedException.ThrowIf(memMappedFileHandle.IsClosed, memMappedFileHandle);
 
            if (requestedSize == MemoryMappedFile.DefaultSize)
            {
                requestedSize = memMappedFileHandle._capacity - requestedOffset;
            }
 
            // mmap can only create views that start at a multiple of the page size. As on Windows,
            // we hide this restriction form the user by creating larger views than the user requested and hiding the parts
            // that the user did not request.  extraMemNeeded is the amount of extra memory we allocate before the start of the
            // requested view. (mmap may round up the actual length such that it is also page-aligned; we hide that by using
            // the right size and not extending the size to be page-aligned.)
            ulong nativeSize;
            long extraMemNeeded, nativeOffset;
            long pageSize = Interop.Sys.SysConf(Interop.Sys.SysConfName._SC_PAGESIZE);
            Debug.Assert(pageSize > 0);
            ValidateSizeAndOffset(
                requestedSize, requestedOffset, pageSize,
                out nativeSize, out extraMemNeeded, out nativeOffset);
 
            // Determine whether to create the pages as private or as shared; the former is used for copy-on-write.
            Interop.Sys.MemoryMappedFlags flags =
                (memMappedFileHandle._access == MemoryMappedFileAccess.CopyOnWrite || access == MemoryMappedFileAccess.CopyOnWrite) ?
                Interop.Sys.MemoryMappedFlags.MAP_PRIVATE :
                Interop.Sys.MemoryMappedFlags.MAP_SHARED;
 
            // If we have a file handle, get the file descriptor from it.  If the handle is null,
            // we'll use an anonymous backing store for the map.
            SafeFileHandle fd;
            if (memMappedFileHandle._fileStreamHandle != null)
            {
                // Get the file descriptor from the SafeFileHandle
                fd = memMappedFileHandle._fileStreamHandle;
                Debug.Assert(!fd.IsInvalid);
            }
            else
            {
                fd = new SafeFileHandle(new IntPtr(-1), false);
                flags |= Interop.Sys.MemoryMappedFlags.MAP_ANONYMOUS;
            }
 
            // Nothing to do for options.DelayAllocatePages, since we're only creating the map
            // with mmap when creating the view.
 
            // Verify that the requested view permissions don't exceed the map's permissions
            Interop.Sys.MemoryMappedProtections viewProtForVerification = GetProtections(access, forVerification: true);
            Interop.Sys.MemoryMappedProtections mapProtForVerification = GetProtections(memMappedFileHandle._access, forVerification: true);
            if ((viewProtForVerification & mapProtForVerification) != viewProtForVerification)
            {
                throw new UnauthorizedAccessException();
            }
 
            // viewProtections is strictly less than mapProtections, so use viewProtections for actually creating the map.
            Interop.Sys.MemoryMappedProtections viewProtForCreation = GetProtections(access, forVerification: false);
 
            // Create the map
            IntPtr addr;
            if (nativeSize > 0)
            {
                addr = Interop.Sys.MMap(
                    IntPtr.Zero,         // don't specify an address; let the system choose one
                    nativeSize,          // specify the rounded-size we computed so as to page align; size + extraMemNeeded
                    viewProtForCreation,
                    flags,
                    fd,                  // mmap adds a ref count to the fd, so there's no need to dup it.
                    nativeOffset);       // specify the rounded-offset we computed so as to page align; offset - extraMemNeeded
            }
            else
            {
                // There are some corner cases where the .NET API allows the requested size to be zero, e.g. the caller is
                // creating a map at the end of the capacity.  We can't pass 0 to mmap, as that'll fail with EINVAL, nor can
                // we create a map that extends beyond the end of the underlying file, as that'll fail on some platforms at the
                // time of the map's creation.  Instead, since there's no data to be read/written, it doesn't actually matter
                // what backs the view, so we just create an anonymous mapping.
                addr = Interop.Sys.MMap(
                    IntPtr.Zero,
                    1,         // any length that's greater than zero will suffice
                    viewProtForCreation,
                    flags | Interop.Sys.MemoryMappedFlags.MAP_ANONYMOUS,
                    new SafeFileHandle(new IntPtr(-1), false),        // ignore the actual fd even if there was one
                    0);
                requestedSize = 0;
                extraMemNeeded = 0;
            }
            if (addr == IntPtr.Zero) // note that shim uses null pointer, not non-null MAP_FAILED sentinel
            {
                throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
            }
 
            // Based on the HandleInheritability, try to prevent the memory-mapped region
            // from being inherited by a forked process
            if (memMappedFileHandle._inheritability == HandleInheritability.None)
            {
                DisableForkingIfPossible(addr, nativeSize);
            }
 
            // Create and return the view handle
            var viewHandle = new SafeMemoryMappedViewHandle(addr, ownsHandle: true);
            viewHandle.Initialize((ulong)nativeSize);
            return new MemoryMappedView(
                viewHandle,
                extraMemNeeded,       // the view points to offset - extraMemNeeded, so we need to shift back by extraMemNeeded
                requestedSize,        // only allow access to the actual size requested
                access);
        }
 
        public unsafe void Flush(UIntPtr capacity)
        {
            if (capacity == UIntPtr.Zero)
                return;
 
            byte* ptr = null;
            try
            {
                _viewHandle.AcquirePointer(ref ptr);
                int result = Interop.Sys.MSync(
                    (IntPtr)ptr, (ulong)capacity,
                    Interop.Sys.MemoryMappedSyncFlags.MS_SYNC | Interop.Sys.MemoryMappedSyncFlags.MS_INVALIDATE);
                if (result < 0)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo());
                }
            }
            finally
            {
                if (ptr != null)
                {
                    _viewHandle.ReleasePointer();
                }
            }
        }
 
        /// <summary>Attempt to prevent the specified pages from being copied into forked processes.</summary>
        /// <param name="addr">The starting address.</param>
        /// <param name="length">The length.</param>
        private static void DisableForkingIfPossible(IntPtr addr, ulong length)
        {
            if (length > 0)
            {
                Interop.Sys.MAdvise(addr, length, Interop.Sys.MemoryAdvice.MADV_DONTFORK);
                // Intentionally ignore error code -- it's just a hint and it's not supported on all systems.
            }
        }
 
        /// <summary>
        /// The Windows implementation limits maps to the size of the logical address space.
        /// We use the same value here.
        /// </summary>
        private const long MaxProcessAddressSpace = 8192L * 1000 * 1000 * 1000;
 
        /// <summary>Maps a MemoryMappedFileAccess to the associated MemoryMappedProtections.</summary>
        internal static Interop.Sys.MemoryMappedProtections GetProtections(
            MemoryMappedFileAccess access, bool forVerification)
        {
            switch (access)
            {
                default:
                case MemoryMappedFileAccess.Read:
                    return Interop.Sys.MemoryMappedProtections.PROT_READ;
 
                case MemoryMappedFileAccess.Write:
                    return Interop.Sys.MemoryMappedProtections.PROT_WRITE;
 
                case MemoryMappedFileAccess.ReadWrite:
                    return
                        Interop.Sys.MemoryMappedProtections.PROT_READ |
                        Interop.Sys.MemoryMappedProtections.PROT_WRITE;
 
                case MemoryMappedFileAccess.ReadExecute:
                    return
                        Interop.Sys.MemoryMappedProtections.PROT_READ |
                        Interop.Sys.MemoryMappedProtections.PROT_EXEC;
 
                case MemoryMappedFileAccess.ReadWriteExecute:
                    return
                        Interop.Sys.MemoryMappedProtections.PROT_READ |
                        Interop.Sys.MemoryMappedProtections.PROT_WRITE |
                        Interop.Sys.MemoryMappedProtections.PROT_EXEC;
 
                case MemoryMappedFileAccess.CopyOnWrite:
                    return forVerification ?
                        Interop.Sys.MemoryMappedProtections.PROT_READ :
                        Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE;
            }
        }
    }
}