Merge branch 'master' into mono4-continuations_fix
[mono.git] / mono / metadata / file-mmap-posix.c
1 /*
2  * file-mmap-posix.c: File mmap internal calls
3  *
4  * Author:
5  *      Rodrigo Kumpera
6  *
7  * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
8  * Licensed under the MIT license. See LICENSE file in the project root for full license information.
9  */
10
11 #include <config.h>
12
13 #ifndef HOST_WIN32
14
15 #include <glib.h>
16 #include <string.h>
17 #include <errno.h>
18 #ifdef HAVE_UNISTD_H
19 #include <unistd.h>
20 #endif
21 #ifdef HAVE_SYS_STAT_H
22 #include <sys/stat.h>
23 #endif
24 #ifdef HAVE_SYS_TYPES_H
25 #include <sys/types.h>
26 #endif
27 #if HAVE_SYS_MMAN_H
28 #include <sys/mman.h>
29 #endif
30
31 #include <fcntl.h>
32
33
34 #include <mono/metadata/object.h>
35 #include <mono/metadata/file-io.h>
36 #include <mono/metadata/file-mmap.h>
37 #include <mono/utils/atomic.h>
38 #include <mono/utils/mono-memory-model.h>
39 #include <mono/utils/mono-mmap.h>
40 #include <mono/utils/mono-coop-mutex.h>
41 #include <mono/utils/mono-threads.h>
42
43 typedef struct {
44         int kind;
45         int ref_count;
46         size_t capacity;
47         char *name;
48         int fd;
49 } MmapHandle;
50
51 typedef struct {
52         void *address;
53         void *free_handle;
54         size_t length;
55 } MmapInstance;
56
57 enum {
58         BAD_CAPACITY_FOR_FILE_BACKED = 1,
59         CAPACITY_SMALLER_THAN_FILE_SIZE,
60         FILE_NOT_FOUND,
61         FILE_ALREADY_EXISTS,
62         PATH_TOO_LONG,
63         COULD_NOT_OPEN,
64         CAPACITY_MUST_BE_POSITIVE,
65         INVALID_FILE_MODE,
66         COULD_NOT_MAP_MEMORY
67 };
68
69 enum {
70         FILE_MODE_CREATE_NEW = 1,
71         FILE_MODE_CREATE = 2,
72         FILE_MODE_OPEN = 3,
73         FILE_MODE_OPEN_OR_CREATE = 4,
74         FILE_MODE_TRUNCATE = 5,
75         FILE_MODE_APPEND = 6,
76 };
77
78 enum {
79         MMAP_FILE_ACCESS_READ_WRITE = 0,
80         MMAP_FILE_ACCESS_READ = 1,
81         MMAP_FILE_ACCESS_WRITE = 2,
82         MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
83         MMAP_FILE_ACCESS_READ_EXECUTE = 4,
84         MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
85 };
86
87 #ifdef DEFFILEMODE
88 #define DEFAULT_FILEMODE DEFFILEMODE
89 #else
90 #define DEFAULT_FILEMODE 0666
91 #endif
92
93 static int mmap_init_state;
94 static MonoCoopMutex named_regions_mutex;
95 static GHashTable *named_regions;
96
97
98 static gint64
99 align_up_to_page_size (gint64 size)
100 {
101         gint64 page_size = mono_pagesize ();
102         return (size + page_size - 1) & ~(page_size - 1);
103 }
104
105 static gint64
106 align_down_to_page_size (gint64 size)
107 {
108         gint64 page_size = mono_pagesize ();
109         return size & ~(page_size - 1);
110 }
111
112 static void
113 file_mmap_init (void)
114 {
115 retry:  
116         switch (mmap_init_state) {
117         case  0:
118                 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
119                         goto retry;
120                 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
121                 mono_coop_mutex_init (&named_regions_mutex);
122
123                 mono_atomic_store_release (&mmap_init_state, 2);
124                 break;
125
126         case 1:
127                 do {
128                         mono_thread_info_sleep (1, NULL); /* Been init'd by other threads, this is very rare. */
129                 } while (mmap_init_state != 2);
130                 break;
131         case 2:
132                 break;
133         default:
134                 g_error ("Invalid init state %d", mmap_init_state);
135         }
136 }
137
138 static void
139 named_regions_lock (void)
140 {
141         file_mmap_init ();
142         mono_coop_mutex_lock (&named_regions_mutex);
143 }
144
145 static void
146 named_regions_unlock (void)
147 {
148         mono_coop_mutex_unlock (&named_regions_mutex);
149 }
150
151
152 static int
153 file_mode_to_unix (int mode)
154 {
155         switch (mode) {
156         case FILE_MODE_CREATE_NEW:
157         return O_CREAT | O_EXCL; 
158         case FILE_MODE_CREATE:
159         return O_CREAT | O_TRUNC;
160         case FILE_MODE_OPEN:
161                 return 0;
162         case FILE_MODE_OPEN_OR_CREATE:
163         return O_CREAT;
164         case FILE_MODE_TRUNCATE:
165         return O_TRUNC;
166         case FILE_MODE_APPEND:
167                 return O_APPEND;
168         default:
169                 g_error ("unknown FileMode %d", mode);
170         }
171 }
172
173 static int
174 access_mode_to_unix (int access)
175 {
176         switch (access) {
177         case MMAP_FILE_ACCESS_READ_WRITE:
178         case MMAP_FILE_ACCESS_COPY_ON_WRITE:
179         case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
180                 return O_RDWR;
181         case MMAP_FILE_ACCESS_READ:
182         case MMAP_FILE_ACCESS_READ_EXECUTE:
183                 return O_RDONLY;
184         case MMAP_FILE_ACCESS_WRITE:
185                 return O_WRONLY;
186         default:
187                 g_error ("unknown MemoryMappedFileAccess %d", access);
188         }
189 }
190
191 static int
192 acess_to_mmap_flags (int access)
193 {
194         switch (access) {
195         case MMAP_FILE_ACCESS_READ_WRITE:
196         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
197         
198         case MMAP_FILE_ACCESS_WRITE:
199         return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
200         
201         case MMAP_FILE_ACCESS_COPY_ON_WRITE:
202         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
203         
204         case MMAP_FILE_ACCESS_READ_EXECUTE:
205         return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
206         
207         case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
208         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
209         
210         case MMAP_FILE_ACCESS_READ:
211         return MONO_MMAP_READ | MONO_MMAP_SHARED;
212         default:
213                 g_error ("unknown MemoryMappedFileAccess %d", access);
214         }
215 }
216
217 /*
218 This allow us to special case zero size files that can be arbitrarily mapped.
219 */
220 static gboolean
221 is_special_zero_size_file (struct stat *buf)
222 {
223         return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0;
224 }
225
226 /*
227 XXX implement options
228 */
229 static void*
230 open_file_map (const char *c_path, int input_fd, int mode, gint64 *capacity, int access, int options, int *ioerror)
231 {
232         struct stat buf;
233         MmapHandle *handle = NULL;
234         int result, fd;
235
236         if (c_path)
237                 result = stat (c_path, &buf);
238         else
239                 result = fstat (input_fd, &buf);
240
241         if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
242                 if (result == -1) { //XXX translate errno?
243                         *ioerror = FILE_NOT_FOUND;
244                         goto done;
245                 }
246         }
247
248         if (mode == FILE_MODE_CREATE_NEW && result == 0) {
249                 *ioerror = FILE_ALREADY_EXISTS;
250                 goto done;
251         }
252
253         if (result == 0) {
254                 if (*capacity == 0) {
255                         /**
256                          * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
257                          * also makes little sense, so don't do the check if th file is one of these.
258                          */
259                         if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) {
260                                 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
261                                 goto done;
262                         }
263                         *capacity = buf.st_size;
264                 } else if (*capacity < buf.st_size) {
265                         *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
266                         goto done;
267                 }
268         } else {
269                 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
270                         *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
271                         goto done;
272                 }
273         }
274
275         if (c_path) //FIXME use io portability?
276                 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFAULT_FILEMODE);
277         else
278                 fd = dup (input_fd);
279
280         if (fd == -1) { //XXX translate errno?
281                 *ioerror = COULD_NOT_OPEN;
282                 goto done;
283         }
284
285         if (result != 0 || *capacity > buf.st_size) {
286                 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
287         }
288
289         handle = g_new0 (MmapHandle, 1);
290         handle->ref_count = 1;
291         handle->capacity = *capacity;
292         handle->fd = fd;
293
294 done:
295         return (void*)handle;
296 }
297
298 #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
299 static void*
300 open_memory_map (const char *c_mapName, int mode, gint64 *capacity, int access, int options, int *ioerror)
301 {
302         MmapHandle *handle;
303         if (*capacity <= 1) {
304                 *ioerror = CAPACITY_MUST_BE_POSITIVE;
305                 return NULL;
306         }
307
308         if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
309                 *ioerror = INVALID_FILE_MODE;
310                 return NULL;
311         }
312
313         named_regions_lock ();
314         handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
315         if (handle) {
316                 if (mode == FILE_MODE_CREATE_NEW) {
317                         *ioerror = FILE_ALREADY_EXISTS;
318                         goto done;
319                 }
320
321                 handle->ref_count++;
322                 //XXX should we ftruncate if the file is smaller than capacity?
323         } else {
324                 int fd;
325                 char *file_name;
326                 const char *tmp_dir;
327                 int unused G_GNUC_UNUSED, alloc_size;
328
329                 if (mode == FILE_MODE_OPEN) {
330                         *ioerror = FILE_NOT_FOUND;
331                         goto done;
332                 }
333                 *capacity = align_up_to_page_size (*capacity);
334
335                 tmp_dir = g_get_tmp_dir ();
336                 alloc_size = strlen (tmp_dir) + strlen (MONO_ANON_FILE_TEMPLATE) + 1;
337                 if (alloc_size > 1024) {//rather fail that stack overflow
338                         *ioerror = COULD_NOT_MAP_MEMORY;
339                         goto done;
340                 }
341                 file_name = (char *)alloca (alloc_size);
342                 strcpy (file_name, tmp_dir);
343                 strcat (file_name, MONO_ANON_FILE_TEMPLATE);
344
345                 fd = mkstemp (file_name);
346                 if (fd == -1) {
347                         *ioerror = COULD_NOT_MAP_MEMORY;
348                         goto done;
349                 }
350
351                 unlink (file_name);
352                 unused = ftruncate (fd, (off_t)*capacity);
353
354                 handle = g_new0 (MmapHandle, 1);
355                 handle->ref_count = 1;
356                 handle->capacity = *capacity;
357                 handle->fd = fd;
358                 handle->name = g_strdup (c_mapName);
359
360                 g_hash_table_insert (named_regions, handle->name, handle);
361
362         }
363
364 done:
365         named_regions_unlock ();
366
367         return handle;
368 }
369
370
371 /* This is an icall */
372 void *
373 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
374 {
375         MonoError error;
376         MmapHandle *handle = NULL;
377         g_assert (path || mapName);
378
379         if (!mapName) {
380                 char * c_path = mono_string_to_utf8_checked (path, &error);
381                 if (mono_error_set_pending_exception (&error))
382                         return NULL;
383                 handle = open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
384                 g_free (c_path);
385                 return handle;
386         }
387
388         char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
389         if (mono_error_set_pending_exception (&error))
390                 return NULL;
391
392         if (path) {
393                 named_regions_lock ();
394                 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
395                 if (handle) {
396                         *ioerror = FILE_ALREADY_EXISTS;
397                         handle = NULL;
398                 } else {
399                         char *c_path = mono_string_to_utf8_checked (path, &error);
400                         if (is_ok (&error)) {
401                                 handle = (MmapHandle *)open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
402                                 if (handle) {
403                                         handle->name = g_strdup (c_mapName);
404                                         g_hash_table_insert (named_regions, handle->name, handle);
405                                 }
406                         } else {
407                                 handle = NULL;
408                         }
409                         g_free (c_path);
410                 }
411                 named_regions_unlock ();
412         } else
413                 handle = open_memory_map (c_mapName, mode, capacity, access, options, ioerror);
414
415         g_free (c_mapName);
416         return handle;
417 }
418
419 /* this is an icall */
420 void *
421 mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
422 {
423         MonoError error;
424         MmapHandle *handle;
425         if (!mapName) {
426                 handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror);
427         } else {
428                 char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
429                 if (mono_error_set_pending_exception (&error))
430                         return NULL;
431
432                 named_regions_lock ();
433                 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
434                 if (handle) {
435                         *ioerror = FILE_ALREADY_EXISTS;
436                         handle = NULL;
437                 } else {
438                         //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
439                         handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror);
440                         handle->name = g_strdup (c_mapName);
441                         g_hash_table_insert (named_regions, handle->name, handle);
442                 }
443                 named_regions_unlock ();
444
445                 g_free (c_mapName);
446         }
447         return handle;
448 }
449
450 void
451 mono_mmap_close (void *mmap_handle)
452 {
453         MmapHandle *handle = (MmapHandle *)mmap_handle;
454
455         named_regions_lock ();
456         --handle->ref_count;
457         if (handle->ref_count == 0) {
458                 if (handle->name)
459                         g_hash_table_remove (named_regions, handle->name);
460
461                 g_free (handle->name);
462                 close (handle->fd);
463                 g_free (handle);
464         }
465         named_regions_unlock ();
466 }
467
468 void
469 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
470 {
471         MmapHandle *h = (MmapHandle *)mmap_handle;
472         int fd, flags;
473
474         fd = h->fd;
475         flags = fcntl (fd, F_GETFD, 0);
476         if (inheritability)
477                 flags &= ~FD_CLOEXEC;
478         else
479                 flags |= FD_CLOEXEC;
480         fcntl (fd, F_SETFD, flags);     
481 }
482
483 void
484 mono_mmap_flush (void *mmap_handle)
485 {
486         MmapInstance *h = (MmapInstance *)mmap_handle;
487
488         if (h)
489                 msync (h->address, h->length, MS_SYNC);
490 }
491
492 int
493 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
494 {
495         gint64 mmap_offset = 0;
496         MmapHandle *fh = (MmapHandle *)handle;
497         MmapInstance res = { 0 };
498         size_t eff_size = *size;
499         struct stat buf = { 0 };
500         fstat (fh->fd, &buf); //FIXME error handling
501
502         if (offset > buf.st_size || ((eff_size + offset) > buf.st_size && !is_special_zero_size_file (&buf)))
503                 goto error;
504         /**
505           * We use the file size if one of the following conditions is true:
506           *  -input size is zero
507           *  -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
508           */
509         if (eff_size == 0)
510                 eff_size = align_up_to_page_size (buf.st_size) - offset;
511         *size = eff_size;
512
513         mmap_offset = align_down_to_page_size (offset);
514         eff_size += (offset - mmap_offset);
515         //FIXME translate some interesting errno values
516         res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
517         res.length = eff_size;
518
519         if (res.address) {
520                 *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
521                 *base_address = (char*)res.address + (offset - mmap_offset);
522                 return 0;
523         }
524
525 error:
526         *mmap_handle = NULL;
527         *base_address = NULL;
528         return COULD_NOT_MAP_MEMORY;
529 }
530
531 gboolean
532 mono_mmap_unmap (void *mmap_handle)
533 {
534         int res = 0;
535         MmapInstance *h = (MmapInstance *)mmap_handle;
536
537         res = mono_file_unmap (h->address, h->free_handle);
538
539         g_free (h);
540         return res == 0;
541 }
542
543 #endif