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 
get_page_access(int access)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 
get_file_access(int access)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 
get_file_map_access(int access)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 
convert_win32_error(int error,int def)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 
open_handle(void * handle,MonoString * mapName,int mode,gint64 * capacity,int access,int options,int * error)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 
mono_mmap_open_file(MonoString * path,int mode,MonoString * mapName,gint64 * capacity,int access,int options,int * error)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 
mono_mmap_open_handle(void * handle,MonoString * mapName,gint64 * capacity,int access,int options,int * error)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 
mono_mmap_close(void * mmap_handle)291 void mono_mmap_close (void *mmap_handle)
292 {
293 	g_assert (mmap_handle);
294 	CloseHandle ((HANDLE) mmap_handle);
295 }
296 
mono_mmap_configure_inheritability(void * mmap_handle,gboolean inheritability)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 
mono_mmap_flush(void * mmap_handle)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 
mono_mmap_map(void * handle,gint64 offset,gint64 * size,int access,void ** mmap_handle,void ** base_address)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 
mono_mmap_unmap(void * mmap_handle)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
422