2 * file-mmap-windows.c: MemoryMappedFile internal calls for Windows
4 * Copyright 2016 Microsoft
5 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9 * The code in this file has been inspired by the CoreFX MemoryMappedFile Windows implementation contained in the files
11 * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.Windows.cs
12 * https://github.com/dotnet/corefx/blob/master/src/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedView.Windows.cs
17 #include <mono/utils/mono-compiler.h>
18 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) && defined(HOST_WIN32)
22 #include <mono/metadata/file-mmap.h>
24 // These control the retry behaviour when lock violation errors occur during Flush:
25 #define MAX_FLUSH_WAITS 15 // must be <=30
26 #define MAX_FLUSH_RETIRES_PER_WAIT 20
34 BAD_CAPACITY_FOR_FILE_BACKED = 1,
35 CAPACITY_SMALLER_THAN_FILE_SIZE,
40 CAPACITY_MUST_BE_POSITIVE,
44 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
48 FILE_MODE_CREATE_NEW = 1,
51 FILE_MODE_OPEN_OR_CREATE = 4,
52 FILE_MODE_TRUNCATE = 5,
57 MMAP_FILE_ACCESS_READ_WRITE = 0,
58 MMAP_FILE_ACCESS_READ = 1,
59 MMAP_FILE_ACCESS_WRITE = 2,
60 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
61 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
62 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
65 static DWORD get_page_access (int access)
68 case MMAP_FILE_ACCESS_READ:
70 case MMAP_FILE_ACCESS_READ_WRITE:
71 return PAGE_READWRITE;
72 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
73 return PAGE_WRITECOPY;
74 case MMAP_FILE_ACCESS_READ_EXECUTE:
75 return PAGE_EXECUTE_READ;
76 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
77 return PAGE_EXECUTE_READWRITE;
79 g_error ("unknown MemoryMappedFileAccess %d", access);
83 static DWORD get_file_access (int access)
86 case MMAP_FILE_ACCESS_READ:
87 case MMAP_FILE_ACCESS_READ_EXECUTE:
89 case MMAP_FILE_ACCESS_READ_WRITE:
90 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
91 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
92 return GENERIC_READ | GENERIC_WRITE;
93 case MMAP_FILE_ACCESS_WRITE:
96 g_error ("unknown MemoryMappedFileAccess %d", access);
100 static int get_file_map_access (int access)
103 case MMAP_FILE_ACCESS_READ:
104 return FILE_MAP_READ;
105 case MMAP_FILE_ACCESS_WRITE:
106 return FILE_MAP_WRITE;
107 case MMAP_FILE_ACCESS_READ_WRITE:
108 return FILE_MAP_READ | FILE_MAP_WRITE;
109 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
110 return FILE_MAP_COPY;
111 case MMAP_FILE_ACCESS_READ_EXECUTE:
112 return FILE_MAP_EXECUTE | FILE_MAP_READ;
113 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
114 return FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE;
116 g_error ("unknown MemoryMappedFileAccess %d", access);
120 static int convert_win32_error (int error, int def)
123 case ERROR_FILE_NOT_FOUND:
124 return FILE_NOT_FOUND;
125 case ERROR_FILE_EXISTS:
126 case ERROR_ALREADY_EXISTS:
127 return FILE_ALREADY_EXISTS;
128 case ERROR_ACCESS_DENIED:
129 return ACCESS_DENIED;
134 static void *open_handle (void *handle, MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
136 g_assert (handle != NULL);
138 wchar_t *w_mapName = NULL;
139 HANDLE result = NULL;
141 if (handle == INVALID_HANDLE_VALUE) {
142 if (*capacity <= 0) {
143 *error = CAPACITY_MUST_BE_POSITIVE;
146 #if SIZEOF_VOID_P == 4
147 if (*capacity > UINT32_MAX) {
148 *error = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
152 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
153 *error = INVALID_FILE_MODE;
157 FILE_STANDARD_INFO info;
158 if (!GetFileInformationByHandleEx ((HANDLE) handle, FileStandardInfo, &info, sizeof (FILE_STANDARD_INFO))) {
159 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
162 if (*capacity == 0) {
163 if (info.EndOfFile.QuadPart == 0) {
164 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
167 } else if (*capacity < info.EndOfFile.QuadPart) {
168 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
173 w_mapName = mapName ? mono_string_to_utf16 (mapName) : NULL;
175 if (mode == FILE_MODE_CREATE_NEW || handle != INVALID_HANDLE_VALUE) {
176 result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName);
177 if (result && GetLastError () == ERROR_ALREADY_EXISTS) {
178 CloseHandle (result);
180 *error = FILE_ALREADY_EXISTS;
181 } else if (!result && GetLastError () != NO_ERROR) {
182 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
184 } else if (mode == FILE_MODE_OPEN || mode == FILE_MODE_OPEN_OR_CREATE && access == MMAP_FILE_ACCESS_WRITE) {
185 result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName);
187 if (mode == FILE_MODE_OPEN_OR_CREATE && GetLastError () == ERROR_FILE_NOT_FOUND) {
188 *error = INVALID_FILE_MODE;
190 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
193 } else if (mode == FILE_MODE_OPEN_OR_CREATE) {
195 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
197 /// Try to open the file if it exists -- this requires a bit more work. Loop until we can
198 /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail
199 /// if the file exists and we have non-null security attributes, in which case we need to
200 /// use OpenFileMapping. But, there exists a race condition because the memory mapped file
201 /// may have closed between the two calls -- hence the loop.
203 /// The retry/timeout logic increases the wait time each pass through the loop and times
204 /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened,
205 /// throw an InvalidOperationException.
207 guint32 waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins
208 guint32 waitSleep = 0;
210 while (waitRetries > 0) {
211 result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName);
214 if (GetLastError() != ERROR_ACCESS_DENIED) {
215 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
218 result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName);
221 if (GetLastError () != ERROR_FILE_NOT_FOUND) {
222 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
225 // increase wait time
227 if (waitSleep == 0) {
230 mono_thread_info_sleep (waitSleep, NULL);
236 *error = COULD_NOT_OPEN;
245 void *mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
247 g_assert (path != NULL || mapName != NULL);
249 wchar_t *w_path = NULL;
250 HANDLE hFile = INVALID_HANDLE_VALUE;
251 HANDLE result = NULL;
252 gboolean delete_on_error = FALSE;
255 w_path = mono_string_to_utf16 (path);
256 WIN32_FILE_ATTRIBUTE_DATA file_attrs;
257 gboolean existed = GetFileAttributesExW (w_path, GetFileExInfoStandard, &file_attrs);
258 if (!existed && mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
259 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
262 hFile = CreateFileW (w_path, get_file_access (access), FILE_SHARE_READ, NULL, mode, FILE_ATTRIBUTE_NORMAL, NULL);
263 if (hFile == INVALID_HANDLE_VALUE) {
264 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
267 delete_on_error = !existed;
270 result = open_handle (hFile, mapName, mode, capacity, access, options, error);
273 if (!result && delete_on_error)
274 DeleteFileW (w_path);
281 void *mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
283 g_assert (handle != NULL);
285 return open_handle (handle, mapName, FILE_MODE_OPEN, capacity, access, options, error);
288 void mono_mmap_close (void *mmap_handle)
290 g_assert (mmap_handle);
291 CloseHandle ((HANDLE) mmap_handle);
294 void mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
296 g_assert (mmap_handle);
297 if (!SetHandleInformation ((HANDLE) mmap_handle, HANDLE_FLAG_INHERIT, inheritability ? HANDLE_FLAG_INHERIT : 0)) {
298 g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ());
302 void mono_mmap_flush (void *mmap_handle)
304 g_assert (mmap_handle);
305 MmapInstance *h = (MmapInstance *)mmap_handle;
307 if (FlushViewOfFile (h->address, h->length))
310 // This replicates how CoreFX does MemoryMappedView.Flush ().
312 // It is a known issue within the NTFS transaction log system that
313 // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
314 // As a workaround, we catch this particular error and retry the flush operation
315 // a few milliseconds later. If it does not work, we give it a few more tries with
316 // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
317 // this strategy successfully flushed the view after no more than 3 retries.
319 if (GetLastError () != ERROR_LOCK_VIOLATION)
320 // TODO: Propagate error to caller
323 for (int w = 0; w < MAX_FLUSH_WAITS; w++) {
324 int pause = (1 << w); // MaxFlushRetries should never be over 30
325 mono_thread_info_sleep (pause, NULL);
327 for (int r = 0; r < MAX_FLUSH_RETIRES_PER_WAIT; r++) {
328 if (FlushViewOfFile (h->address, h->length))
331 if (GetLastError () != ERROR_LOCK_VIOLATION)
332 // TODO: Propagate error to caller
335 mono_thread_info_yield ();
339 // We got to here, so there was no success:
340 // TODO: Propagate error to caller
343 int mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
345 static DWORD allocationGranularity = 0;
346 if (allocationGranularity == 0) {
348 GetSystemInfo (&info);
349 allocationGranularity = info.dwAllocationGranularity;
352 gint64 extraMemNeeded = offset % allocationGranularity;
353 guint64 newOffset = offset - extraMemNeeded;
354 gint64 nativeSize = (*size != 0) ? *size + extraMemNeeded : 0;
356 #if SIZEOF_VOID_P == 4
357 if (nativeSize > UINT32_MAX)
358 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
361 void *address = MapViewOfFile ((HANDLE) handle, get_file_map_access (access), (DWORD) (newOffset >> 32), (DWORD) newOffset, (SIZE_T) nativeSize);
363 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
365 // Query the view for its size and allocation type
366 MEMORY_BASIC_INFORMATION viewInfo;
367 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
368 guint64 viewSize = (guint64) viewInfo.RegionSize;
370 // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
371 // OR check if the allocated view size is smaller than the expected native size
372 // If multiple overlapping views are created over the file mapping object, the pages in a given region
373 // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between
374 // views created on a mapping object backed by same file.
375 // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT
376 // but more pages may need to be committed in the region.
377 // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes
378 // and size of the region of pages with matching attributes starting from base address.
379 // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
380 if (((viewInfo.State & MEM_RESERVE) != 0) || viewSize < (guint64) nativeSize) {
381 void *tempAddress = VirtualAlloc (address, nativeSize != 0 ? nativeSize : viewSize, MEM_COMMIT, get_page_access (access));
383 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
385 // again query the view for its new size
386 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
387 viewSize = (guint64) viewInfo.RegionSize;
391 *size = viewSize - extraMemNeeded;
393 MmapInstance *h = g_malloc0 (sizeof (MmapInstance));
394 h->address = address;
395 h->length = *size + extraMemNeeded;
397 *base_address = (char*) address + (offset - newOffset);
402 gboolean mono_mmap_unmap (void *mmap_handle)
404 g_assert (mmap_handle);
406 MmapInstance *h = (MmapInstance *) mmap_handle;
408 gboolean result = UnmapViewOfFile (h->address);
416 MONO_EMPTY_SOURCE_FILE (file_mmap_windows);