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