/** * \file * File mmap internal calls * * Author: * Rodrigo Kumpera * * Copyright 2014 Xamarin Inc (http://www.xamarin.com) * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ #include #ifndef HOST_WIN32 #include #include #include #ifdef HAVE_UNISTD_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_TYPES_H #include #endif #if HAVE_SYS_MMAN_H #include #endif #include #include #include #include #include #include #include #include #include typedef struct { int kind; int ref_count; size_t capacity; char *name; int fd; } MmapHandle; typedef struct { void *address; void *free_handle; size_t length; } MmapInstance; enum { BAD_CAPACITY_FOR_FILE_BACKED = 1, CAPACITY_SMALLER_THAN_FILE_SIZE, FILE_NOT_FOUND, FILE_ALREADY_EXISTS, PATH_TOO_LONG, COULD_NOT_OPEN, CAPACITY_MUST_BE_POSITIVE, INVALID_FILE_MODE, COULD_NOT_MAP_MEMORY, ACCESS_DENIED, CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE }; enum { FILE_MODE_CREATE_NEW = 1, FILE_MODE_CREATE = 2, FILE_MODE_OPEN = 3, FILE_MODE_OPEN_OR_CREATE = 4, FILE_MODE_TRUNCATE = 5, FILE_MODE_APPEND = 6, }; enum { MMAP_FILE_ACCESS_READ_WRITE = 0, MMAP_FILE_ACCESS_READ = 1, MMAP_FILE_ACCESS_WRITE = 2, MMAP_FILE_ACCESS_COPY_ON_WRITE = 3, MMAP_FILE_ACCESS_READ_EXECUTE = 4, MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5, }; #ifdef DEFFILEMODE #define DEFAULT_FILEMODE DEFFILEMODE #else #define DEFAULT_FILEMODE 0666 #endif static int mmap_init_state; static MonoCoopMutex named_regions_mutex; static GHashTable *named_regions; static gint64 align_up_to_page_size (gint64 size) { gint64 page_size = mono_pagesize (); return (size + page_size - 1) & ~(page_size - 1); } static gint64 align_down_to_page_size (gint64 size) { gint64 page_size = mono_pagesize (); return size & ~(page_size - 1); } static void file_mmap_init (void) { retry: switch (mmap_init_state) { case 0: if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0) goto retry; named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); mono_coop_mutex_init (&named_regions_mutex); mono_atomic_store_release (&mmap_init_state, 2); break; case 1: do { mono_thread_info_sleep (1, NULL); /* Been init'd by other threads, this is very rare. */ } while (mmap_init_state != 2); break; case 2: break; default: g_error ("Invalid init state %d", mmap_init_state); } } static void named_regions_lock (void) { file_mmap_init (); mono_coop_mutex_lock (&named_regions_mutex); } static void named_regions_unlock (void) { mono_coop_mutex_unlock (&named_regions_mutex); } static int file_mode_to_unix (int mode) { switch (mode) { case FILE_MODE_CREATE_NEW: return O_CREAT | O_EXCL; case FILE_MODE_CREATE: return O_CREAT | O_TRUNC; case FILE_MODE_OPEN: return 0; case FILE_MODE_OPEN_OR_CREATE: return O_CREAT; case FILE_MODE_TRUNCATE: return O_TRUNC; case FILE_MODE_APPEND: return O_APPEND; default: g_error ("unknown FileMode %d", mode); } } static int access_mode_to_unix (int access) { switch (access) { case MMAP_FILE_ACCESS_READ_WRITE: case MMAP_FILE_ACCESS_COPY_ON_WRITE: case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE: return O_RDWR; case MMAP_FILE_ACCESS_READ: case MMAP_FILE_ACCESS_READ_EXECUTE: return O_RDONLY; case MMAP_FILE_ACCESS_WRITE: return O_WRONLY; default: g_error ("unknown MemoryMappedFileAccess %d", access); } } static int acess_to_mmap_flags (int access) { switch (access) { case MMAP_FILE_ACCESS_READ_WRITE: return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED; case MMAP_FILE_ACCESS_WRITE: return MONO_MMAP_WRITE | MONO_MMAP_SHARED; case MMAP_FILE_ACCESS_COPY_ON_WRITE: return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE; case MMAP_FILE_ACCESS_READ_EXECUTE: return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED; case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE: return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED; case MMAP_FILE_ACCESS_READ: return MONO_MMAP_READ | MONO_MMAP_SHARED; default: g_error ("unknown MemoryMappedFileAccess %d", access); } } /* This allow us to special case zero size files that can be arbitrarily mapped. */ static gboolean is_special_zero_size_file (struct stat *buf) { return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0; } /* XXX implement options */ static void* open_file_map (const char *c_path, int input_fd, int mode, gint64 *capacity, int access, int options, int *ioerror) { struct stat buf; MmapHandle *handle = NULL; int result, fd; if (c_path) result = stat (c_path, &buf); else result = fstat (input_fd, &buf); if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) { if (result == -1) { //XXX translate errno? *ioerror = FILE_NOT_FOUND; goto done; } } if (mode == FILE_MODE_CREATE_NEW && result == 0) { *ioerror = FILE_ALREADY_EXISTS; goto done; } if (result == 0) { if (*capacity == 0) { /** * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these * also makes little sense, so don't do the check if th file is one of these. */ if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) { *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE; goto done; } *capacity = buf.st_size; } else if (*capacity < buf.st_size) { *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE; goto done; } } else { if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) { *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE; goto done; } } if (c_path) //FIXME use io portability? fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFAULT_FILEMODE); else fd = dup (input_fd); if (fd == -1) { //XXX translate errno? *ioerror = COULD_NOT_OPEN; goto done; } if (result != 0 || *capacity > buf.st_size) { int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity); } handle = g_new0 (MmapHandle, 1); handle->ref_count = 1; handle->capacity = *capacity; handle->fd = fd; done: return (void*)handle; } #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX" static void* open_memory_map (const char *c_mapName, int mode, gint64 *capacity, int access, int options, int *ioerror) { MmapHandle *handle; if (*capacity <= 0 && mode != FILE_MODE_OPEN) { *ioerror = CAPACITY_MUST_BE_POSITIVE; return NULL; } #if SIZEOF_VOID_P == 4 if (*capacity > UINT32_MAX) { *ioerror = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE; return NULL; } #endif if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) { *ioerror = INVALID_FILE_MODE; return NULL; } named_regions_lock (); handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName); if (handle) { if (mode == FILE_MODE_CREATE_NEW) { *ioerror = FILE_ALREADY_EXISTS; goto done; } handle->ref_count++; //XXX should we ftruncate if the file is smaller than capacity? } else { int fd; char *file_name; const char *tmp_dir; int unused G_GNUC_UNUSED, alloc_size; if (mode == FILE_MODE_OPEN) { *ioerror = FILE_NOT_FOUND; goto done; } *capacity = align_up_to_page_size (*capacity); tmp_dir = g_get_tmp_dir (); alloc_size = strlen (tmp_dir) + strlen (MONO_ANON_FILE_TEMPLATE) + 1; if (alloc_size > 1024) {//rather fail that stack overflow *ioerror = COULD_NOT_MAP_MEMORY; goto done; } file_name = (char *)alloca (alloc_size); strcpy (file_name, tmp_dir); strcat (file_name, MONO_ANON_FILE_TEMPLATE); fd = mkstemp (file_name); if (fd == -1) { *ioerror = COULD_NOT_MAP_MEMORY; goto done; } unlink (file_name); unused = ftruncate (fd, (off_t)*capacity); handle = g_new0 (MmapHandle, 1); handle->ref_count = 1; handle->capacity = *capacity; handle->fd = fd; handle->name = g_strdup (c_mapName); g_hash_table_insert (named_regions, handle->name, handle); } done: named_regions_unlock (); return handle; } /* This is an icall */ void * mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror) { MonoError error; MmapHandle *handle = NULL; g_assert (path || mapName); if (!mapName) { char * c_path = mono_string_to_utf8_checked (path, &error); if (mono_error_set_pending_exception (&error)) return NULL; handle = open_file_map (c_path, -1, mode, capacity, access, options, ioerror); g_free (c_path); return handle; } char *c_mapName = mono_string_to_utf8_checked (mapName, &error); if (mono_error_set_pending_exception (&error)) return NULL; if (path) { named_regions_lock (); handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName); if (handle) { *ioerror = FILE_ALREADY_EXISTS; handle = NULL; } else { char *c_path = mono_string_to_utf8_checked (path, &error); if (is_ok (&error)) { handle = (MmapHandle *)open_file_map (c_path, -1, mode, capacity, access, options, ioerror); if (handle) { handle->name = g_strdup (c_mapName); g_hash_table_insert (named_regions, handle->name, handle); } } else { handle = NULL; } g_free (c_path); } named_regions_unlock (); } else handle = open_memory_map (c_mapName, mode, capacity, access, options, ioerror); g_free (c_mapName); return handle; } /* this is an icall */ void * mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror) { MonoError error; MmapHandle *handle; if (!mapName) { handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror); } else { char *c_mapName = mono_string_to_utf8_checked (mapName, &error); if (mono_error_set_pending_exception (&error)) return NULL; named_regions_lock (); handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName); if (handle) { *ioerror = FILE_ALREADY_EXISTS; handle = NULL; } else { //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror); handle->name = g_strdup (c_mapName); g_hash_table_insert (named_regions, handle->name, handle); } named_regions_unlock (); g_free (c_mapName); } return handle; } void mono_mmap_close (void *mmap_handle) { MmapHandle *handle = (MmapHandle *)mmap_handle; named_regions_lock (); --handle->ref_count; if (handle->ref_count == 0) { if (handle->name) g_hash_table_remove (named_regions, handle->name); g_free (handle->name); close (handle->fd); g_free (handle); } named_regions_unlock (); } void mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability) { MmapHandle *h = (MmapHandle *)mmap_handle; int fd, flags; fd = h->fd; flags = fcntl (fd, F_GETFD, 0); if (inheritability) flags &= ~FD_CLOEXEC; else flags |= FD_CLOEXEC; fcntl (fd, F_SETFD, flags); } void mono_mmap_flush (void *mmap_handle) { MmapInstance *h = (MmapInstance *)mmap_handle; if (h) msync (h->address, h->length, MS_SYNC); } int mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address) { gint64 mmap_offset = 0; MmapHandle *fh = (MmapHandle *)handle; MmapInstance res = { 0 }; size_t eff_size = *size; struct stat buf = { 0 }; fstat (fh->fd, &buf); //FIXME error handling *mmap_handle = NULL; *base_address = NULL; if (offset > buf.st_size || ((eff_size + offset) > buf.st_size && !is_special_zero_size_file (&buf))) return ACCESS_DENIED; /** * We use the file size if one of the following conditions is true: * -input size is zero * -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem. */ if (eff_size == 0) eff_size = align_up_to_page_size (buf.st_size) - offset; *size = eff_size; mmap_offset = align_down_to_page_size (offset); eff_size += (offset - mmap_offset); //FIXME translate some interesting errno values res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle); res.length = eff_size; if (res.address) { *mmap_handle = g_memdup (&res, sizeof (MmapInstance)); *base_address = (char*)res.address + (offset - mmap_offset); return 0; } return COULD_NOT_MAP_MEMORY; } gboolean mono_mmap_unmap (void *mmap_handle) { int res = 0; MmapInstance *h = (MmapInstance *)mmap_handle; res = mono_file_unmap (h->address, h->free_handle); g_free (h); return res == 0; } #endif