Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mono / metadata / file-mmap-posix.c
index afd97b22ed5602a05b9bacf779e9315200050ab5..3c971a091b5f907ea864a239e9e118b5f121e162 100644 (file)
@@ -1,15 +1,17 @@
-/*
- * file-mmap-posix.c: File mmap internal calls
+/**
+ * \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 TARGET_WIN32
+#ifndef HOST_WIN32
 
 #include <glib.h>
 #include <string.h>
 
 
 #include <mono/metadata/object.h>
-#include <mono/metadata/file-io.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>
-
-enum {
-       MMAP_KIND_FILE = 1,
-       MMAP_KIND_MEMORY = 2    
-};
+#include <mono/utils/mono-coop-mutex.h>
+#include <mono/utils/mono-threads.h>
 
 typedef struct {
        int kind;
        int ref_count;
-       gint64 capacity;
+       size_t capacity;
        char *name;
-} Handle;
-
-typedef struct {
-       Handle handle;
        int fd;
-} FileHandle;
-
-typedef struct {
-       Handle handle;
-       void *address;
-       size_t length;
-} MemoryHandle;
+} MmapHandle;
 
 typedef struct {
        void *address;
        void *free_handle;
-       int kind;
        size_t length;
-} MmapHandle;
+} MmapInstance;
 
 enum {
        BAD_CAPACITY_FOR_FILE_BACKED = 1,
@@ -76,7 +64,9 @@ enum {
        COULD_NOT_OPEN,
        CAPACITY_MUST_BE_POSITIVE,
        INVALID_FILE_MODE,
-       COULD_NOT_MAP_MEMORY
+       COULD_NOT_MAP_MEMORY,
+       ACCESS_DENIED,
+       CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
 };
 
 enum {
@@ -97,9 +87,14 @@ enum {
        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 mono_mutex_t named_regions_mutex;
+static MonoCoopMutex named_regions_mutex;
 static GHashTable *named_regions;
 
 
@@ -126,14 +121,14 @@ retry:
                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_mutex_init (&named_regions_mutex);
+               mono_coop_mutex_init (&named_regions_mutex);
 
                mono_atomic_store_release (&mmap_init_state, 2);
                break;
 
        case 1:
                do {
-                       g_usleep (1000); /* Been init'd by other threads, this is very rare. */
+                       mono_thread_info_sleep (1, NULL); /* Been init'd by other threads, this is very rare. */
                } while (mmap_init_state != 2);
                break;
        case 2:
@@ -147,13 +142,13 @@ static void
 named_regions_lock (void)
 {
        file_mmap_init ();
-       mono_mutex_lock (&named_regions_mutex);
+       mono_coop_mutex_lock (&named_regions_mutex);
 }
 
 static void
 named_regions_unlock (void)
 {
-       mono_mutex_unlock (&named_regions_mutex);       
+       mono_coop_mutex_unlock (&named_regions_mutex);
 }
 
 
@@ -201,52 +196,60 @@ acess_to_mmap_flags (int access)
 {
        switch (access) {
        case MMAP_FILE_ACCESS_READ_WRITE:
-        return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
+        return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
         
        case MMAP_FILE_ACCESS_WRITE:
-        return MONO_MMAP_WRITE | MONO_MMAP_PRIVATE;
+        return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
         
        case MMAP_FILE_ACCESS_COPY_ON_WRITE:
-        return MONO_MMAP_WRITE | MONO_MMAP_READ;
+        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_PRIVATE;
+        return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
         
        case MMAP_FILE_ACCESS_READ:
-        return MONO_MMAP_READ | MONO_MMAP_PRIVATE;
+        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 (MonoString *path, int input_fd, int mode, gint64 *capacity, int access, int options, int *error)
+open_file_map (const char *c_path, int input_fd, int mode, gint64 *capacity, int access, int options, int *ioerror)
 {
        struct stat buf;
-       char *c_path = path ? mono_string_to_utf8 (path) : NULL;
-       FileHandle *handle = NULL;
+       MmapHandle *handle = NULL;
        int result, fd;
 
-       if (path)
+       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?
-                       *error = FILE_NOT_FOUND;
+                       *ioerror = FILE_NOT_FOUND;
                        goto done;
                }
        }
 
        if (mode == FILE_MODE_CREATE_NEW && result == 0) {
-               *error = FILE_ALREADY_EXISTS;
+               *ioerror = FILE_ALREADY_EXISTS;
                goto done;
        }
 
@@ -256,170 +259,216 @@ open_file_map (MonoString *path, int input_fd, int mode, gint64 *capacity, int a
                         * 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 && (buf.st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) == 0) {
-                               *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
+                       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) {
-                       *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
+                       *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
                        goto done;
                }
        } else {
                if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
-                       *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
+                       *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
                        goto done;
                }
        }
 
-       if (path) //FIXME use io portability?
-               fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFFILEMODE);
+       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?
-               *error = COULD_NOT_OPEN;
+               *ioerror = COULD_NOT_OPEN;
                goto done;
        }
 
-       *capacity = align_up_to_page_size ((size_t)*capacity);
-
-       if (*capacity > buf.st_size)
-               ftruncate (fd, (off_t)*capacity);
+       if (result != 0 || *capacity > buf.st_size) {
+               int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
+       }
 
-       handle = g_new0 (FileHandle, 1);
-       handle->handle.kind = MMAP_KIND_FILE;
-       handle->handle.ref_count = 1;
-       handle->handle.capacity = *capacity;
+       handle = g_new0 (MmapHandle, 1);
+       handle->ref_count = 1;
+       handle->capacity = *capacity;
        handle->fd = fd;
 
 done:
-       g_free (c_path);
        return (void*)handle;
 }
 
+#define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
 static void*
-open_memory_map (MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
+open_memory_map (const char *c_mapName, int mode, gint64 *capacity, int access, int options, int *ioerror)
 {
-       char *c_mapName;
-       MemoryHandle *handle;
-       if (*capacity <= 1) {
-               *error = CAPACITY_MUST_BE_POSITIVE;
+       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)) {
-               *error = INVALID_FILE_MODE;
+               *ioerror = INVALID_FILE_MODE;
                return NULL;
        }
 
-       c_mapName = mono_string_to_utf8 (mapName);
-
        named_regions_lock ();
-       handle = (MemoryHandle*)g_hash_table_lookup (named_regions, c_mapName);
+       handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
        if (handle) {
-               if (mode == FILE_MODE_CREATE_NEW)
-                       *error = FILE_ALREADY_EXISTS;
+               if (mode == FILE_MODE_CREATE_NEW) {
+                       *ioerror = FILE_ALREADY_EXISTS;
+                       goto done;
+               }
 
-               handle->handle.ref_count++;
+               handle->ref_count++;
                //XXX should we ftruncate if the file is smaller than capacity?
        } else {
-               if (mode == FILE_MODE_OPEN)
-                       *error = FILE_NOT_FOUND;
+               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);
 
-               handle = g_new0 (MemoryHandle, 1);
-               handle->handle.kind = MMAP_KIND_MEMORY;
-               handle->handle.ref_count = 1;
-               handle->handle.capacity = *capacity;
-               handle->handle.name = g_strdup (c_mapName);
+               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);
 
-               //FIXME compute RWX from access
-               handle->address = mono_valloc (NULL, (size_t)*capacity, MONO_MMAP_READ | MONO_MMAP_WRITE | MONO_MMAP_PRIVATE | MONO_MMAP_ANON);
-               handle->length = (size_t)*capacity;
-               g_hash_table_insert (named_regions, handle->handle.name, handle);
        }
 
+done:
        named_regions_unlock ();
 
-       g_free (c_mapName);
        return handle;
 }
 
 
+/* This is an icall */
 void *
-mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
+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)
-               return open_file_map (path, -1, mode, capacity, access, options, error);
+       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;
+       }
 
-       if (path) {
-               FileHandle *file_handle;
-               char *c_mapName = mono_string_to_utf8 (mapName);
+       char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
+       if (mono_error_set_pending_exception (&error))
+               return NULL;
 
+       if (path) {
                named_regions_lock ();
-               file_handle = (FileHandle*)g_hash_table_lookup (named_regions, c_mapName);
-               if (file_handle) {
-                       *error = FILE_ALREADY_EXISTS;
-                       file_handle = NULL;
+               handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
+               if (handle) {
+                       *ioerror = FILE_ALREADY_EXISTS;
+                       handle = NULL;
                } else {
-                       file_handle = open_file_map (path, -1, mode, capacity, access, options, error);
-                       if (file_handle) {
-                               file_handle->handle.name = g_strdup (c_mapName);
-                               g_hash_table_insert (named_regions, file_handle->handle.name, file_handle);
+                       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 file_handle;
-       }
-
-       return open_memory_map (mapName, mode, capacity, access, options, error);
+       g_free (c_mapName);
+       return handle;
 }
 
+/* this is an icall */
 void *
-mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
+mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
 {
-       FileHandle *file_handle;
-       char *c_mapName = mono_string_to_utf8 (mapName);
-
-       named_regions_lock ();
-       file_handle = (FileHandle*)g_hash_table_lookup (named_regions, c_mapName);
-       if (file_handle) {
-               *error = FILE_ALREADY_EXISTS;
-               file_handle = NULL;
+       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 {
-               //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
-               file_handle = open_file_map (NULL, GPOINTER_TO_INT (handle), FILE_MODE_OPEN, capacity, access, options, error);
-               file_handle->handle.name = g_strdup (c_mapName);
-               g_hash_table_insert (named_regions, file_handle->handle.name, file_handle);
-       }
-       named_regions_unlock ();
+               char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
+               if (mono_error_set_pending_exception (&error))
+                       return NULL;
 
-       g_free (c_mapName);
-       return file_handle;
+               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)
 {
-       Handle *handle = 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);
-               if (handle->kind == MMAP_KIND_MEMORY)
-                       mono_vfree (((MemoryHandle*)handle)->address, ((MemoryHandle*)handle)->length);
-               else
-                       close (((FileHandle*)handle)->fd);
+               close (handle->fd);
                g_free (handle);
        }
        named_regions_unlock ();
@@ -428,12 +477,9 @@ mono_mmap_close (void *mmap_handle)
 void
 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
 {
-       FileHandle *h = mmap_handle;
+       MmapHandle *h = (MmapHandle *)mmap_handle;
        int fd, flags;
 
-       if (h->handle.kind != MMAP_KIND_FILE)
-               return;
-
        fd = h->fd;
        flags = fcntl (fd, F_GETFD, 0);
        if (inheritability)
@@ -446,7 +492,7 @@ mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
 void
 mono_mmap_flush (void *mmap_handle)
 {
-       MmapHandle *h = mmap_handle;
+       MmapInstance *h = (MmapInstance *)mmap_handle;
 
        if (h)
                msync (h->address, h->length, MS_SYNC);
@@ -456,45 +502,38 @@ int
 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
 {
        gint64 mmap_offset = 0;
-       Handle *h = handle;
-       MmapHandle res = { 0 };
-       if (h->kind == MMAP_KIND_FILE) {
-               FileHandle *fh = (FileHandle*)h;
-               size_t eff_size = *size;
-               struct stat buf = { 0 };
-               fstat (fh->fd, &buf); //FIXME error handling
-
-               if (eff_size == 0 || eff_size > buf.st_size)
-                       eff_size = buf.st_size;
-               *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;
-               res.kind = MMAP_KIND_FILE;
+       MmapHandle *fh = (MmapHandle *)handle;
+       MmapInstance res = { 0 };
+       size_t eff_size = *size;
+       struct stat buf = { 0 };
+       fstat (fh->fd, &buf); //FIXME error handling
 
-       } else {
-               MemoryHandle *mh = (MemoryHandle*)h;
-               size_t eff_size = *size;
-
-               if (!eff_size)
-                       eff_size = *size = mh->length;
-               mmap_offset = (size_t)mmap_offset;
-               res.address = (char*)mh->address + offset;
-               res.length = (size_t)size;
-               res.kind = MMAP_KIND_MEMORY;
-       }
+       *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 (MmapHandle));
+               *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
                *base_address = (char*)res.address + (offset - mmap_offset);
                return 0;
        }
 
-       *mmap_handle = NULL;
-       *base_address = NULL;
        return COULD_NOT_MAP_MEMORY;
 }
 
@@ -502,10 +541,9 @@ gboolean
 mono_mmap_unmap (void *mmap_handle)
 {
        int res = 0;
-       MmapHandle *h = mmap_handle;
+       MmapInstance *h = (MmapInstance *)mmap_handle;
 
-       if (h->kind == MMAP_KIND_FILE)
-               res = mono_file_unmap (h->address, h->free_handle);
+       res = mono_file_unmap (h->address, h->free_handle);
 
        g_free (h);
        return res == 0;