[debugger] Ports from dotnet/runtime to maintain compatibility (#21653)
[mono-project.git] / mono / metadata / file-mmap-windows.c
blobea4c3266b50d8305ed94666ae11c97cbcd72dcee
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 #ifdef HOST_WIN32
20 #include <mono/utils/mono-error.h>
21 #include <mono/utils/mono-error-internals.h>
22 #include <mono/metadata/object-internals.h>
23 #include <mono/metadata/file-mmap.h>
24 #include <mono/utils/w32subset.h>
26 #if HAVE_API_SUPPORT_WIN32_FILE_MAPPING
27 // These control the retry behaviour when lock violation errors occur during Flush:
28 #define MAX_FLUSH_WAITS 15 // must be <=30
29 #define MAX_FLUSH_RETIRES_PER_WAIT 20
31 typedef struct {
32 void *address;
33 size_t length;
34 } MmapInstance;
36 enum {
37 BAD_CAPACITY_FOR_FILE_BACKED = 1,
38 CAPACITY_SMALLER_THAN_FILE_SIZE,
39 FILE_NOT_FOUND,
40 FILE_ALREADY_EXISTS,
41 PATH_TOO_LONG,
42 COULD_NOT_OPEN,
43 CAPACITY_MUST_BE_POSITIVE,
44 INVALID_FILE_MODE,
45 COULD_NOT_MAP_MEMORY,
46 ACCESS_DENIED,
47 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
50 enum {
51 FILE_MODE_CREATE_NEW = 1,
52 FILE_MODE_CREATE = 2,
53 FILE_MODE_OPEN = 3,
54 FILE_MODE_OPEN_OR_CREATE = 4,
55 FILE_MODE_TRUNCATE = 5,
56 FILE_MODE_APPEND = 6,
59 enum {
60 MMAP_FILE_ACCESS_READ_WRITE = 0,
61 MMAP_FILE_ACCESS_READ = 1,
62 MMAP_FILE_ACCESS_WRITE = 2,
63 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
64 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
65 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
68 static DWORD get_page_access (int access)
70 switch (access) {
71 case MMAP_FILE_ACCESS_READ:
72 return PAGE_READONLY;
73 case MMAP_FILE_ACCESS_READ_WRITE:
74 return PAGE_READWRITE;
75 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
76 return PAGE_WRITECOPY;
77 case MMAP_FILE_ACCESS_READ_EXECUTE:
78 return PAGE_EXECUTE_READ;
79 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
80 return PAGE_EXECUTE_READWRITE;
81 default:
82 g_error ("unknown MemoryMappedFileAccess %d", access);
86 static DWORD get_file_access (int access)
88 switch (access) {
89 case MMAP_FILE_ACCESS_READ:
90 case MMAP_FILE_ACCESS_READ_EXECUTE:
91 return GENERIC_READ;
92 case MMAP_FILE_ACCESS_READ_WRITE:
93 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
94 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
95 return GENERIC_READ | GENERIC_WRITE;
96 case MMAP_FILE_ACCESS_WRITE:
97 return GENERIC_WRITE;
98 default:
99 g_error ("unknown MemoryMappedFileAccess %d", access);
103 static int get_file_map_access (int access)
105 switch (access) {
106 case MMAP_FILE_ACCESS_READ:
107 return FILE_MAP_READ;
108 case MMAP_FILE_ACCESS_WRITE:
109 return FILE_MAP_WRITE;
110 case MMAP_FILE_ACCESS_READ_WRITE:
111 return FILE_MAP_READ | FILE_MAP_WRITE;
112 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
113 return FILE_MAP_COPY;
114 case MMAP_FILE_ACCESS_READ_EXECUTE:
115 return FILE_MAP_EXECUTE | FILE_MAP_READ;
116 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
117 return FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE;
118 default:
119 g_error ("unknown MemoryMappedFileAccess %d", access);
123 static int convert_win32_error (int win32error, int default_)
125 switch (win32error) {
126 case ERROR_FILE_NOT_FOUND:
127 return FILE_NOT_FOUND;
128 case ERROR_FILE_EXISTS:
129 case ERROR_ALREADY_EXISTS:
130 return FILE_ALREADY_EXISTS;
131 case ERROR_ACCESS_DENIED:
132 return ACCESS_DENIED;
134 return default_;
137 static void*
138 open_handle (void *handle, const gunichar2 *mapName, gint mapName_length, int mode, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
140 g_assert (handle != NULL);
142 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
143 // backed by physical memory / pagefile.
145 if (handle == INVALID_HANDLE_VALUE) {
146 if (*capacity <= 0 && mode != FILE_MODE_OPEN) {
147 *ioerror = CAPACITY_MUST_BE_POSITIVE;
148 return NULL;
150 #if SIZEOF_VOID_P == 4
151 if (*capacity > UINT32_MAX) {
152 *ioerror = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
153 return NULL;
155 #endif
156 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
157 *ioerror = INVALID_FILE_MODE;
158 return NULL;
160 } else {
161 FILE_STANDARD_INFO info;
162 gboolean getinfo_success;
163 MONO_ENTER_GC_SAFE;
164 getinfo_success = GetFileInformationByHandleEx (handle, FileStandardInfo, &info, sizeof (FILE_STANDARD_INFO));
165 MONO_EXIT_GC_SAFE;
166 if (!getinfo_success) {
167 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
168 return NULL;
170 if (*capacity == 0) {
171 if (info.EndOfFile.QuadPart == 0) {
172 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
173 return NULL;
175 } else if (*capacity < info.EndOfFile.QuadPart) {
176 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
177 return NULL;
181 HANDLE result = NULL;
183 if (mode == FILE_MODE_CREATE_NEW || handle != INVALID_HANDLE_VALUE) {
184 MONO_ENTER_GC_SAFE;
185 result = CreateFileMappingW (handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, mapName);
186 MONO_EXIT_GC_SAFE;
187 if (result && GetLastError () == ERROR_ALREADY_EXISTS) {
188 MONO_ENTER_GC_SAFE;
189 CloseHandle (result);
190 MONO_EXIT_GC_SAFE;
191 result = NULL;
192 *ioerror = FILE_ALREADY_EXISTS;
193 } else if (!result && GetLastError () != NO_ERROR) {
194 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
196 } else if (mode == FILE_MODE_OPEN || mode == FILE_MODE_OPEN_OR_CREATE && access == MMAP_FILE_ACCESS_WRITE) {
197 MONO_ENTER_GC_SAFE;
198 result = OpenFileMappingW (get_file_map_access (access), FALSE, mapName);
199 MONO_EXIT_GC_SAFE;
200 if (!result) {
201 if (mode == FILE_MODE_OPEN_OR_CREATE && GetLastError () == ERROR_FILE_NOT_FOUND) {
202 *ioerror = INVALID_FILE_MODE;
203 } else {
204 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
207 } else if (mode == FILE_MODE_OPEN_OR_CREATE) {
209 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
211 /// Try to open the file if it exists -- this requires a bit more work. Loop until we can
212 /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail
213 /// if the file exists and we have non-null security attributes, in which case we need to
214 /// use OpenFileMapping. But, there exists a race condition because the memory mapped file
215 /// may have closed between the two calls -- hence the loop.
216 ///
217 /// The retry/timeout logic increases the wait time each pass through the loop and times
218 /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened,
219 /// throw an InvalidOperationException.
221 guint32 waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins
222 guint32 waitSleep = 0;
224 while (waitRetries > 0) {
225 MONO_ENTER_GC_SAFE;
226 result = CreateFileMappingW (handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, mapName);
227 MONO_EXIT_GC_SAFE;
228 if (result)
229 break;
230 if (GetLastError() != ERROR_ACCESS_DENIED) {
231 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
232 break;
234 MONO_ENTER_GC_SAFE;
235 result = OpenFileMappingW (get_file_map_access (access), FALSE, mapName);
236 MONO_EXIT_GC_SAFE;
237 if (result)
238 break;
239 if (GetLastError () != ERROR_FILE_NOT_FOUND) {
240 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
241 break;
243 // increase wait time
244 --waitRetries;
245 if (waitSleep == 0) {
246 waitSleep = 10;
247 } else {
248 mono_thread_info_sleep (waitSleep, NULL);
249 waitSleep *= 2;
253 if (!result) {
254 *ioerror = COULD_NOT_OPEN;
258 return result;
261 void*
262 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)
264 g_assert (path != NULL || mapName != NULL);
266 HANDLE hFile = INVALID_HANDLE_VALUE;
267 HANDLE result = NULL;
268 gboolean delete_on_error = FALSE;
270 if (path) {
271 WIN32_FILE_ATTRIBUTE_DATA file_attrs;
272 gboolean existed;
273 MONO_ENTER_GC_SAFE;
274 existed = GetFileAttributesExW (path, GetFileExInfoStandard, &file_attrs);
275 MONO_EXIT_GC_SAFE;
276 if (!existed && mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
277 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
278 goto done;
280 MONO_ENTER_GC_SAFE;
281 hFile = CreateFileW (path, get_file_access (access), FILE_SHARE_READ, NULL, mode, FILE_ATTRIBUTE_NORMAL, NULL);
282 MONO_EXIT_GC_SAFE;
283 if (hFile == INVALID_HANDLE_VALUE) {
284 *ioerror = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
285 goto done;
287 delete_on_error = !existed;
288 } else {
289 // INVALID_HANDLE_VALUE (-1) is valid, to make named shared memory,
290 // backed by physical memory / pagefile.
293 result = open_handle (hFile, mapName, mapName_length, mode, capacity, access, options, ioerror, error);
295 done:
296 MONO_ENTER_GC_SAFE;
297 if (hFile != INVALID_HANDLE_VALUE)
298 CloseHandle (hFile);
299 if (!result && delete_on_error)
300 DeleteFileW (path);
301 MONO_EXIT_GC_SAFE;
303 return result;
306 void*
307 mono_mmap_open_handle (void *handle, const gunichar2 *mapName, gint mapName_length, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
309 g_assert (handle != NULL);
311 return open_handle (handle, mapName, mapName_length, FILE_MODE_OPEN, capacity, access, options, ioerror, error);
314 void
315 mono_mmap_close (void *mmap_handle, MonoError *error)
317 g_assert (mmap_handle);
318 MONO_ENTER_GC_SAFE;
319 CloseHandle (mmap_handle);
320 MONO_EXIT_GC_SAFE;
323 void
324 mono_mmap_configure_inheritability (void *mmap_handle, gint32 inheritability, MonoError *error)
326 g_assert (mmap_handle);
327 if (!SetHandleInformation (mmap_handle, HANDLE_FLAG_INHERIT, inheritability ? HANDLE_FLAG_INHERIT : 0)) {
328 g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ());
332 void
333 mono_mmap_flush (void *mmap_handle, MonoError *error)
335 g_assert (mmap_handle);
336 MmapInstance *h = (MmapInstance *)mmap_handle;
338 gboolean flush_success;
339 MONO_ENTER_GC_SAFE;
340 flush_success = FlushViewOfFile (h->address, h->length);
341 MONO_EXIT_GC_SAFE;
342 if (flush_success)
343 return;
346 // This replicates how CoreFX does MemoryMappedView.Flush ().
348 // It is a known issue within the NTFS transaction log system that
349 // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
350 // As a workaround, we catch this particular error and retry the flush operation
351 // a few milliseconds later. If it does not work, we give it a few more tries with
352 // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
353 // this strategy successfully flushed the view after no more than 3 retries.
355 if (GetLastError () != ERROR_LOCK_VIOLATION)
356 // TODO: Propagate error to caller
357 return;
359 for (int w = 0; w < MAX_FLUSH_WAITS; w++) {
360 int pause = (1 << w); // MaxFlushRetries should never be over 30
361 mono_thread_info_sleep (pause, NULL);
363 for (int r = 0; r < MAX_FLUSH_RETIRES_PER_WAIT; r++) {
364 MONO_ENTER_GC_SAFE;
365 flush_success = FlushViewOfFile (h->address, h->length);
366 MONO_EXIT_GC_SAFE;
367 if (flush_success)
368 return;
370 if (GetLastError () != ERROR_LOCK_VIOLATION)
371 // TODO: Propagate error to caller
372 return;
374 mono_thread_info_yield ();
378 // We got to here, so there was no success:
379 // TODO: Propagate error to caller
383 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address, MonoError *error)
385 static DWORD allocationGranularity = 0;
386 if (allocationGranularity == 0) {
387 SYSTEM_INFO info;
388 GetSystemInfo (&info);
389 allocationGranularity = info.dwAllocationGranularity;
392 gint64 extraMemNeeded = offset % allocationGranularity;
393 guint64 newOffset = offset - extraMemNeeded;
394 gint64 nativeSize = (*size != 0) ? *size + extraMemNeeded : 0;
396 #if SIZEOF_VOID_P == 4
397 if (nativeSize > UINT32_MAX)
398 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
399 #endif
401 void *address;
402 MONO_ENTER_GC_SAFE;
403 address = MapViewOfFile (handle, get_file_map_access (access), (DWORD) (newOffset >> 32), (DWORD) newOffset, (SIZE_T) nativeSize);
404 MONO_EXIT_GC_SAFE;
405 if (!address)
406 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
408 // Query the view for its size and allocation type
409 MEMORY_BASIC_INFORMATION viewInfo;
410 MONO_ENTER_GC_SAFE;
411 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
412 MONO_EXIT_GC_SAFE;
413 guint64 viewSize = (guint64) viewInfo.RegionSize;
415 // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
416 // OR check if the allocated view size is smaller than the expected native size
417 // If multiple overlapping views are created over the file mapping object, the pages in a given region
418 // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
419 // views created on a mapping object backed by same file.
420 // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
421 // but more pages may need to be committed in the region.
422 // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
423 // and size of the region of pages with matching attributes starting from base address.
424 // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
425 if (((viewInfo.State & MEM_RESERVE) != 0) || viewSize < (guint64) nativeSize) {
426 void *tempAddress;
427 MONO_ENTER_GC_SAFE;
428 tempAddress = VirtualAlloc (address, nativeSize != 0 ? nativeSize : viewSize, MEM_COMMIT, get_page_access (access));
429 MONO_EXIT_GC_SAFE;
430 if (!tempAddress) {
431 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
433 // again query the view for its new size
434 MONO_ENTER_GC_SAFE;
435 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
436 MONO_EXIT_GC_SAFE;
437 viewSize = (guint64) viewInfo.RegionSize;
440 if (*size == 0)
441 *size = viewSize - extraMemNeeded;
443 MmapInstance *h = g_malloc0 (sizeof (MmapInstance));
444 h->address = address;
445 h->length = *size + extraMemNeeded;
446 *mmap_handle = h;
447 *base_address = (char*) address + (offset - newOffset);
449 return 0;
452 MonoBoolean
453 mono_mmap_unmap (void *base_address, MonoError *error)
455 g_assert (base_address);
457 MmapInstance *h = (MmapInstance *) base_address;
459 gboolean result;
460 MONO_ENTER_GC_SAFE;
461 result = UnmapViewOfFile (h->address);
462 MONO_EXIT_GC_SAFE;
464 g_free (h);
465 return result;
467 #elif !HAVE_EXTERN_DEFINED_WIN32_FILE_MAPPING
468 void *
469 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)
471 g_unsupported_api ("MapViewOfFile");
472 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "MapViewOfFile");
473 SetLastError (ERROR_NOT_SUPPORTED);
474 return NULL;
477 void *
478 mono_mmap_open_handle (void *handle, const gunichar2 *mapName, gint mapName_length, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
480 g_unsupported_api ("MapViewOfFile");
481 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "MapViewOfFile");
482 SetLastError (ERROR_NOT_SUPPORTED);
483 return NULL;
486 void
487 mono_mmap_close (void *mmap_handle, MonoError *error)
489 g_unsupported_api ("MapViewOfFile");
490 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "MapViewOfFile");
491 SetLastError (ERROR_NOT_SUPPORTED);
492 return;
495 void
496 mono_mmap_configure_inheritability (void *mmap_handle, gint32 inheritability, MonoError *error)
498 g_unsupported_api ("MapViewOfFile");
499 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "MapViewOfFile");
500 SetLastError (ERROR_NOT_SUPPORTED);
501 return;
504 void
505 mono_mmap_flush (void *mmap_handle, MonoError *error)
507 g_unsupported_api ("FlushViewOfFile");
508 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "FlushViewOfFile");
509 SetLastError (ERROR_NOT_SUPPORTED);
510 return;
514 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address, MonoError *error)
516 g_unsupported_api ("MapViewOfFile");
517 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "MapViewOfFile");
518 SetLastError (ERROR_NOT_SUPPORTED);
519 return 0;
522 MonoBoolean
523 mono_mmap_unmap (void *base_address, MonoError *error)
525 g_unsupported_api ("UnmapViewOfFile");
526 mono_error_set_not_supported (error, G_UNSUPPORTED_API, "UnmapViewOfFile");
527 SetLastError (ERROR_NOT_SUPPORTED);
528 return FALSE;
530 #endif /* HAVE_API_SUPPORT_WIN32_FILE_MAPPING */
531 #endif /* HOST_WIN32 */
533 MONO_EMPTY_SOURCE_FILE (file_mmap_windows)