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