Revert some changes which don't have proper dependencies.
[mono-project.git] / mono / metadata / file-mmap-windows.c
blob792bbf8ac8988d204b5ec98c90312db78ae2fae5
1 /**
2 * \file
3 * MemoryMappedFile internal calls for Windows
5 * Copyright 2016 Microsoft
6 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
7 */
9 /*
10 * The code in this file has been inspired by the CoreFX MemoryMappedFile Windows implementation contained in the files
12 * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs
13 * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedView.Windows.cs
16 #include <config.h>
17 #include <glib.h>
18 #include <mono/utils/mono-compiler.h>
19 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) && defined(HOST_WIN32)
21 #include <glib.h>
23 #include <mono/metadata/file-mmap.h>
25 // These control the retry behaviour when lock violation errors occur during Flush:
26 #define MAX_FLUSH_WAITS 15 // must be <=30
27 #define MAX_FLUSH_RETIRES_PER_WAIT 20
29 typedef struct {
30 void *address;
31 size_t length;
32 } MmapInstance;
34 enum {
35 BAD_CAPACITY_FOR_FILE_BACKED = 1,
36 CAPACITY_SMALLER_THAN_FILE_SIZE,
37 FILE_NOT_FOUND,
38 FILE_ALREADY_EXISTS,
39 PATH_TOO_LONG,
40 COULD_NOT_OPEN,
41 CAPACITY_MUST_BE_POSITIVE,
42 INVALID_FILE_MODE,
43 COULD_NOT_MAP_MEMORY,
44 ACCESS_DENIED,
45 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
48 enum {
49 FILE_MODE_CREATE_NEW = 1,
50 FILE_MODE_CREATE = 2,
51 FILE_MODE_OPEN = 3,
52 FILE_MODE_OPEN_OR_CREATE = 4,
53 FILE_MODE_TRUNCATE = 5,
54 FILE_MODE_APPEND = 6,
57 enum {
58 MMAP_FILE_ACCESS_READ_WRITE = 0,
59 MMAP_FILE_ACCESS_READ = 1,
60 MMAP_FILE_ACCESS_WRITE = 2,
61 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
62 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
63 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
66 static DWORD get_page_access (int access)
68 switch (access) {
69 case MMAP_FILE_ACCESS_READ:
70 return PAGE_READONLY;
71 case MMAP_FILE_ACCESS_READ_WRITE:
72 return PAGE_READWRITE;
73 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
74 return PAGE_WRITECOPY;
75 case MMAP_FILE_ACCESS_READ_EXECUTE:
76 return PAGE_EXECUTE_READ;
77 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
78 return PAGE_EXECUTE_READWRITE;
79 default:
80 g_error ("unknown MemoryMappedFileAccess %d", access);
84 static DWORD get_file_access (int access)
86 switch (access) {
87 case MMAP_FILE_ACCESS_READ:
88 case MMAP_FILE_ACCESS_READ_EXECUTE:
89 return GENERIC_READ;
90 case MMAP_FILE_ACCESS_READ_WRITE:
91 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
92 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
93 return GENERIC_READ | GENERIC_WRITE;
94 case MMAP_FILE_ACCESS_WRITE:
95 return GENERIC_WRITE;
96 default:
97 g_error ("unknown MemoryMappedFileAccess %d", access);
101 static int get_file_map_access (int access)
103 switch (access) {
104 case MMAP_FILE_ACCESS_READ:
105 return FILE_MAP_READ;
106 case MMAP_FILE_ACCESS_WRITE:
107 return FILE_MAP_WRITE;
108 case MMAP_FILE_ACCESS_READ_WRITE:
109 return FILE_MAP_READ | FILE_MAP_WRITE;
110 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
111 return FILE_MAP_COPY;
112 case MMAP_FILE_ACCESS_READ_EXECUTE:
113 return FILE_MAP_EXECUTE | FILE_MAP_READ;
114 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
115 return FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE;
116 default:
117 g_error ("unknown MemoryMappedFileAccess %d", access);
121 static int convert_win32_error (int win32error, int default_)
123 switch (win32error) {
124 case ERROR_FILE_NOT_FOUND:
125 return FILE_NOT_FOUND;
126 case ERROR_FILE_EXISTS:
127 case ERROR_ALREADY_EXISTS:
128 return FILE_ALREADY_EXISTS;
129 case ERROR_ACCESS_DENIED:
130 return ACCESS_DENIED;
132 return default_;
135 static void*
136 open_handle (void *handle, const gunichar2 *mapName, gint mapName_length, int mode, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
138 g_assert (handle != NULL);
140 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
141 // backed by physical memory / pagefile.
143 if (handle == INVALID_HANDLE_VALUE) {
144 if (*capacity <= 0 && mode != FILE_MODE_OPEN) {
145 *ioerror = CAPACITY_MUST_BE_POSITIVE;
146 return NULL;
148 #if SIZEOF_VOID_P == 4
149 if (*capacity > UINT32_MAX) {
150 *ioerror = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
151 return NULL;
153 #endif
154 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
155 *ioerror = INVALID_FILE_MODE;
156 return NULL;
158 } else {
159 FILE_STANDARD_INFO info;
160 gboolean getinfo_success;
161 MONO_ENTER_GC_SAFE;
162 getinfo_success = GetFileInformationByHandleEx (handle, FileStandardInfo, &info, sizeof (FILE_STANDARD_INFO));
163 MONO_EXIT_GC_SAFE;
164 if (!getinfo_success) {
165 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
166 return NULL;
168 if (*capacity == 0) {
169 if (info.EndOfFile.QuadPart == 0) {
170 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
171 return NULL;
173 } else if (*capacity < info.EndOfFile.QuadPart) {
174 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
175 return NULL;
179 HANDLE result = NULL;
181 if (mode == FILE_MODE_CREATE_NEW || handle != INVALID_HANDLE_VALUE) {
182 MONO_ENTER_GC_SAFE;
183 result = CreateFileMappingW (handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, mapName);
184 MONO_EXIT_GC_SAFE;
185 if (result && GetLastError () == ERROR_ALREADY_EXISTS) {
186 MONO_ENTER_GC_SAFE;
187 CloseHandle (result);
188 MONO_EXIT_GC_SAFE;
189 result = NULL;
190 *ioerror = FILE_ALREADY_EXISTS;
191 } else if (!result && GetLastError () != NO_ERROR) {
192 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
194 } else if (mode == FILE_MODE_OPEN || mode == FILE_MODE_OPEN_OR_CREATE && access == MMAP_FILE_ACCESS_WRITE) {
195 MONO_ENTER_GC_SAFE;
196 result = OpenFileMappingW (get_file_map_access (access), FALSE, mapName);
197 MONO_EXIT_GC_SAFE;
198 if (!result) {
199 if (mode == FILE_MODE_OPEN_OR_CREATE && GetLastError () == ERROR_FILE_NOT_FOUND) {
200 *ioerror = INVALID_FILE_MODE;
201 } else {
202 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
205 } else if (mode == FILE_MODE_OPEN_OR_CREATE) {
207 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
209 /// Try to open the file if it exists -- this requires a bit more work. Loop until we can
210 /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail
211 /// if the file exists and we have non-null security attributes, in which case we need to
212 /// use OpenFileMapping. But, there exists a race condition because the memory mapped file
213 /// may have closed between the two calls -- hence the loop.
214 ///
215 /// The retry/timeout logic increases the wait time each pass through the loop and times
216 /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened,
217 /// throw an InvalidOperationException.
219 guint32 waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins
220 guint32 waitSleep = 0;
222 while (waitRetries > 0) {
223 MONO_ENTER_GC_SAFE;
224 result = CreateFileMappingW (handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, mapName);
225 MONO_EXIT_GC_SAFE;
226 if (result)
227 break;
228 if (GetLastError() != ERROR_ACCESS_DENIED) {
229 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
230 break;
232 MONO_ENTER_GC_SAFE;
233 result = OpenFileMappingW (get_file_map_access (access), FALSE, mapName);
234 MONO_EXIT_GC_SAFE;
235 if (result)
236 break;
237 if (GetLastError () != ERROR_FILE_NOT_FOUND) {
238 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
239 break;
241 // increase wait time
242 --waitRetries;
243 if (waitSleep == 0) {
244 waitSleep = 10;
245 } else {
246 mono_thread_info_sleep (waitSleep, NULL);
247 waitSleep *= 2;
251 if (!result) {
252 *ioerror = COULD_NOT_OPEN;
256 return result;
259 void*
260 mono_mmap_open_file (const gunichar2 *path, gint path_length, int mode, const gunichar2 *mapName, gint mapName_length, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
262 g_assert (path != NULL || mapName != NULL);
264 HANDLE hFile = INVALID_HANDLE_VALUE;
265 HANDLE result = NULL;
266 gboolean delete_on_error = FALSE;
268 if (path) {
269 WIN32_FILE_ATTRIBUTE_DATA file_attrs;
270 gboolean existed;
271 MONO_ENTER_GC_SAFE;
272 existed = GetFileAttributesExW (path, GetFileExInfoStandard, &file_attrs);
273 MONO_EXIT_GC_SAFE;
274 if (!existed && mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
275 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
276 goto done;
278 MONO_ENTER_GC_SAFE;
279 hFile = CreateFileW (path, get_file_access (access), FILE_SHARE_READ, NULL, mode, FILE_ATTRIBUTE_NORMAL, NULL);
280 MONO_EXIT_GC_SAFE;
281 if (hFile == INVALID_HANDLE_VALUE) {
282 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
283 goto done;
285 delete_on_error = !existed;
286 } else {
287 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
288 // backed by physical memory / pagefile.
291 result = open_handle (hFile, mapName, mapName_length, mode, capacity, access, options, ioerror, error);
293 done:
294 MONO_ENTER_GC_SAFE;
295 if (hFile != INVALID_HANDLE_VALUE)
296 CloseHandle (hFile);
297 if (!result && delete_on_error)
298 DeleteFileW (path);
299 MONO_EXIT_GC_SAFE;
301 return result;
304 void*
305 mono_mmap_open_handle (void *handle, const gunichar2 *mapName, gint mapName_length, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
307 g_assert (handle != NULL);
309 return open_handle (handle, mapName, mapName_length, FILE_MODE_OPEN, capacity, access, options, ioerror, error);
312 void
313 mono_mmap_close (void *mmap_handle, MonoError *error)
315 g_assert (mmap_handle);
316 MONO_ENTER_GC_SAFE;
317 CloseHandle (mmap_handle);
318 MONO_EXIT_GC_SAFE;
321 void
322 mono_mmap_configure_inheritability (void *mmap_handle, gint32 inheritability, MonoError *error)
324 g_assert (mmap_handle);
325 if (!SetHandleInformation (mmap_handle, HANDLE_FLAG_INHERIT, inheritability ? HANDLE_FLAG_INHERIT : 0)) {
326 g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ());
330 void
331 mono_mmap_flush (void *mmap_handle, MonoError *error)
333 g_assert (mmap_handle);
334 MmapInstance *h = (MmapInstance *)mmap_handle;
336 gboolean flush_success;
337 MONO_ENTER_GC_SAFE;
338 flush_success = FlushViewOfFile (h->address, h->length);
339 MONO_EXIT_GC_SAFE;
340 if (flush_success)
341 return;
344 // This replicates how CoreFX does MemoryMappedView.Flush ().
346 // It is a known issue within the NTFS transaction log system that
347 // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
348 // As a workaround, we catch this particular error and retry the flush operation
349 // a few milliseconds later. If it does not work, we give it a few more tries with
350 // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
351 // this strategy successfully flushed the view after no more than 3 retries.
353 if (GetLastError () != ERROR_LOCK_VIOLATION)
354 // TODO: Propagate error to caller
355 return;
357 for (int w = 0; w < MAX_FLUSH_WAITS; w++) {
358 int pause = (1 << w); // MaxFlushRetries should never be over 30
359 mono_thread_info_sleep (pause, NULL);
361 for (int r = 0; r < MAX_FLUSH_RETIRES_PER_WAIT; r++) {
362 MONO_ENTER_GC_SAFE;
363 flush_success = FlushViewOfFile (h->address, h->length);
364 MONO_EXIT_GC_SAFE;
365 if (flush_success)
366 return;
368 if (GetLastError () != ERROR_LOCK_VIOLATION)
369 // TODO: Propagate error to caller
370 return;
372 mono_thread_info_yield ();
376 // We got to here, so there was no success:
377 // TODO: Propagate error to caller
381 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address, MonoError *error)
383 static DWORD allocationGranularity = 0;
384 if (allocationGranularity == 0) {
385 SYSTEM_INFO info;
386 GetSystemInfo (&info);
387 allocationGranularity = info.dwAllocationGranularity;
390 gint64 extraMemNeeded = offset % allocationGranularity;
391 guint64 newOffset = offset - extraMemNeeded;
392 gint64 nativeSize = (*size != 0) ? *size + extraMemNeeded : 0;
394 #if SIZEOF_VOID_P == 4
395 if (nativeSize > UINT32_MAX)
396 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
397 #endif
399 void *address;
400 MONO_ENTER_GC_SAFE;
401 address = MapViewOfFile (handle, get_file_map_access (access), (DWORD) (newOffset >> 32), (DWORD) newOffset, (SIZE_T) nativeSize);
402 MONO_EXIT_GC_SAFE;
403 if (!address)
404 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
406 // Query the view for its size and allocation type
407 MEMORY_BASIC_INFORMATION viewInfo;
408 MONO_ENTER_GC_SAFE;
409 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
410 MONO_EXIT_GC_SAFE;
411 guint64 viewSize = (guint64) viewInfo.RegionSize;
413 // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
414 // OR check if the allocated view size is smaller than the expected native size
415 // If multiple overlapping views are created over the file mapping object, the pages in a given region
416 // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
417 // views created on a mapping object backed by same file.
418 // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
419 // but more pages may need to be committed in the region.
420 // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
421 // and size of the region of pages with matching attributes starting from base address.
422 // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
423 if (((viewInfo.State & MEM_RESERVE) != 0) || viewSize < (guint64) nativeSize) {
424 void *tempAddress;
425 MONO_ENTER_GC_SAFE;
426 tempAddress = VirtualAlloc (address, nativeSize != 0 ? nativeSize : viewSize, MEM_COMMIT, get_page_access (access));
427 MONO_EXIT_GC_SAFE;
428 if (!tempAddress) {
429 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
431 // again query the view for its new size
432 MONO_ENTER_GC_SAFE;
433 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
434 MONO_EXIT_GC_SAFE;
435 viewSize = (guint64) viewInfo.RegionSize;
438 if (*size == 0)
439 *size = viewSize - extraMemNeeded;
441 MmapInstance *h = g_malloc0 (sizeof (MmapInstance));
442 h->address = address;
443 h->length = *size + extraMemNeeded;
444 *mmap_handle = h;
445 *base_address = (char*) address + (offset - newOffset);
447 return 0;
450 MonoBoolean
451 mono_mmap_unmap (void *mmap_handle, MonoError *error)
453 g_assert (mmap_handle);
455 MmapInstance *h = (MmapInstance *) mmap_handle;
457 gboolean result;
458 MONO_ENTER_GC_SAFE;
459 result = UnmapViewOfFile (h->address);
460 MONO_EXIT_GC_SAFE;
462 g_free (h);
463 return result;
466 #else
468 MONO_EMPTY_SOURCE_FILE (file_mmap_windows);
470 #endif