Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mono / metadata / file-mmap-windows.c
1 /**
2  * \file
3  * MemoryMappedFile internal calls for Windows
4  *
5  * Copyright 2016 Microsoft
6  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
7  */
8
9 /*
10  * The code in this file has been inspired by the CoreFX MemoryMappedFile Windows implementation contained in the files
11  *
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
14  */
15
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)
20
21 #include <glib.h>
22
23 #include <mono/metadata/file-mmap.h>
24
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
28
29 typedef struct {
30         void *address;
31         size_t length;
32 } MmapInstance;
33
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
46 };
47
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,
55 };
56
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,
64 };
65
66 static DWORD get_page_access (int access)
67 {
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);
81         }
82 }
83
84 static DWORD get_file_access (int access)
85 {
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);
98         }
99 }
100
101 static int get_file_map_access (int access)
102 {
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);
118         }
119 }
120
121 static int convert_win32_error (int error, int def)
122 {
123         switch (error) {
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;
131         }
132         return def;
133 }
134
135 static void *open_handle (void *handle, MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
136 {
137         g_assert (handle != NULL);
138
139         wchar_t *w_mapName = NULL;
140         HANDLE result = NULL;
141
142         if (handle == INVALID_HANDLE_VALUE) {
143                 if (*capacity <= 0 && mode != FILE_MODE_OPEN) {
144                         *error = CAPACITY_MUST_BE_POSITIVE;
145                         return NULL;
146                 }
147 #if SIZEOF_VOID_P == 4
148                 if (*capacity > UINT32_MAX) {
149                         *error = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
150                         return NULL;
151                 }
152 #endif
153                 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
154                         *error = INVALID_FILE_MODE;
155                         return NULL;
156                 }
157         } else {
158                 FILE_STANDARD_INFO info;
159                 if (!GetFileInformationByHandleEx ((HANDLE) handle, FileStandardInfo, &info, sizeof (FILE_STANDARD_INFO))) {
160                         *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
161                         return NULL;
162                 }
163                 if (*capacity == 0) {
164                         if (info.EndOfFile.QuadPart == 0) {
165                                 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
166                                 return NULL;
167                         }
168                 } else if (*capacity < info.EndOfFile.QuadPart) {
169                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
170                         return NULL;
171                 }
172         }
173
174         w_mapName = mapName ? mono_string_to_utf16 (mapName) : NULL;
175
176         if (mode == FILE_MODE_CREATE_NEW || handle != INVALID_HANDLE_VALUE) {
177                 result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName);
178                 if (result && GetLastError () == ERROR_ALREADY_EXISTS) {
179                         CloseHandle (result);
180                         result = NULL;
181                         *error = FILE_ALREADY_EXISTS;
182                 } else if (!result && GetLastError () != NO_ERROR) {
183                         *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
184                 }
185         } else if (mode == FILE_MODE_OPEN || mode == FILE_MODE_OPEN_OR_CREATE && access == MMAP_FILE_ACCESS_WRITE) {
186                 result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName);
187                 if (!result) {
188                         if (mode == FILE_MODE_OPEN_OR_CREATE && GetLastError () == ERROR_FILE_NOT_FOUND) {
189                                 *error = INVALID_FILE_MODE;
190                         } else {
191                                 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
192                         }
193                 }
194         } else if (mode == FILE_MODE_OPEN_OR_CREATE) {
195
196                 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
197
198                 /// Try to open the file if it exists -- this requires a bit more work. Loop until we can
199                 /// either create or open a memory mapped file up to a timeout. CreateFileMapping may fail
200                 /// if the file exists and we have non-null security attributes, in which case we need to
201                 /// use OpenFileMapping.  But, there exists a race condition because the memory mapped file
202                 /// may have closed between the two calls -- hence the loop. 
203                 /// 
204                 /// The retry/timeout logic increases the wait time each pass through the loop and times 
205                 /// out in approximately 1.4 minutes. If after retrying, a MMF handle still hasn't been opened, 
206                 /// throw an InvalidOperationException.
207
208                 guint32 waitRetries = 14;   //((2^13)-1)*10ms == approximately 1.4mins
209                 guint32 waitSleep = 0;
210
211                 while (waitRetries > 0) {
212                         result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName);
213                         if (result)
214                                 break;
215                         if (GetLastError() != ERROR_ACCESS_DENIED) {
216                                 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
217                                 break;
218                         }
219                         result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName);
220                         if (result)
221                                 break;
222                         if (GetLastError () != ERROR_FILE_NOT_FOUND) {
223                                 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
224                                 break;
225                         }
226                         // increase wait time
227                         --waitRetries;
228                         if (waitSleep == 0) {
229                                 waitSleep = 10;
230                         } else {
231                                 mono_thread_info_sleep (waitSleep, NULL);
232                                 waitSleep *= 2;
233                         }
234                 }
235
236                 if (!result) {
237                         *error = COULD_NOT_OPEN;
238                 }
239         }
240
241         if (w_mapName)
242                 g_free (w_mapName);
243         return result;
244 }
245
246 void *mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
247 {
248         g_assert (path != NULL || mapName != NULL);
249
250         wchar_t *w_path = NULL;
251         HANDLE hFile = INVALID_HANDLE_VALUE;
252         HANDLE result = NULL;
253         gboolean delete_on_error = FALSE;
254
255         if (path) {
256                 w_path = mono_string_to_utf16 (path);
257                 WIN32_FILE_ATTRIBUTE_DATA file_attrs;
258                 gboolean existed = GetFileAttributesExW (w_path, GetFileExInfoStandard, &file_attrs);
259                 if (!existed && mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
260                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
261                         goto done;
262                 }
263                 hFile = CreateFileW (w_path, get_file_access (access), FILE_SHARE_READ, NULL, mode, FILE_ATTRIBUTE_NORMAL, NULL);
264                 if (hFile == INVALID_HANDLE_VALUE) {
265                         *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
266                         goto done;
267                 }
268                 delete_on_error = !existed;
269         }
270
271         result = open_handle (hFile, mapName, mode, capacity, access, options, error);
272
273 done:
274         if (hFile != INVALID_HANDLE_VALUE)
275                 CloseHandle (hFile);
276         if (!result && delete_on_error)
277                 DeleteFileW (w_path);
278         if (w_path)
279                 g_free (w_path);
280
281         return result;
282 }
283
284 void *mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
285 {
286         g_assert (handle != NULL);
287
288         return open_handle (handle, mapName, FILE_MODE_OPEN, capacity, access, options, error);
289 }
290
291 void mono_mmap_close (void *mmap_handle)
292 {
293         g_assert (mmap_handle);
294         CloseHandle ((HANDLE) mmap_handle);
295 }
296
297 void mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
298 {
299         g_assert (mmap_handle);
300         if (!SetHandleInformation ((HANDLE) mmap_handle, HANDLE_FLAG_INHERIT, inheritability ? HANDLE_FLAG_INHERIT : 0)) {
301                 g_error ("mono_mmap_configure_inheritability: SetHandleInformation failed with error %d!", GetLastError ());
302         }
303 }
304
305 void mono_mmap_flush (void *mmap_handle)
306 {
307         g_assert (mmap_handle);
308         MmapInstance *h = (MmapInstance *)mmap_handle;
309
310         if (FlushViewOfFile (h->address, h->length))
311                 return;
312
313         // This replicates how CoreFX does MemoryMappedView.Flush ().
314
315         // It is a known issue within the NTFS transaction log system that
316         // causes FlushViewOfFile to intermittently fail with ERROR_LOCK_VIOLATION
317         // As a workaround, we catch this particular error and retry the flush operation 
318         // a few milliseconds later. If it does not work, we give it a few more tries with
319         // increasing intervals. Eventually, however, we need to give up. In ad-hoc tests
320         // this strategy successfully flushed the view after no more than 3 retries.
321
322         if (GetLastError () != ERROR_LOCK_VIOLATION)
323                 // TODO: Propagate error to caller
324                 return;
325
326         for (int w = 0; w < MAX_FLUSH_WAITS; w++) {
327                 int pause = (1 << w);  // MaxFlushRetries should never be over 30
328                 mono_thread_info_sleep (pause, NULL);
329
330                 for (int r = 0; r < MAX_FLUSH_RETIRES_PER_WAIT; r++) {
331                         if (FlushViewOfFile (h->address, h->length))
332                                 return;
333
334                         if (GetLastError () != ERROR_LOCK_VIOLATION)
335                                 // TODO: Propagate error to caller
336                                 return;
337
338                         mono_thread_info_yield ();
339                 }
340         }
341
342         // We got to here, so there was no success:
343         // TODO: Propagate error to caller
344 }
345
346 int mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
347 {
348         static DWORD allocationGranularity = 0;
349         if (allocationGranularity == 0) {
350                 SYSTEM_INFO info;
351                 GetSystemInfo (&info);
352                 allocationGranularity = info.dwAllocationGranularity;
353         }
354
355         gint64 extraMemNeeded = offset % allocationGranularity;
356         guint64 newOffset = offset - extraMemNeeded;
357         gint64 nativeSize = (*size != 0) ? *size + extraMemNeeded : 0;
358
359 #if SIZEOF_VOID_P == 4
360         if (nativeSize > UINT32_MAX)
361                 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
362 #endif
363         
364         void *address = MapViewOfFile ((HANDLE) handle, get_file_map_access (access), (DWORD) (newOffset >> 32), (DWORD) newOffset, (SIZE_T) nativeSize);
365         if (!address)
366                 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
367
368         // Query the view for its size and allocation type
369         MEMORY_BASIC_INFORMATION viewInfo;
370         VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
371         guint64 viewSize = (guint64) viewInfo.RegionSize;
372
373         // Allocate the pages if we were using the MemoryMappedFileOptions.DelayAllocatePages option
374         // OR check if the allocated view size is smaller than the expected native size
375         // If multiple overlapping views are created over the file mapping object, the pages in a given region
376         // could have different attributes(MEM_RESERVE OR MEM_COMMIT) as MapViewOfFile preserves coherence between 
377         // views created on a mapping object backed by same file.
378         // In which case, the viewSize will be smaller than nativeSize required and viewState could be MEM_COMMIT 
379         // but more pages may need to be committed in the region.
380         // This is because, VirtualQuery function(that internally invokes VirtualQueryEx function) returns the attributes 
381         // and size of the region of pages with matching attributes starting from base address.
382         // VirtualQueryEx: http://msdn.microsoft.com/en-us/library/windows/desktop/aa366907(v=vs.85).aspx
383         if (((viewInfo.State & MEM_RESERVE) != 0) || viewSize < (guint64) nativeSize) {
384                 void *tempAddress = VirtualAlloc (address, nativeSize != 0 ? nativeSize : viewSize, MEM_COMMIT, get_page_access (access));
385                 if (!tempAddress) {
386                         return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
387                 }
388                 // again query the view for its new size
389                 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
390                 viewSize = (guint64) viewInfo.RegionSize;
391         }
392
393         if (*size == 0)
394                 *size = viewSize - extraMemNeeded;
395
396         MmapInstance *h = g_malloc0 (sizeof (MmapInstance));
397         h->address = address;
398         h->length = *size + extraMemNeeded;
399         *mmap_handle = h;
400         *base_address = (char*) address + (offset - newOffset);
401
402         return 0;
403 }
404
405 gboolean mono_mmap_unmap (void *mmap_handle)
406 {
407         g_assert (mmap_handle);
408
409         MmapInstance *h = (MmapInstance *) mmap_handle;
410
411         gboolean result = UnmapViewOfFile (h->address);
412
413         g_free (h);
414         return result;
415 }
416
417 #else
418
419 MONO_EMPTY_SOURCE_FILE (file_mmap_windows);
420
421 #endif