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>
71 BAD_CAPACITY_FOR_FILE_BACKED = 1,
72 CAPACITY_SMALLER_THAN_FILE_SIZE,
77 CAPACITY_MUST_BE_POSITIVE,
83 FILE_MODE_CREATE_NEW = 1,
86 FILE_MODE_OPEN_OR_CREATE = 4,
87 FILE_MODE_TRUNCATE = 5,
92 MMAP_FILE_ACCESS_READ_WRITE = 0,
93 MMAP_FILE_ACCESS_READ = 1,
94 MMAP_FILE_ACCESS_WRITE = 2,
95 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
96 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
97 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
101 #define DEFFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
104 static int mmap_init_state;
105 static mono_mutex_t named_regions_mutex;
106 static GHashTable *named_regions;
110 align_up_to_page_size (gint64 size)
112 gint64 page_size = mono_pagesize ();
113 return (size + page_size - 1) & ~(page_size - 1);
117 align_down_to_page_size (gint64 size)
119 gint64 page_size = mono_pagesize ();
120 return size & ~(page_size - 1);
124 file_mmap_init (void)
127 switch (mmap_init_state) {
129 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
131 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
132 mono_mutex_init (&named_regions_mutex);
134 mono_atomic_store_release (&mmap_init_state, 2);
139 g_usleep (1000); /* Been init'd by other threads, this is very rare. */
140 } while (mmap_init_state != 2);
145 g_error ("Invalid init state %d", mmap_init_state);
150 named_regions_lock (void)
153 mono_mutex_lock (&named_regions_mutex);
157 named_regions_unlock (void)
159 mono_mutex_unlock (&named_regions_mutex);
164 file_mode_to_unix (int mode)
167 case FILE_MODE_CREATE_NEW:
168 return O_CREAT | O_EXCL;
169 case FILE_MODE_CREATE:
170 return O_CREAT | O_TRUNC;
173 case FILE_MODE_OPEN_OR_CREATE:
175 case FILE_MODE_TRUNCATE:
177 case FILE_MODE_APPEND:
180 g_error ("unknown FileMode %d", mode);
185 access_mode_to_unix (int access)
188 case MMAP_FILE_ACCESS_READ_WRITE:
189 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
190 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
192 case MMAP_FILE_ACCESS_READ:
193 case MMAP_FILE_ACCESS_READ_EXECUTE:
195 case MMAP_FILE_ACCESS_WRITE:
198 g_error ("unknown MemoryMappedFileAccess %d", access);
203 acess_to_mmap_flags (int access)
206 case MMAP_FILE_ACCESS_READ_WRITE:
207 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
209 case MMAP_FILE_ACCESS_WRITE:
210 return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
212 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
213 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
215 case MMAP_FILE_ACCESS_READ_EXECUTE:
216 return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
218 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
219 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
221 case MMAP_FILE_ACCESS_READ:
222 return MONO_MMAP_READ | MONO_MMAP_SHARED;
224 g_error ("unknown MemoryMappedFileAccess %d", access);
229 XXX implement options
232 open_file_map (MonoString *path, int input_fd, int mode, gint64 *capacity, int access, int options, int *error)
235 char *c_path = path ? mono_string_to_utf8 (path) : NULL;
236 FileHandle *handle = NULL;
240 result = stat (c_path, &buf);
242 result = fstat (input_fd, &buf);
244 if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
245 if (result == -1) { //XXX translate errno?
246 *error = FILE_NOT_FOUND;
251 if (mode == FILE_MODE_CREATE_NEW && result == 0) {
252 *error = FILE_ALREADY_EXISTS;
257 if (*capacity == 0) {
259 * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
260 * also makes little sense, so don't do the check if th file is one of these.
262 if (buf.st_size == 0 && (buf.st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) == 0) {
263 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
266 *capacity = buf.st_size;
267 } else if (*capacity < buf.st_size) {
268 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
272 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
273 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
278 if (path) //FIXME use io portability?
279 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFFILEMODE);
283 if (fd == -1) { //XXX translate errno?
284 *error = COULD_NOT_OPEN;
288 *capacity = align_up_to_page_size ((size_t)*capacity);
290 if (*capacity > buf.st_size) {
291 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
294 handle = g_new0 (FileHandle, 1);
295 handle->handle.kind = MMAP_KIND_FILE;
296 handle->handle.ref_count = 1;
297 handle->handle.capacity = *capacity;
302 return (void*)handle;
306 open_memory_map (MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
309 MemoryHandle *handle;
310 if (*capacity <= 1) {
311 *error = CAPACITY_MUST_BE_POSITIVE;
315 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
316 *error = INVALID_FILE_MODE;
320 c_mapName = mono_string_to_utf8 (mapName);
322 named_regions_lock ();
323 handle = (MemoryHandle*)g_hash_table_lookup (named_regions, c_mapName);
325 if (mode == FILE_MODE_CREATE_NEW)
326 *error = FILE_ALREADY_EXISTS;
328 handle->handle.ref_count++;
329 //XXX should we ftruncate if the file is smaller than capacity?
331 if (mode == FILE_MODE_OPEN)
332 *error = FILE_NOT_FOUND;
334 *capacity = align_up_to_page_size (*capacity);
336 handle = g_new0 (MemoryHandle, 1);
337 handle->handle.kind = MMAP_KIND_MEMORY;
338 handle->handle.ref_count = 1;
339 handle->handle.capacity = *capacity;
340 handle->handle.name = g_strdup (c_mapName);
342 //FIXME compute RWX from access
343 handle->address = mono_valloc (NULL, (size_t)*capacity, MONO_MMAP_READ | MONO_MMAP_WRITE | MONO_MMAP_PRIVATE | MONO_MMAP_ANON);
344 handle->length = (size_t)*capacity;
345 g_hash_table_insert (named_regions, handle->handle.name, handle);
348 named_regions_unlock ();
356 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
358 g_assert (path || mapName);
361 return open_file_map (path, -1, mode, capacity, access, options, error);
364 FileHandle *file_handle;
365 char *c_mapName = mono_string_to_utf8 (mapName);
367 named_regions_lock ();
368 file_handle = (FileHandle*)g_hash_table_lookup (named_regions, c_mapName);
370 *error = FILE_ALREADY_EXISTS;
373 file_handle = open_file_map (path, -1, mode, capacity, access, options, error);
375 file_handle->handle.name = g_strdup (c_mapName);
376 g_hash_table_insert (named_regions, file_handle->handle.name, file_handle);
379 named_regions_unlock ();
385 return open_memory_map (mapName, mode, capacity, access, options, error);
389 mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
391 FileHandle *file_handle;
392 char *c_mapName = mono_string_to_utf8 (mapName);
394 named_regions_lock ();
395 file_handle = (FileHandle*)g_hash_table_lookup (named_regions, c_mapName);
397 *error = FILE_ALREADY_EXISTS;
400 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
401 file_handle = open_file_map (NULL, GPOINTER_TO_INT (handle), FILE_MODE_OPEN, capacity, access, options, error);
402 file_handle->handle.name = g_strdup (c_mapName);
403 g_hash_table_insert (named_regions, file_handle->handle.name, file_handle);
405 named_regions_unlock ();
412 mono_mmap_close (void *mmap_handle)
414 Handle *handle = mmap_handle;
416 named_regions_lock ();
418 if (handle->ref_count == 0) {
420 g_hash_table_remove (named_regions, handle->name);
422 g_free (handle->name);
423 if (handle->kind == MMAP_KIND_MEMORY)
424 mono_vfree (((MemoryHandle*)handle)->address, ((MemoryHandle*)handle)->length);
426 close (((FileHandle*)handle)->fd);
429 named_regions_unlock ();
433 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
435 FileHandle *h = mmap_handle;
438 if (h->handle.kind != MMAP_KIND_FILE)
442 flags = fcntl (fd, F_GETFD, 0);
444 flags &= ~FD_CLOEXEC;
447 fcntl (fd, F_SETFD, flags);
451 mono_mmap_flush (void *mmap_handle)
453 MmapHandle *h = mmap_handle;
456 msync (h->address, h->length, MS_SYNC);
460 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
462 gint64 mmap_offset = 0;
464 MmapHandle res = { 0 };
465 if (h->kind == MMAP_KIND_FILE) {
466 FileHandle *fh = (FileHandle*)h;
467 size_t eff_size = *size;
468 struct stat buf = { 0 };
469 fstat (fh->fd, &buf); //FIXME error handling
471 if (eff_size == 0 || eff_size > buf.st_size)
472 eff_size = buf.st_size;
475 mmap_offset = align_down_to_page_size (offset);
476 eff_size += (offset - mmap_offset);
477 //FIXME translate some interesting errno values
478 res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
479 res.length = eff_size;
480 res.kind = MMAP_KIND_FILE;
483 MemoryHandle *mh = (MemoryHandle*)h;
484 size_t eff_size = *size;
487 eff_size = *size = mh->length;
488 mmap_offset = (size_t)offset;
489 res.address = (char*)mh->address + offset;
490 res.length = (size_t)*size;
491 res.kind = MMAP_KIND_MEMORY;
495 *mmap_handle = g_memdup (&res, sizeof (MmapHandle));
496 *base_address = (char*)res.address + (offset - mmap_offset);
501 *base_address = NULL;
502 return COULD_NOT_MAP_MEMORY;
506 mono_mmap_unmap (void *mmap_handle)
509 MmapHandle *h = mmap_handle;
511 if (h->kind == MMAP_KIND_FILE)
512 res = mono_file_unmap (h->address, h->free_handle);