2 * file-mmap-posix.c: File mmap internal calls
7 * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
8 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
21 #ifdef HAVE_SYS_STAT_H
24 #ifdef HAVE_SYS_TYPES_H
25 #include <sys/types.h>
34 #include <mono/metadata/object.h>
35 #include <mono/metadata/w32file.h>
36 #include <mono/metadata/file-mmap.h>
37 #include <mono/utils/atomic.h>
38 #include <mono/utils/mono-memory-model.h>
39 #include <mono/utils/mono-mmap.h>
40 #include <mono/utils/mono-coop-mutex.h>
41 #include <mono/utils/mono-threads.h>
58 BAD_CAPACITY_FOR_FILE_BACKED = 1,
59 CAPACITY_SMALLER_THAN_FILE_SIZE,
64 CAPACITY_MUST_BE_POSITIVE,
68 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
72 FILE_MODE_CREATE_NEW = 1,
75 FILE_MODE_OPEN_OR_CREATE = 4,
76 FILE_MODE_TRUNCATE = 5,
81 MMAP_FILE_ACCESS_READ_WRITE = 0,
82 MMAP_FILE_ACCESS_READ = 1,
83 MMAP_FILE_ACCESS_WRITE = 2,
84 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
85 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
86 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
90 #define DEFAULT_FILEMODE DEFFILEMODE
92 #define DEFAULT_FILEMODE 0666
95 static int mmap_init_state;
96 static MonoCoopMutex named_regions_mutex;
97 static GHashTable *named_regions;
101 align_up_to_page_size (gint64 size)
103 gint64 page_size = mono_pagesize ();
104 return (size + page_size - 1) & ~(page_size - 1);
108 align_down_to_page_size (gint64 size)
110 gint64 page_size = mono_pagesize ();
111 return size & ~(page_size - 1);
115 file_mmap_init (void)
118 switch (mmap_init_state) {
120 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
122 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
123 mono_coop_mutex_init (&named_regions_mutex);
125 mono_atomic_store_release (&mmap_init_state, 2);
130 mono_thread_info_sleep (1, NULL); /* Been init'd by other threads, this is very rare. */
131 } while (mmap_init_state != 2);
136 g_error ("Invalid init state %d", mmap_init_state);
141 named_regions_lock (void)
144 mono_coop_mutex_lock (&named_regions_mutex);
148 named_regions_unlock (void)
150 mono_coop_mutex_unlock (&named_regions_mutex);
155 file_mode_to_unix (int mode)
158 case FILE_MODE_CREATE_NEW:
159 return O_CREAT | O_EXCL;
160 case FILE_MODE_CREATE:
161 return O_CREAT | O_TRUNC;
164 case FILE_MODE_OPEN_OR_CREATE:
166 case FILE_MODE_TRUNCATE:
168 case FILE_MODE_APPEND:
171 g_error ("unknown FileMode %d", mode);
176 access_mode_to_unix (int access)
179 case MMAP_FILE_ACCESS_READ_WRITE:
180 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
181 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
183 case MMAP_FILE_ACCESS_READ:
184 case MMAP_FILE_ACCESS_READ_EXECUTE:
186 case MMAP_FILE_ACCESS_WRITE:
189 g_error ("unknown MemoryMappedFileAccess %d", access);
194 acess_to_mmap_flags (int access)
197 case MMAP_FILE_ACCESS_READ_WRITE:
198 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
200 case MMAP_FILE_ACCESS_WRITE:
201 return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
203 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
204 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
206 case MMAP_FILE_ACCESS_READ_EXECUTE:
207 return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
209 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
210 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
212 case MMAP_FILE_ACCESS_READ:
213 return MONO_MMAP_READ | MONO_MMAP_SHARED;
215 g_error ("unknown MemoryMappedFileAccess %d", access);
220 This allow us to special case zero size files that can be arbitrarily mapped.
223 is_special_zero_size_file (struct stat *buf)
225 return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0;
229 XXX implement options
232 open_file_map (const char *c_path, int input_fd, int mode, gint64 *capacity, int access, int options, int *ioerror)
235 MmapHandle *handle = NULL;
239 result = stat (c_path, &buf);
241 result = fstat (input_fd, &buf);
243 if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
244 if (result == -1) { //XXX translate errno?
245 *ioerror = FILE_NOT_FOUND;
250 if (mode == FILE_MODE_CREATE_NEW && result == 0) {
251 *ioerror = FILE_ALREADY_EXISTS;
256 if (*capacity == 0) {
258 * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
259 * also makes little sense, so don't do the check if th file is one of these.
261 if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) {
262 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
265 *capacity = buf.st_size;
266 } else if (*capacity < buf.st_size) {
267 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
271 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
272 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
277 if (c_path) //FIXME use io portability?
278 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFAULT_FILEMODE);
282 if (fd == -1) { //XXX translate errno?
283 *ioerror = COULD_NOT_OPEN;
287 if (result != 0 || *capacity > buf.st_size) {
288 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
291 handle = g_new0 (MmapHandle, 1);
292 handle->ref_count = 1;
293 handle->capacity = *capacity;
297 return (void*)handle;
300 #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
302 open_memory_map (const char *c_mapName, int mode, gint64 *capacity, int access, int options, int *ioerror)
305 if (*capacity <= 0) {
306 *ioerror = CAPACITY_MUST_BE_POSITIVE;
309 #if SIZEOF_VOID_P == 4
310 if (*capacity > UINT32_MAX) {
311 *ioerror = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
316 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
317 *ioerror = INVALID_FILE_MODE;
321 named_regions_lock ();
322 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
324 if (mode == FILE_MODE_CREATE_NEW) {
325 *ioerror = FILE_ALREADY_EXISTS;
330 //XXX should we ftruncate if the file is smaller than capacity?
335 int unused G_GNUC_UNUSED, alloc_size;
337 if (mode == FILE_MODE_OPEN) {
338 *ioerror = FILE_NOT_FOUND;
341 *capacity = align_up_to_page_size (*capacity);
343 tmp_dir = g_get_tmp_dir ();
344 alloc_size = strlen (tmp_dir) + strlen (MONO_ANON_FILE_TEMPLATE) + 1;
345 if (alloc_size > 1024) {//rather fail that stack overflow
346 *ioerror = COULD_NOT_MAP_MEMORY;
349 file_name = (char *)alloca (alloc_size);
350 strcpy (file_name, tmp_dir);
351 strcat (file_name, MONO_ANON_FILE_TEMPLATE);
353 fd = mkstemp (file_name);
355 *ioerror = COULD_NOT_MAP_MEMORY;
360 unused = ftruncate (fd, (off_t)*capacity);
362 handle = g_new0 (MmapHandle, 1);
363 handle->ref_count = 1;
364 handle->capacity = *capacity;
366 handle->name = g_strdup (c_mapName);
368 g_hash_table_insert (named_regions, handle->name, handle);
373 named_regions_unlock ();
379 /* This is an icall */
381 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
384 MmapHandle *handle = NULL;
385 g_assert (path || mapName);
388 char * c_path = mono_string_to_utf8_checked (path, &error);
389 if (mono_error_set_pending_exception (&error))
391 handle = open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
396 char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
397 if (mono_error_set_pending_exception (&error))
401 named_regions_lock ();
402 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
404 *ioerror = FILE_ALREADY_EXISTS;
407 char *c_path = mono_string_to_utf8_checked (path, &error);
408 if (is_ok (&error)) {
409 handle = (MmapHandle *)open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
411 handle->name = g_strdup (c_mapName);
412 g_hash_table_insert (named_regions, handle->name, handle);
419 named_regions_unlock ();
421 handle = open_memory_map (c_mapName, mode, capacity, access, options, ioerror);
427 /* this is an icall */
429 mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
434 handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror);
436 char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
437 if (mono_error_set_pending_exception (&error))
440 named_regions_lock ();
441 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
443 *ioerror = FILE_ALREADY_EXISTS;
446 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
447 handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror);
448 handle->name = g_strdup (c_mapName);
449 g_hash_table_insert (named_regions, handle->name, handle);
451 named_regions_unlock ();
459 mono_mmap_close (void *mmap_handle)
461 MmapHandle *handle = (MmapHandle *)mmap_handle;
463 named_regions_lock ();
465 if (handle->ref_count == 0) {
467 g_hash_table_remove (named_regions, handle->name);
469 g_free (handle->name);
473 named_regions_unlock ();
477 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
479 MmapHandle *h = (MmapHandle *)mmap_handle;
483 flags = fcntl (fd, F_GETFD, 0);
485 flags &= ~FD_CLOEXEC;
488 fcntl (fd, F_SETFD, flags);
492 mono_mmap_flush (void *mmap_handle)
494 MmapInstance *h = (MmapInstance *)mmap_handle;
497 msync (h->address, h->length, MS_SYNC);
501 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
503 gint64 mmap_offset = 0;
504 MmapHandle *fh = (MmapHandle *)handle;
505 MmapInstance res = { 0 };
506 size_t eff_size = *size;
507 struct stat buf = { 0 };
508 fstat (fh->fd, &buf); //FIXME error handling
511 *base_address = NULL;
513 if (offset > buf.st_size || ((eff_size + offset) > buf.st_size && !is_special_zero_size_file (&buf)))
514 return ACCESS_DENIED;
516 * We use the file size if one of the following conditions is true:
517 * -input size is zero
518 * -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
521 eff_size = align_up_to_page_size (buf.st_size) - offset;
524 mmap_offset = align_down_to_page_size (offset);
525 eff_size += (offset - mmap_offset);
526 //FIXME translate some interesting errno values
527 res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
528 res.length = eff_size;
531 *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
532 *base_address = (char*)res.address + (offset - mmap_offset);
536 return COULD_NOT_MAP_MEMORY;
540 mono_mmap_unmap (void *mmap_handle)
543 MmapInstance *h = (MmapInstance *)mmap_handle;
545 res = mono_file_unmap (h->address, h->free_handle);