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