2 * file-mmap-posix.c: File mmap internal calls
7 * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
20 #ifdef HAVE_SYS_STAT_H
23 #ifdef HAVE_SYS_TYPES_H
24 #include <sys/types.h>
33 #include <mono/metadata/object.h>
34 #include <mono/metadata/file-io.h>
35 #include <mono/metadata/file-mmap.h>
36 #include <mono/utils/atomic.h>
37 #include <mono/utils/mono-memory-model.h>
38 #include <mono/utils/mono-mmap.h>
55 BAD_CAPACITY_FOR_FILE_BACKED = 1,
56 CAPACITY_SMALLER_THAN_FILE_SIZE,
61 CAPACITY_MUST_BE_POSITIVE,
67 FILE_MODE_CREATE_NEW = 1,
70 FILE_MODE_OPEN_OR_CREATE = 4,
71 FILE_MODE_TRUNCATE = 5,
76 MMAP_FILE_ACCESS_READ_WRITE = 0,
77 MMAP_FILE_ACCESS_READ = 1,
78 MMAP_FILE_ACCESS_WRITE = 2,
79 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
80 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
81 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
85 #define DEFFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
88 static int mmap_init_state;
89 static mono_mutex_t named_regions_mutex;
90 static GHashTable *named_regions;
94 align_up_to_page_size (gint64 size)
96 gint64 page_size = mono_pagesize ();
97 return (size + page_size - 1) & ~(page_size - 1);
101 align_down_to_page_size (gint64 size)
103 gint64 page_size = mono_pagesize ();
104 return size & ~(page_size - 1);
108 file_mmap_init (void)
111 switch (mmap_init_state) {
113 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
115 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
116 mono_mutex_init (&named_regions_mutex);
118 mono_atomic_store_release (&mmap_init_state, 2);
123 g_usleep (1000); /* Been init'd by other threads, this is very rare. */
124 } while (mmap_init_state != 2);
129 g_error ("Invalid init state %d", mmap_init_state);
134 named_regions_lock (void)
137 mono_mutex_lock (&named_regions_mutex);
141 named_regions_unlock (void)
143 mono_mutex_unlock (&named_regions_mutex);
148 file_mode_to_unix (int mode)
151 case FILE_MODE_CREATE_NEW:
152 return O_CREAT | O_EXCL;
153 case FILE_MODE_CREATE:
154 return O_CREAT | O_TRUNC;
157 case FILE_MODE_OPEN_OR_CREATE:
159 case FILE_MODE_TRUNCATE:
161 case FILE_MODE_APPEND:
164 g_error ("unknown FileMode %d", mode);
169 access_mode_to_unix (int access)
172 case MMAP_FILE_ACCESS_READ_WRITE:
173 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
174 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
176 case MMAP_FILE_ACCESS_READ:
177 case MMAP_FILE_ACCESS_READ_EXECUTE:
179 case MMAP_FILE_ACCESS_WRITE:
182 g_error ("unknown MemoryMappedFileAccess %d", access);
187 acess_to_mmap_flags (int access)
190 case MMAP_FILE_ACCESS_READ_WRITE:
191 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
193 case MMAP_FILE_ACCESS_WRITE:
194 return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
196 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
197 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
199 case MMAP_FILE_ACCESS_READ_EXECUTE:
200 return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
202 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
203 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
205 case MMAP_FILE_ACCESS_READ:
206 return MONO_MMAP_READ | MONO_MMAP_SHARED;
208 g_error ("unknown MemoryMappedFileAccess %d", access);
213 This allow us to special case zero size files that can be arbitrarily mapped.
216 is_special_zero_size_file (struct stat *buf)
218 return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0;
222 XXX implement options
225 open_file_map (MonoString *path, int input_fd, int mode, gint64 *capacity, int access, int options, int *error)
228 char *c_path = path ? mono_string_to_utf8 (path) : NULL;
229 MmapHandle *handle = NULL;
233 result = stat (c_path, &buf);
235 result = fstat (input_fd, &buf);
237 if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
238 if (result == -1) { //XXX translate errno?
239 *error = FILE_NOT_FOUND;
244 if (mode == FILE_MODE_CREATE_NEW && result == 0) {
245 *error = FILE_ALREADY_EXISTS;
250 if (*capacity == 0) {
252 * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
253 * also makes little sense, so don't do the check if th file is one of these.
255 if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) {
256 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
259 *capacity = buf.st_size;
260 } else if (*capacity < buf.st_size) {
261 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
265 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
266 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
271 if (path) //FIXME use io portability?
272 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFFILEMODE);
276 if (fd == -1) { //XXX translate errno?
277 *error = COULD_NOT_OPEN;
281 *capacity = align_up_to_page_size ((size_t)*capacity);
283 if (*capacity > buf.st_size) {
284 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
287 handle = g_new0 (MmapHandle, 1);
288 handle->ref_count = 1;
289 handle->capacity = *capacity;
294 return (void*)handle;
297 #define MONO_ANON_FILE_TEMPLATE "/tmp/mono.anonmap.XXXXXXXXX"
299 open_memory_map (MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
303 if (*capacity <= 1) {
304 *error = CAPACITY_MUST_BE_POSITIVE;
308 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
309 *error = INVALID_FILE_MODE;
313 c_mapName = mono_string_to_utf8 (mapName);
315 named_regions_lock ();
316 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
318 if (mode == FILE_MODE_CREATE_NEW) {
319 *error = FILE_ALREADY_EXISTS;
324 //XXX should we ftruncate if the file is smaller than capacity?
327 char file_name [sizeof (MONO_ANON_FILE_TEMPLATE) + 1];
329 if (mode == FILE_MODE_OPEN) {
330 *error = FILE_NOT_FOUND;
333 *capacity = align_up_to_page_size (*capacity);
335 strcpy (file_name, MONO_ANON_FILE_TEMPLATE);
336 fd = mkstemp (file_name);
338 *error = COULD_NOT_MAP_MEMORY;
343 ftruncate (fd, (off_t)*capacity);
345 handle = g_new0 (MmapHandle, 1);
346 handle->ref_count = 1;
347 handle->capacity = *capacity;
349 handle->name = g_strdup (c_mapName);
351 g_hash_table_insert (named_regions, handle->name, handle);
356 named_regions_unlock ();
364 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
366 g_assert (path || mapName);
369 return open_file_map (path, -1, mode, capacity, access, options, error);
373 char *c_mapName = mono_string_to_utf8 (mapName);
375 named_regions_lock ();
376 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
378 *error = FILE_ALREADY_EXISTS;
381 handle = open_file_map (path, -1, mode, capacity, access, options, error);
383 handle->name = g_strdup (c_mapName);
384 g_hash_table_insert (named_regions, handle->name, handle);
387 named_regions_unlock ();
393 return open_memory_map (mapName, mode, capacity, access, options, error);
397 mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
400 char *c_mapName = mono_string_to_utf8 (mapName);
402 named_regions_lock ();
403 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
405 *error = FILE_ALREADY_EXISTS;
408 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
409 handle = open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, error);
410 handle->name = g_strdup (c_mapName);
411 g_hash_table_insert (named_regions, handle->name, handle);
413 named_regions_unlock ();
420 mono_mmap_close (void *mmap_handle)
422 MmapHandle *handle = mmap_handle;
424 named_regions_lock ();
426 if (handle->ref_count == 0) {
428 g_hash_table_remove (named_regions, handle->name);
430 g_free (handle->name);
434 named_regions_unlock ();
438 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
440 MmapHandle *h = mmap_handle;
444 flags = fcntl (fd, F_GETFD, 0);
446 flags &= ~FD_CLOEXEC;
449 fcntl (fd, F_SETFD, flags);
453 mono_mmap_flush (void *mmap_handle)
455 MmapInstance *h = mmap_handle;
458 msync (h->address, h->length, MS_SYNC);
462 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
464 gint64 mmap_offset = 0;
465 MmapHandle *fh = handle;
466 MmapInstance res = { 0 };
467 size_t eff_size = *size;
468 struct stat buf = { 0 };
469 fstat (fh->fd, &buf); //FIXME error handling
472 * We use the file size if one of the following conditions is true:
473 * -input size is zero
474 * -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
476 if (eff_size == 0 || (eff_size > buf.st_size && !is_special_zero_size_file (&buf)))
477 eff_size = buf.st_size;
480 mmap_offset = align_down_to_page_size (offset);
481 eff_size += (offset - mmap_offset);
482 //FIXME translate some interesting errno values
483 res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
484 res.length = eff_size;
487 *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
488 *base_address = (char*)res.address + (offset - mmap_offset);
493 *base_address = NULL;
494 return COULD_NOT_MAP_MEMORY;
498 mono_mmap_unmap (void *mmap_handle)
501 MmapInstance *h = mmap_handle;
503 res = mono_file_unmap (h->address, h->free_handle);