private static FileStream CreateSharedBackingObjectUsingFile(Interop.Sys.MemoryMappedProtections protections, long capacity) { string tempMapsDirectory = s_tempMapsDirectory ?? (s_tempMapsDirectory = PersistedFiles.GetTempFeatureDirectory("maps")); Directory.CreateDirectory(tempMapsDirectory); string path = Path.Combine(tempMapsDirectory, Guid.NewGuid().ToString("N")); FileAccess access = (protections & (Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite : (protections & (Interop.Sys.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.Sys.Unlink(path)); fs.SetLength(capacity); } catch { fs.Dispose(); throw; } return(fs); }
private static FileAccess TranslateProtectionsToFileAccess(Interop.Sys.MemoryMappedProtections protections) { return ((protections & (Interop.Sys.MemoryMappedProtections.PROT_READ | Interop.Sys.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.ReadWrite : (protections & (Interop.Sys.MemoryMappedProtections.PROT_WRITE)) != 0 ? FileAccess.Write : FileAccess.Read); }
private static FileStream CreateSharedBackingObjectUsingFile(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability) { // We create a temporary backing file in TMPDIR. We don't bother putting it into subdirectories as the file exists // extremely briefly: it's opened/created and then immediately unlinked. string path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); FileShare share = inheritability == HandleInheritability.None ? FileShare.ReadWrite : FileShare.ReadWrite | FileShare.Inheritable; // 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), share, DefaultBufferSize); try { Interop.CheckIo(Interop.Sys.Unlink(path)); fs.SetLength(capacity); } catch { fs.Dispose(); throw; } return(fs); }
private unsafe void ChangeProtection(Interop.Sys.MemoryMappedProtections prots) { byte *ptr = null; try { AcquirePointer(ref ptr); if (Interop.Sys.MProtect((IntPtr)ptr, ByteLength, prots) != 0) { throw CreateExceptionFromErrno(); } } finally { if (ptr != null) { ReleasePointer(); } } }
public 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(nameof(MemoryMappedFile)); } 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._fileStream != null) { // Get the file descriptor from the SafeFileHandle fd = memMappedFileHandle._fileStream.SafeFileHandle; 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 = IntPtr.Zero; 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)); }
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) { try { fileStream.SetLength(capacity); } catch (ArgumentException exc) { // If the capacity is too large, we'll get an ArgumentException from SetLength, // but on Windows this same condition is represented by an IOException. throw new IOException(exc.Message, exc); } } } 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.Sys.MemoryMappedProtections protections = MemoryMappedView.GetProtections(access, forVerification: false); if ((protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0 && capacity > 0) { ownsFileStream = true; fileStream = CreateSharedBackingObject(protections, capacity); // If the MMF handle should not be inherited, mark the backing object fd as O_CLOEXEC. if (inheritability == HandleInheritability.None) { Interop.CheckIo(Interop.Sys.Fcntl.SetCloseOnExec(fileStream.SafeFileHandle)); } } } return(new SafeMemoryMappedFileHandle(fileStream, ownsFileStream, inheritability, access, options, capacity)); }
// ----------------------------- // ---- PAL layer ends here ---- // ----------------------------- private static FileStream CreateSharedBackingObjectUsingMemory( Interop.Sys.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.Sys.OpenFlags flags = (protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0 ? Interop.Sys.OpenFlags.O_RDWR : Interop.Sys.OpenFlags.O_RDONLY; flags |= Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL; // CreateNew // Determine the permissions with which to create the file Interop.Sys.Permissions perms = default(Interop.Sys.Permissions); if ((protections & Interop.Sys.MemoryMappedProtections.PROT_READ) != 0) { perms |= Interop.Sys.Permissions.S_IRUSR; } if ((protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0) { perms |= Interop.Sys.Permissions.S_IWUSR; } if ((protections & Interop.Sys.MemoryMappedProtections.PROT_EXEC) != 0) { perms |= Interop.Sys.Permissions.S_IXUSR; } // Create the shared memory object. SafeFileHandle fd = Interop.Sys.ShmOpen(mapName, flags, (int)perms); if (fd.IsInvalid) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); if (errorInfo.Error == Interop.Error.ENOTSUP) { // If ShmOpen is not supported, fall back to file backing object. // Note that the System.Native shim will force this failure on platforms where // the result of native shm_open does not work well with our subsequent call // to mmap. return(null); } throw Interop.GetExceptionForIoErrno(errorInfo); } try { // Unlink the shared memory object immediately 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.Sys.ShmUnlink(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.Sys.FTruncate(fd, capacity)); // Wrap the file descriptor in a stream and return it. return(new FileStream(fd, TranslateProtectionsToFileAccess(protections))); } catch { fd.Dispose(); throw; } }
private static FileStream CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity) { return(CreateSharedBackingObjectUsingMemory(protections, capacity) ?? CreateSharedBackingObjectUsingFile(protections, capacity)); }
private static FileStream?CreateSharedBackingObjectUsingMemory( Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability) { // Determine the flags to use when creating the shared memory object Interop.Sys.OpenFlags flags = (protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0 ? Interop.Sys.OpenFlags.O_RDWR : Interop.Sys.OpenFlags.O_RDONLY; flags |= Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL; // CreateNew // Determine the permissions with which to create the file Interop.Sys.Permissions perms = default(Interop.Sys.Permissions); if ((protections & Interop.Sys.MemoryMappedProtections.PROT_READ) != 0) { perms |= Interop.Sys.Permissions.S_IRUSR; } if ((protections & Interop.Sys.MemoryMappedProtections.PROT_WRITE) != 0) { perms |= Interop.Sys.Permissions.S_IWUSR; } if ((protections & Interop.Sys.MemoryMappedProtections.PROT_EXEC) != 0) { perms |= Interop.Sys.Permissions.S_IXUSR; } string mapName; SafeFileHandle fd; do { mapName = GenerateMapName(); fd = Interop.Sys.ShmOpen(mapName, flags, (int)perms); // Create the shared memory object. if (fd.IsInvalid) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); fd.Dispose(); if (errorInfo.Error == Interop.Error.ENOTSUP) { // If ShmOpen is not supported, fall back to file backing object. // Note that the System.Native shim will force this failure on platforms where // the result of native shm_open does not work well with our subsequent call to mmap. return(null); } else if (errorInfo.Error != Interop.Error.EEXIST) // map with same name already existed { throw Interop.GetExceptionForIoErrno(errorInfo); } } } while (fd.IsInvalid); try { // Unlink the shared memory object immediately 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.Sys.ShmUnlink(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.Sys.FTruncate(fd, capacity)); // shm_open sets CLOEXEC implicitly. If the inheritability requested is Inheritable, remove CLOEXEC. if (inheritability == HandleInheritability.Inheritable && Interop.Sys.Fcntl.SetFD(fd, 0) == -1) { throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo()); } // Wrap the file descriptor in a stream and return it. return(new FileStream(fd, TranslateProtectionsToFileAccess(protections))); } catch { fd.Dispose(); throw; }