This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/
MemoryMappedFile.Unix.cs
236 lines (212 loc) · 12.3 KB
/
MemoryMappedFile.Unix.cs
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
// 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.Security;
namespace System.IO.MemoryMappedFiles
{
public partial class MemoryMappedFile
{
/// <summary>
/// Used by the 2 Create factory method groups. A null fileHandle specifies that the
/// memory mapped file should not be associated with an existing file on disk (i.e. start
/// out empty).
/// </summary>
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, inheritability);
}
}
return new SafeMemoryMappedFileHandle(fileStream, ownsFileStream, inheritability, access, options, capacity);
}
/// <summary>
/// Used by the CreateOrOpen factory method groups.
/// </summary>
private static SafeMemoryMappedFileHandle CreateOrOpenCore(
string mapName,
HandleInheritability inheritability, MemoryMappedFileAccess access,
MemoryMappedFileOptions options, long capacity)
{
// Since we don't support mapName != null, CreateOrOpenCore can't
// be used to Open an existing map, and thus is identical to CreateCore.
return CreateCore(null, mapName, inheritability, access, options, capacity);
}
/// <summary>
/// Used by the OpenExisting factory method group and by CreateOrOpen if access is write.
/// We'll throw an ArgumentException if the file mapping object didn't exist and the
/// caller used CreateOrOpen since Create isn't valid with Write access
/// </summary>
private static SafeMemoryMappedFileHandle OpenCore(
string mapName, HandleInheritability inheritability, MemoryMappedFileAccess access, bool createOrOpen)
{
throw CreateNamedMapsNotSupportedException();
}
/// <summary>
/// Used by the OpenExisting factory method group and by CreateOrOpen if access is write.
/// We'll throw an ArgumentException if the file mapping object didn't exist and the
/// caller used CreateOrOpen since Create isn't valid with Write access
/// </summary>
private static SafeMemoryMappedFileHandle OpenCore(
string mapName, HandleInheritability inheritability, MemoryMappedFileRights rights, bool createOrOpen)
{
throw CreateNamedMapsNotSupportedException();
}
// -----------------------------
// ---- PAL layer ends here ----
// -----------------------------
/// <summary>Gets an exception indicating that named maps are not supported on this platform.</summary>
private static Exception CreateNamedMapsNotSupportedException()
{
return new PlatformNotSupportedException(SR.PlatformNotSupported_NamedMaps);
}
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 CreateSharedBackingObject(Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
return CreateSharedBackingObjectUsingMemory(protections, capacity, inheritability)
?? CreateSharedBackingObjectUsingFile(protections, capacity, inheritability);
}
// -----------------------------
// ---- PAL layer ends here ----
// -----------------------------
private static FileStream CreateSharedBackingObjectUsingMemory(
Interop.Sys.MemoryMappedProtections protections, long capacity, HandleInheritability inheritability)
{
// 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));
// 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;
}
}
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;
}
}
}