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