123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571 |
- /**
- * \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 <config.h>
- #ifndef HOST_WIN32
- #include <glib.h>
- #include <string.h>
- #include <errno.h>
- #ifdef HAVE_UNISTD_H
- #include <unistd.h>
- #endif
- #ifdef HAVE_SYS_STAT_H
- #include <sys/stat.h>
- #endif
- #ifdef HAVE_SYS_TYPES_H
- #include <sys/types.h>
- #endif
- #if HAVE_SYS_MMAN_H
- #include <sys/mman.h>
- #endif
- #include <fcntl.h>
- #include <mono/metadata/object.h>
- #include <mono/metadata/w32file.h>
- #include <mono/metadata/file-mmap.h>
- #include <mono/utils/atomic.h>
- #include <mono/utils/mono-memory-model.h>
- #include <mono/utils/mono-mmap.h>
- #include <mono/utils/mono-coop-mutex.h>
- #include <mono/utils/mono-threads.h>
- 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 (mono_atomic_cas_i32 (&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
- access_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;
- MONO_ENTER_GC_SAFE;
- if (c_path)
- result = stat (c_path, &buf);
- else
- result = fstat (input_fd, &buf);
- MONO_EXIT_GC_SAFE;
- 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;
- }
- }
- MONO_ENTER_GC_SAFE;
- 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);
- MONO_EXIT_GC_SAFE;
- if (fd == -1) { //XXX translate errno?
- *ioerror = COULD_NOT_OPEN;
- goto done;
- }
- if (result != 0 || *capacity > buf.st_size) {
- #ifdef HAVE_FTRUNCATE
- int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
- #endif
- }
- 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 = g_newa (char, alloc_size);
- strcpy (file_name, tmp_dir);
- strcat (file_name, MONO_ANON_FILE_TEMPLATE);
- MONO_ENTER_GC_SAFE;
- fd = mkstemp (file_name);
- MONO_EXIT_GC_SAFE;
- if (fd == -1) {
- *ioerror = COULD_NOT_MAP_MEMORY;
- goto done;
- }
- MONO_ENTER_GC_SAFE;
- unlink (file_name);
- MONO_EXIT_GC_SAFE;
- #ifdef HAVE_FTRUNCATE
- unused = ftruncate (fd, (off_t)*capacity);
- #endif
- 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 (const gunichar2 *path, gint path_length, int mode, const gunichar2 *mapName, gint mapName_length, gint64 *capacity, int access, int options, int *ioerror, MonoError *error)
- {
- MmapHandle *handle = NULL;
- g_assert (path || mapName);
- if (!mapName) {
- char * c_path = mono_utf16_to_utf8 (path, path_length, error);
- return_val_if_nok (error, NULL);
- handle = (MmapHandle*)open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
- g_free (c_path);
- return handle;
- }
- char *c_mapName = mono_utf16_to_utf8 (mapName, mapName_length, error);
- return_val_if_nok (error, 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_utf16_to_utf8 (path, path_length, 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 = (MmapHandle*)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, const mono_unichar2 *mapName, gint mapName_length, 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_utf16_to_utf8 (mapName, mapName_length, error);
- return_val_if_nok (error, 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, MonoError *error)
- {
- 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);
- MONO_ENTER_GC_SAFE;
- close (handle->fd);
- MONO_EXIT_GC_SAFE;
- g_free (handle);
- }
- named_regions_unlock ();
- }
- void
- mono_mmap_configure_inheritability (void *mmap_handle, gint32 inheritability, MonoError *error)
- {
- MmapHandle *h = (MmapHandle *)mmap_handle;
- int fd, flags;
- fd = h->fd;
- MONO_ENTER_GC_SAFE;
- flags = fcntl (fd, F_GETFD, 0);
- MONO_EXIT_GC_SAFE;
- if (inheritability)
- flags &= ~FD_CLOEXEC;
- else
- flags |= FD_CLOEXEC;
- fcntl (fd, F_SETFD, flags);
- }
- void
- mono_mmap_flush (void *mmap_handle, MonoError *error)
- {
- MmapInstance *h = (MmapInstance *)mmap_handle;
- #ifdef HAVE_MSYNC
- if (h)
- MONO_ENTER_GC_SAFE;
- msync (h->address, h->length, MS_SYNC);
- MONO_EXIT_GC_SAFE;
- #endif
- }
- int
- mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address, MonoError *error)
- {
- 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);
- MONO_ENTER_GC_SAFE;
- //FIXME translate some interesting errno values
- res.address = mono_file_map ((size_t)eff_size, access_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
- MONO_EXIT_GC_SAFE;
- 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;
- }
- MonoBoolean
- mono_mmap_unmap (void *base_address, MonoError *error)
- {
- int res = 0;
- MmapInstance *h = (MmapInstance *)base_address;
- MONO_ENTER_GC_SAFE;
- res = mono_file_unmap (h->address, h->free_handle);
- MONO_EXIT_GC_SAFE;
- g_free (h);
- return res == 0;
- }
- #endif
|