Additional none desktop API families triggered by changes.
[mono.git] / mono / metadata / file-mmap-windows.c
1 /*
2  * file-mmap-windows.c: MemoryMappedFile internal calls for Windows
3  *
4  * Copyright 2016 Microsoft
5  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
6  */
7
8 /*
9  * The code in this file has been inspired by the CoreFX MemoryMappedFile Windows implementation contained in the files
10  *
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
13  */
14
15 #include <config.h>
16 #include <glib.h>
17 #include <mono/utils/mono-compiler.h>
18 #if G_HAVE_API_SUPPORT(HAVE_CLASSIC_WINAPI_SUPPORT) && defined(HOST_WIN32)
19
20 #include <glib.h>
21
22 #include <mono/metadata/file-mmap.h>
23
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
27
28 typedef struct {
29         void *address;
30         size_t length;
31 } MmapInstance;
32
33 enum {
34         BAD_CAPACITY_FOR_FILE_BACKED = 1,
35         CAPACITY_SMALLER_THAN_FILE_SIZE,
36         FILE_NOT_FOUND,
37         FILE_ALREADY_EXISTS,
38         PATH_TOO_LONG,
39         COULD_NOT_OPEN,
40         CAPACITY_MUST_BE_POSITIVE,
41         INVALID_FILE_MODE,
42         COULD_NOT_MAP_MEMORY,
43         ACCESS_DENIED,
44         CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
45 };
46
47 enum {
48         FILE_MODE_CREATE_NEW = 1,
49         FILE_MODE_CREATE = 2,
50         FILE_MODE_OPEN = 3,
51         FILE_MODE_OPEN_OR_CREATE = 4,
52         FILE_MODE_TRUNCATE = 5,
53         FILE_MODE_APPEND = 6,
54 };
55
56 enum {
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,
63 };
64
65 static DWORD get_page_access (int access)
66 {
67         switch (access) {
68         case MMAP_FILE_ACCESS_READ:
69                 return PAGE_READONLY;
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;
78         default:
79                 g_error ("unknown MemoryMappedFileAccess %d", access);
80         }
81 }
82
83 static DWORD get_file_access (int access)
84 {
85         switch (access) {
86         case MMAP_FILE_ACCESS_READ:
87         case MMAP_FILE_ACCESS_READ_EXECUTE:
88                 return GENERIC_READ;
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:
94                 return GENERIC_WRITE;
95         default:
96                 g_error ("unknown MemoryMappedFileAccess %d", access);
97         }
98 }
99
100 static int get_file_map_access (int access)
101 {
102         switch (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;
115         default:
116                 g_error ("unknown MemoryMappedFileAccess %d", access);
117         }
118 }
119
120 static int convert_win32_error (int error, int def)
121 {
122         switch (error) {
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;
130         }
131         return def;
132 }
133
134 static void *open_handle (void *handle, MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
135 {
136         g_assert (handle != NULL);
137
138         wchar_t *w_mapName = NULL;
139         HANDLE result = NULL;
140
141         if (handle == INVALID_HANDLE_VALUE) {
142                 if (*capacity <= 0) {
143                         *error = CAPACITY_MUST_BE_POSITIVE;
144                         return NULL;
145                 }
146 #if SIZEOF_VOID_P == 4
147                 if (*capacity > UINT32_MAX) {
148                         *error = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
149                         return NULL;
150                 }
151 #endif
152                 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
153                         *error = INVALID_FILE_MODE;
154                         return NULL;
155                 }
156         } else {
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);
160                         return NULL;
161                 }
162                 if (*capacity == 0) {
163                         if (info.EndOfFile.QuadPart == 0) {
164                                 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
165                                 return NULL;
166                         }
167                 } else if (*capacity < info.EndOfFile.QuadPart) {
168                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
169                         return NULL;
170                 }
171         }
172
173         w_mapName = mapName ? mono_string_to_utf16 (mapName) : NULL;
174
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);
179                         result = NULL;
180                         *error = FILE_ALREADY_EXISTS;
181                 } else if (!result && GetLastError () != NO_ERROR) {
182                         *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
183                 }
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);
186                 if (!result) {
187                         if (mode == FILE_MODE_OPEN_OR_CREATE && GetLastError () == ERROR_FILE_NOT_FOUND) {
188                                 *error = INVALID_FILE_MODE;
189                         } else {
190                                 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
191                         }
192                 }
193         } else if (mode == FILE_MODE_OPEN_OR_CREATE) {
194
195                 // This replicates how CoreFX does MemoryMappedFile.CreateOrOpen ().
196
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. 
202                 /// 
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.
206
207                 guint32 waitRetries = 14;   //((2^13)-1)*10ms == approximately 1.4mins
208                 guint32 waitSleep = 0;
209
210                 while (waitRetries > 0) {
211                         result = CreateFileMappingW ((HANDLE)handle, NULL, get_page_access (access) | options, (DWORD)(((guint64)*capacity) >> 32), (DWORD)*capacity, w_mapName);
212                         if (result)
213                                 break;
214                         if (GetLastError() != ERROR_ACCESS_DENIED) {
215                                 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
216                                 break;
217                         }
218                         result = OpenFileMappingW (get_file_map_access (access), FALSE, w_mapName);
219                         if (result)
220                                 break;
221                         if (GetLastError () != ERROR_FILE_NOT_FOUND) {
222                                 *error = convert_win32_error (GetLastError (), COULD_NOT_OPEN);
223                                 break;
224                         }
225                         // increase wait time
226                         --waitRetries;
227                         if (waitSleep == 0) {
228                                 waitSleep = 10;
229                         } else {
230                                 mono_thread_info_sleep (waitSleep, NULL);
231                                 waitSleep *= 2;
232                         }
233                 }
234
235                 if (!result) {
236                         *error = COULD_NOT_OPEN;
237                 }
238         }
239
240         if (w_mapName)
241                 g_free (w_mapName);
242         return result;
243 }
244
245 void *mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
246 {
247         g_assert (path != NULL || mapName != NULL);
248
249         wchar_t *w_path = NULL;
250         HANDLE hFile = INVALID_HANDLE_VALUE;
251         HANDLE result = NULL;
252         gboolean delete_on_error = FALSE;
253
254         if (path) {
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;
260                         goto done;
261                 }
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);
265                         goto done;
266                 }
267                 delete_on_error = !existed;
268         }
269
270         result = open_handle (hFile, mapName, mode, capacity, access, options, error);
271
272 done:
273         if (!result && delete_on_error)
274                 DeleteFileW (w_path);
275         if (w_path)
276                 g_free (w_path);
277
278         return result;
279 }
280
281 void *mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
282 {
283         g_assert (handle != NULL);
284
285         return open_handle (handle, mapName, FILE_MODE_OPEN, capacity, access, options, error);
286 }
287
288 void mono_mmap_close (void *mmap_handle)
289 {
290         g_assert (mmap_handle);
291         CloseHandle ((HANDLE) mmap_handle);
292 }
293
294 void mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
295 {
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 ());
299         }
300 }
301
302 void mono_mmap_flush (void *mmap_handle)
303 {
304         g_assert (mmap_handle);
305         MmapInstance *h = (MmapInstance *)mmap_handle;
306
307         if (FlushViewOfFile (h->address, h->length))
308                 return;
309
310         // This replicates how CoreFX does MemoryMappedView.Flush ().
311
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.
318
319         if (GetLastError () != ERROR_LOCK_VIOLATION)
320                 // TODO: Propagate error to caller
321                 return;
322
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);
326
327                 for (int r = 0; r < MAX_FLUSH_RETIRES_PER_WAIT; r++) {
328                         if (FlushViewOfFile (h->address, h->length))
329                                 return;
330
331                         if (GetLastError () != ERROR_LOCK_VIOLATION)
332                                 // TODO: Propagate error to caller
333                                 return;
334
335                         mono_thread_info_yield ();
336                 }
337         }
338
339         // We got to here, so there was no success:
340         // TODO: Propagate error to caller
341 }
342
343 int mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
344 {
345         static DWORD allocationGranularity = 0;
346         if (allocationGranularity == 0) {
347                 SYSTEM_INFO info;
348                 GetSystemInfo (&info);
349                 allocationGranularity = info.dwAllocationGranularity;
350         }
351
352         gint64 extraMemNeeded = offset % allocationGranularity;
353         guint64 newOffset = offset - extraMemNeeded;
354         gint64 nativeSize = (*size != 0) ? *size + extraMemNeeded : 0;
355
356 #if SIZEOF_VOID_P == 4
357         if (nativeSize > UINT32_MAX)
358                 return CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
359 #endif
360         
361         void *address = MapViewOfFile ((HANDLE) handle, get_file_map_access (access), (DWORD) (newOffset >> 32), (DWORD) newOffset, (SIZE_T) nativeSize);
362         if (!address)
363                 return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
364
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;
369
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));
382                 if (!tempAddress) {
383                         return convert_win32_error (GetLastError (), COULD_NOT_MAP_MEMORY);
384                 }
385                 // again query the view for its new size
386                 VirtualQuery (address, &viewInfo, sizeof (MEMORY_BASIC_INFORMATION));
387                 viewSize = (guint64) viewInfo.RegionSize;
388         }
389
390         if (*size == 0)
391                 *size = viewSize - extraMemNeeded;
392
393         MmapInstance *h = g_malloc0 (sizeof (MmapInstance));
394         h->address = address;
395         h->length = *size + extraMemNeeded;
396         *mmap_handle = h;
397         *base_address = (char*) address + (offset - newOffset);
398
399         return 0;
400 }
401
402 gboolean mono_mmap_unmap (void *mmap_handle)
403 {
404         g_assert (mmap_handle);
405
406         MmapInstance *h = (MmapInstance *) mmap_handle;
407
408         gboolean result = UnmapViewOfFile (h->address);
409
410         g_free (h);
411         return result;
412 }
413
414 #else
415
416 MONO_EMPTY_SOURCE_FILE (file_mmap_windows);
417
418 #endif