Exemple #1
0
 private static FileAccess TranslateProtectionsToFileAccess(Interop.libc.MemoryMappedProtections protections)
 {
     return
         ((protections & (Interop.libc.MemoryMappedProtections.PROT_READ | Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite :
          (protections & (Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.Write :
          FileAccess.Read);
 }
Exemple #2
0
        private static FileStream CreateSharedBackingObject(Interop.libc.MemoryMappedProtections protections, long capacity)
        {
            string path = TmpPathPrefix + Guid.NewGuid().ToString("N") + ".tmp";

            FileAccess access =
                (protections & (Interop.libc.MemoryMappedProtections.PROT_READ | Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite :
                (protections & (Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.Write :
                FileAccess.Read;

            // Create the backing file, then immediately unlink it so that it'll be cleaned up when no longer in use.
            // Then enlarge it to the requested capacity.
            const int DefaultBufferSize = 0x1000;
            var       fs = new FileStream(path, FileMode.CreateNew, TranslateProtectionsToFileAccess(protections), FileShare.ReadWrite, DefaultBufferSize);

            try
            {
                Interop.CheckIo(Interop.libc.unlink(path));
                fs.SetLength(capacity);
            }
            catch
            {
                fs.Dispose();
                throw;
            }
            return(fs);
        }
Exemple #3
0
        private static FileStream CreateSharedBackingObject(
            Interop.libc.MemoryMappedProtections protections, long capacity)
        {
            // The POSIX shared memory object name must begin with '/'.  After that we just want something short and unique.
            string mapName = "/corefx_map_" + Guid.NewGuid().ToString("N");

            // Determine the flags to use when creating the shared memory object
            Interop.libc.OpenFlags flags = (protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 ?
                                           Interop.libc.OpenFlags.O_RDWR :
                                           Interop.libc.OpenFlags.O_RDONLY;
            flags |= Interop.libc.OpenFlags.O_CREAT | Interop.libc.OpenFlags.O_EXCL; // CreateNew

            // Determine the permissions with which to create the file
            Interop.libc.Permissions perms = default(Interop.libc.Permissions);
            if ((protections & Interop.libc.MemoryMappedProtections.PROT_READ) != 0)
            {
                perms |= Interop.libc.Permissions.S_IRUSR;
            }
            if ((protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0)
            {
                perms |= Interop.libc.Permissions.S_IWUSR;
            }
            if ((protections & Interop.libc.MemoryMappedProtections.PROT_EXEC) != 0)
            {
                perms |= Interop.libc.Permissions.S_IXUSR;
            }

            // Create the shared memory object.
            int fd;

            Interop.CheckIo(fd = Interop.libc.shm_open(mapName, flags, (int)perms), mapName);
            SafeFileHandle fileHandle = new SafeFileHandle((IntPtr)fd, ownsHandle: true);

            try
            {
                // Unlink the shared memory object immediatley so that it'll go away once all handles
                // to it are closed (as with opened then unlinked files, it'll remain usable via
                // the open handles even though it's unlinked and can't be opened anew via its name).
                Interop.CheckIo(Interop.libc.shm_unlink(mapName));

                // Give it the right capacity.  We do this directly with ftruncate rather
                // than via FileStream.SetLength after the FileStream is created because, on some systems,
                // lseek fails on shared memory objects, causing the FileStream to think it's unseekable,
                // causing it to preemptively throw from SetLength.
                Interop.CheckIo(Interop.libc.ftruncate(fd, capacity));

                // Wrap the file descriptor in a stream and return it.
                return(new FileStream(fileHandle, TranslateProtectionsToFileAccess(protections)));
            }
            catch
            {
                fileHandle.Dispose();
                throw;
            }
        }
        private static unsafe SafeMemoryMappedFileHandle CreateCore(
            FileStream fileStream, string mapName,
            HandleInheritability inheritability, MemoryMappedFileAccess access,
            MemoryMappedFileOptions options, long capacity)
        {
            if (mapName != null)
            {
                // Named maps are not supported in our Unix implementation.  We could support named maps on Linux using
                // shared memory segments (shmget/shmat/shmdt/shmctl/etc.), but that doesn't work on OSX by default due
                // to very low default limits on OSX for the size of such objects; it also doesn't support behaviors
                // like copy-on-write or the ability to control handle inheritability, and reliably cleaning them up
                // relies on some non-conforming behaviors around shared memory IDs remaining valid even after they've
                // been marked for deletion (IPC_RMID).  We could also support named maps using the current implementation
                // by not unlinking after creating the backing store, but then the backing stores would remain around
                // and accessible even after process exit, with no good way to appropriately clean them up.
                // (File-backed maps may still be used for cross-process communication.)
                throw CreateNamedMapsNotSupportedException();
            }

            bool ownsFileStream = false;

            if (fileStream != null)
            {
                // This map is backed by a file.  Make sure the file's size is increased to be
                // at least as big as the requested capacity of the map.
                if (fileStream.Length < capacity)
                {
                    fileStream.SetLength(capacity);
                }
            }
            else
            {
                // This map is backed by memory-only.  With files, multiple views over the same map
                // will end up being able to share data through the same file-based backing store;
                // for anonymous maps, we need a similar backing store, or else multiple views would logically
                // each be their own map and wouldn't share any data.  To achieve this, we create a backing object
                // (either memory or on disk, depending on the system) and use its file descriptor as the file handle.
                // However, we only do this when the permission is more than read-only.  We can't change the size
                // of an object that has read-only permissions, but we also don't need to worry about sharing
                // views over a read-only, anonymous, memory-backed map, because the data will never change, so all views
                // will always see zero and can't change that.  In that case, we just use the built-in anonymous support of
                // the map by leaving fileStream as null.
                Interop.libc.MemoryMappedProtections protections = MemoryMappedView.GetProtections(access, forVerification: false);
                if ((protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 && capacity > 0)
                {
                    ownsFileStream = true;
                    fileStream     = CreateSharedBackingObject(protections, capacity);
                }
            }

            return(new SafeMemoryMappedFileHandle(fileStream, ownsFileStream, inheritability, access, options, capacity));
        }
Exemple #5
0
        private static unsafe SafeMemoryMappedFileHandle CreateCore(
            FileStream fileStream, string mapName,
            HandleInheritability inheritability, MemoryMappedFileAccess access,
            MemoryMappedFileOptions options, long capacity)
        {
            if (mapName != null)
            {
                // TODO: We currently do not support named maps.  We could possibly support
                // named maps in the future by using shm_open / shm_unlink, as we do for
                // giving internal names to anonymous maps.  Issues to work through will include
                // dealing with permissions, passing information from the creator of the
                // map to another opener of it, etc.
                throw CreateNamedMapsNotSupportedException();
            }

            SafeFileHandle fileHandle = null;

            if (fileStream != null)
            {
                // This map is backed by a file.  Make sure the file's size is increased to be
                // at least as big as the requested capacity of the map.
                fileHandle = fileStream.SafeFileHandle;
                if (fileStream.Length < capacity)
                {
                    fileStream.SetLength(capacity);
                }
            }
            else
            {
                // This map is backed by memory-only.  With files, multiple views over the same map
                // will end up being able to share data through the same file-based backing store;
                // for anonymous maps, we need a similar backing store, or else multiple views would logically
                // each be their own map and wouldn't share any data.  To achieve this, we create a POSIX shared
                // memory object and use its file descriptor as the file handle.  However, we only do this when the
                // permission is more than read-only.  We can't ftruncate to increase the size of a shared memory
                // object that has read-only permissions, but we also don't need to worry about sharing
                // views over a read-only, anonymous, memory-backed map, because the data will never change, so all views
                // will always see zero and can't change that.  In that case, we just use the built-in anonymous support of
                // the map by leaving fileHandle as null.
                Interop.libc.MemoryMappedProtections protections = MemoryMappedView.GetProtections(access, forVerification: false);
                if ((protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 && capacity > 0)
                {
                    mapName    = "/AnonCoreFxMemMap_" + Guid.NewGuid().ToString("N"); // unique name must start with "/" and be < NAME_MAX length
                    fileHandle = CreateNewSharedMemoryObject(mapName, protections, capacity);
                }
            }

            return(new SafeMemoryMappedFileHandle(mapName, fileHandle, inheritability, access, options, capacity));
        }
Exemple #6
0
            private unsafe void ChangeProtection(Interop.libc.MemoryMappedProtections prots)
            {
                byte *ptr = null;

                try
                {
                    AcquirePointer(ref ptr);
                    if (Interop.libc.mprotect((IntPtr)ptr, (IntPtr)ByteLength, prots) != 0)
                    {
                        throw CreateExceptionFromErrno();
                    }
                }
                finally
                {
                    if (ptr != null)
                    {
                        ReleasePointer();
                    }
                }
            }
        private static FileStream CreateSharedBackingObject(
            Interop.libc.MemoryMappedProtections protections, long capacity,
            out string mapName, out SafeMemoryMappedFileHandle.FileStreamSource fileHandleSource)
        {
            mapName          = MemoryMapObjectFilePrefix + Guid.NewGuid().ToString("N") + ".tmp";
            fileHandleSource = SafeMemoryMappedFileHandle.FileStreamSource.ManufacturedFile;

            FileAccess access =
                (protections & (Interop.libc.MemoryMappedProtections.PROT_READ | Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite :
                (protections & (Interop.libc.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.Write :
                FileAccess.Read;

            const int DefaultBufferSize = 0x1000;
            var       fs = new FileStream(Path.Combine(Path.GetTempPath(), mapName),
                                          FileMode.CreateNew, TranslateProtectionsToFileAccess(protections), FileShare.ReadWrite,
                                          DefaultBufferSize, FileOptions.DeleteOnClose);

            fs.SetLength(capacity);
            return(fs);
        }
Exemple #8
0
        private static FileStream CreateSharedBackingObject(
            Interop.libc.MemoryMappedProtections protections, long capacity,
            out string mapName, out SafeMemoryMappedFileHandle.FileStreamSource fileStreamSource)
        {
            // The POSIX shared memory object name must begin with '/'.  After that we just want something short and unique.
            mapName          = "/" + MemoryMapObjectFilePrefix + Guid.NewGuid().ToString("N");
            fileStreamSource = SafeMemoryMappedFileHandle.FileStreamSource.ManufacturedSharedMemory;

            // Determine the flags to use when creating the shared memory object
            Interop.libc.OpenFlags flags = (protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 ?
                                           Interop.libc.OpenFlags.O_RDWR :
                                           Interop.libc.OpenFlags.O_RDONLY;
            flags |= Interop.libc.OpenFlags.O_CREAT | Interop.libc.OpenFlags.O_EXCL; // CreateNew

            // Determine the permissions with which to create the file
            Interop.libc.Permissions perms = default(Interop.libc.Permissions);
            if ((protections & Interop.libc.MemoryMappedProtections.PROT_READ) != 0)
            {
                perms |= Interop.libc.Permissions.S_IRUSR;
            }
            if ((protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0)
            {
                perms |= Interop.libc.Permissions.S_IWUSR;
            }
            if ((protections & Interop.libc.MemoryMappedProtections.PROT_EXEC) != 0)
            {
                perms |= Interop.libc.Permissions.S_IXUSR;
            }

            // Create the shared memory object. Then enlarge it to the requested capacity.
            int fd;

            Interop.CheckIo(fd = Interop.libc.shm_open(mapName, flags, (int)perms), mapName);
            SafeFileHandle fileHandle = new SafeFileHandle((IntPtr)fd, ownsHandle: true);

            // Wrap the handle in a stream and return it.
            var fs = new FileStream(fileHandle, TranslateProtectionsToFileAccess(protections));

            fs.SetLength(capacity);
            return(fs);
        }
Exemple #9
0
        private static SafeFileHandle CreateNewSharedMemoryObject(
            string mapName, Interop.libc.MemoryMappedProtections protections, long capacity)
        {
            // Determine the flags to use when creating the shared memory object
            Interop.libc.OpenFlags flags = (protections & Interop.libc.MemoryMappedProtections.PROT_WRITE) != 0 ?
                                           Interop.libc.OpenFlags.O_RDWR :
                                           Interop.libc.OpenFlags.O_RDONLY;
            flags |= Interop.libc.OpenFlags.O_CREAT | Interop.libc.OpenFlags.O_EXCL; // CreateNew

            // Create the shared memory object
            int fd;

            Interop.CheckIo(fd = Interop.libc.shm_open(mapName, flags, (int)Interop.libc.Permissions.S_IRWXU), mapName);
            SafeFileHandle fileHandle = new SafeFileHandle((IntPtr)fd, ownsHandle: true);

            // Then enlarge it to the requested capacity
            bool gotFd = false;

            fileHandle.DangerousAddRef(ref gotFd);
            try
            {
                while (Interop.CheckIo(Interop.libc.ftruncate((int)fileHandle.DangerousGetHandle(), capacity), mapName))
                {
                    ;
                }
            }
            finally
            {
                if (gotFd)
                {
                    fileHandle.DangerousRelease();
                }
            }

            // Return the fd for the object
            return(fileHandle);
        }
        public unsafe static MemoryMappedView CreateView(
            SafeMemoryMappedFileHandle memMappedFileHandle, MemoryMappedFileAccess access,
            long requestedOffset, long requestedSize)
        {
            if (requestedOffset > memMappedFileHandle._capacity)
            {
                throw new ArgumentOutOfRangeException("offset");
            }
            if (requestedSize > MaxProcessAddressSpace)
            {
                throw new IOException(SR.ArgumentOutOfRange_CapacityLargerThanLogicalAddressSpaceNotAllowed);
            }
            if (requestedOffset + requestedSize > memMappedFileHandle._capacity)
            {
                throw new UnauthorizedAccessException();
            }
            if (memMappedFileHandle.IsClosed)
            {
                throw new ObjectDisposedException(typeof(MemoryMappedFile).Name);
            }

            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, extraMemNeeded, nativeOffset;
            int   pageSize = Interop.libc.sysconf(Interop.libc.SysConfNames._SC_PAGESIZE);

            ValidateSizeAndOffset(
                requestedSize, requestedOffset, pageSize,
                out nativeSize, out extraMemNeeded, out nativeOffset);
            if (nativeSize == 0)
            {
                nativeSize = (ulong)pageSize;
            }

            bool gotRefOnHandle = false;

            try
            {
                // Determine whether to create the pages as private or as shared; the former is used for copy-on-write.
                Interop.libc.MemoryMappedFlags flags =
                    (memMappedFileHandle._access == MemoryMappedFileAccess.CopyOnWrite || access == MemoryMappedFileAccess.CopyOnWrite) ?
                    Interop.libc.MemoryMappedFlags.MAP_PRIVATE :
                    Interop.libc.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.
                int fd;
                if (memMappedFileHandle._fileHandle != null)
                {
                    // Get the file descriptor from the SafeFileHandle
                    memMappedFileHandle._fileHandle.DangerousAddRef(ref gotRefOnHandle);
                    Debug.Assert(gotRefOnHandle);
                    fd = (int)memMappedFileHandle._fileHandle.DangerousGetHandle();
                    Debug.Assert(fd >= 0);
                }
                else
                {
                    Debug.Assert(!gotRefOnHandle);
                    fd     = -1;
                    flags |= Interop.libc.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.libc.MemoryMappedProtections viewProtForVerification = GetProtections(access, forVerification: true);
                Interop.libc.MemoryMappedProtections mapProtForVerification  = GetProtections(memMappedFileHandle._access, forVerification: true);
                if ((viewProtForVerification & mapProtForVerification) != viewProtForVerification)
                {
                    throw new UnauthorizedAccessException();
                }

                // Create the map
                IntPtr addr = Interop.libc.mmap(
                    IntPtr.Zero,                                    // don't specify an address; let the system choose one
                    (IntPtr)nativeSize,                             // specify the rounded-size we computed so as to page align; size + extraMemNeeded
                    GetProtections(access, forVerification: false), // viewProtections is strictly less than mapProtections, so use viewProtections
                    flags,
                    fd,                                             // mmap adds a ref count to the fd, so there's no need to dup it.
                    (long)nativeOffset);                            // specify the rounded-offset we computed so as to page align; offset - extraMemNeeded
                if ((long)addr < 0)
                {
                    throw Interop.GetExceptionForIoErrno(Marshal.GetLastWin32Error());
                }

                // Based on the HandleInheritability, try to prevent the memory-mapped region
                // from being inherited by a forked process
                if (memMappedFileHandle._inheritability == HandleInheritability.None)
                {
                    int adviseResult = Interop.libc.madvise(addr, (IntPtr)nativeSize, Interop.libc.MemoryMappedAdvice.MADV_DONTFORK);
                    Debug.Assert(adviseResult == 0); // In release, ignore failures from advise; it's just a hint, anyway.
                }

                // Create and return the view handle
                var viewHandle = new SafeMemoryMappedViewHandle(addr, ownsHandle: true);
                viewHandle.Initialize(nativeSize);
                return(new MemoryMappedView(
                           viewHandle,
                           (long)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));
            }
            finally
            {
                if (gotRefOnHandle)
                {
                    memMappedFileHandle._fileHandle.DangerousRelease();
                }
            }
        }
        public unsafe static MemoryMappedView CreateView(
            SafeMemoryMappedFileHandle memMappedFileHandle, MemoryMappedFileAccess access,
            long requestedOffset, long requestedSize)
        {
            if (requestedOffset > memMappedFileHandle._capacity)
            {
                throw new ArgumentOutOfRangeException("offset");
            }
            if (requestedSize > MaxProcessAddressSpace)
            {
                throw new IOException(SR.ArgumentOutOfRange_CapacityLargerThanLogicalAddressSpaceNotAllowed);
            }
            if (requestedOffset + requestedSize > memMappedFileHandle._capacity)
            {
                throw new UnauthorizedAccessException();
            }
            if (memMappedFileHandle.IsClosed)
            {
                throw new ObjectDisposedException(typeof(MemoryMappedFile).Name);
            }

            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, extraMemNeeded, nativeOffset;
            int   pageSize = Interop.libc.sysconf(Interop.libc.SysConfNames._SC_PAGESIZE);

            Debug.Assert(pageSize > 0);
            ValidateSizeAndOffset(
                requestedSize, requestedOffset, pageSize,
                out nativeSize, out extraMemNeeded, out nativeOffset);

            bool gotRefOnHandle = false;

            try
            {
                // Determine whether to create the pages as private or as shared; the former is used for copy-on-write.
                Interop.libc.MemoryMappedFlags flags =
                    (memMappedFileHandle._access == MemoryMappedFileAccess.CopyOnWrite || access == MemoryMappedFileAccess.CopyOnWrite) ?
                    Interop.libc.MemoryMappedFlags.MAP_PRIVATE :
                    Interop.libc.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.
                int fd;
                if (memMappedFileHandle._fileStream != null)
                {
                    // Get the file descriptor from the SafeFileHandle
                    memMappedFileHandle._fileStream.SafeFileHandle.DangerousAddRef(ref gotRefOnHandle);
                    Debug.Assert(gotRefOnHandle);
                    fd = (int)memMappedFileHandle._fileStream.SafeFileHandle.DangerousGetHandle();
                    Debug.Assert(fd >= 0);
                }
                else
                {
                    Debug.Assert(!gotRefOnHandle);
                    fd     = -1;
                    flags |= Interop.libc.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.libc.MemoryMappedProtections viewProtForVerification = GetProtections(access, forVerification: true);
                Interop.libc.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.libc.MemoryMappedProtections viewProtForCreation = GetProtections(access, forVerification: false);

                // Create the map
                IntPtr addr = IntPtr.Zero;
                if (nativeSize > 0)
                {
                    addr = Interop.libc.mmap(
                        IntPtr.Zero,         // don't specify an address; let the system choose one
                        (IntPtr)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.
                        (long)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.libc.mmap(
                        IntPtr.Zero,
                        (IntPtr)1, // any length that's greater than zero will suffice
                        viewProtForCreation,
                        flags | Interop.libc.MemoryMappedFlags.MAP_ANONYMOUS,
                        -1,        // ignore the actual fd even if there was one
                        0);
                    requestedSize  = 0;
                    extraMemNeeded = 0;
                }
                if ((long)addr < 0)
                {
                    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, (IntPtr)nativeSize);
                }

                // Create and return the view handle
                var viewHandle = new SafeMemoryMappedViewHandle(addr, ownsHandle: true);
                viewHandle.Initialize(nativeSize);
                return(new MemoryMappedView(
                           viewHandle,
                           (long)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));
            }
            finally
            {
                if (gotRefOnHandle)
                {
                    memMappedFileHandle._fileStream.SafeFileHandle.DangerousRelease();
                }
            }
        }