4b7fa7a8bfe50626cd1ad6a63b8fd7f76314085e
[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 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 #ifndef DEFFILEMODE
85 #define DEFFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
86 #endif
87
88 static int mmap_init_state;
89 static mono_mutex_t named_regions_mutex;
90 static GHashTable *named_regions;
91
92
93 static gint64
94 align_up_to_page_size (gint64 size)
95 {
96         gint64 page_size = mono_pagesize ();
97         return (size + page_size - 1) & ~(page_size - 1);
98 }
99
100 static gint64
101 align_down_to_page_size (gint64 size)
102 {
103         gint64 page_size = mono_pagesize ();
104         return size & ~(page_size - 1);
105 }
106
107 static void
108 file_mmap_init (void)
109 {
110 retry:  
111         switch (mmap_init_state) {
112         case  0:
113                 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
114                         goto retry;
115                 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
116                 mono_mutex_init (&named_regions_mutex);
117
118                 mono_atomic_store_release (&mmap_init_state, 2);
119                 break;
120
121         case 1:
122                 do {
123                         g_usleep (1000); /* Been init'd by other threads, this is very rare. */
124                 } while (mmap_init_state != 2);
125                 break;
126         case 2:
127                 break;
128         default:
129                 g_error ("Invalid init state %d", mmap_init_state);
130         }
131 }
132
133 static void
134 named_regions_lock (void)
135 {
136         file_mmap_init ();
137         mono_mutex_lock (&named_regions_mutex);
138 }
139
140 static void
141 named_regions_unlock (void)
142 {
143         mono_mutex_unlock (&named_regions_mutex);       
144 }
145
146
147 static int
148 file_mode_to_unix (int mode)
149 {
150         switch (mode) {
151         case FILE_MODE_CREATE_NEW:
152         return O_CREAT | O_EXCL; 
153         case FILE_MODE_CREATE:
154         return O_CREAT | O_TRUNC;
155         case FILE_MODE_OPEN:
156                 return 0;
157         case FILE_MODE_OPEN_OR_CREATE:
158         return O_CREAT;
159         case FILE_MODE_TRUNCATE:
160         return O_TRUNC;
161         case FILE_MODE_APPEND:
162                 return O_APPEND;
163         default:
164                 g_error ("unknown FileMode %d", mode);
165         }
166 }
167
168 static int
169 access_mode_to_unix (int access)
170 {
171         switch (access) {
172         case MMAP_FILE_ACCESS_READ_WRITE:
173         case MMAP_FILE_ACCESS_COPY_ON_WRITE:
174         case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
175                 return O_RDWR;
176         case MMAP_FILE_ACCESS_READ:
177         case MMAP_FILE_ACCESS_READ_EXECUTE:
178                 return O_RDONLY;
179         case MMAP_FILE_ACCESS_WRITE:
180                 return O_WRONLY;
181         default:
182                 g_error ("unknown MemoryMappedFileAccess %d", access);
183         }
184 }
185
186 static int
187 acess_to_mmap_flags (int access)
188 {
189         switch (access) {
190         case MMAP_FILE_ACCESS_READ_WRITE:
191         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
192         
193         case MMAP_FILE_ACCESS_WRITE:
194         return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
195         
196         case MMAP_FILE_ACCESS_COPY_ON_WRITE:
197         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
198         
199         case MMAP_FILE_ACCESS_READ_EXECUTE:
200         return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
201         
202         case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
203         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
204         
205         case MMAP_FILE_ACCESS_READ:
206         return MONO_MMAP_READ | MONO_MMAP_SHARED;
207         default:
208                 g_error ("unknown MemoryMappedFileAccess %d", access);
209         }
210 }
211
212 /*
213 This allow us to special case zero size files that can be arbitrarily mapped.
214 */
215 static gboolean
216 is_special_zero_size_file (struct stat *buf)
217 {
218         return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0;
219 }
220
221 /*
222 XXX implement options
223 */
224 static void*
225 open_file_map (MonoString *path, int input_fd, int mode, gint64 *capacity, int access, int options, int *error)
226 {
227         struct stat buf;
228         char *c_path = path ? mono_string_to_utf8 (path) : NULL;
229         MmapHandle *handle = NULL;
230         int result, fd;
231
232         if (path)
233                 result = stat (c_path, &buf);
234         else
235                 result = fstat (input_fd, &buf);
236
237         if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
238                 if (result == -1) { //XXX translate errno?
239                         *error = FILE_NOT_FOUND;
240                         goto done;
241                 }
242         }
243
244         if (mode == FILE_MODE_CREATE_NEW && result == 0) {
245                 *error = FILE_ALREADY_EXISTS;
246                 goto done;
247         }
248
249         if (result == 0) {
250                 if (*capacity == 0) {
251                         /**
252                          * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
253                          * also makes little sense, so don't do the check if th file is one of these.
254                          */
255                         if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) {
256                                 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
257                                 goto done;
258                         }
259                         *capacity = buf.st_size;
260                 } else if (*capacity < buf.st_size) {
261                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
262                         goto done;
263                 }
264         } else {
265                 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
266                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
267                         goto done;
268                 }
269         }
270
271         if (path) //FIXME use io portability?
272                 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFFILEMODE);
273         else
274                 fd = dup (input_fd);
275
276         if (fd == -1) { //XXX translate errno?
277                 *error = COULD_NOT_OPEN;
278                 goto done;
279         }
280
281         *capacity = align_up_to_page_size ((size_t)*capacity);
282
283         if (*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 "/tmp/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 [sizeof (MONO_ANON_FILE_TEMPLATE) + 1];
328
329                 if (mode == FILE_MODE_OPEN) {
330                         *error = FILE_NOT_FOUND;
331                         goto done;
332                 }
333                 *capacity = align_up_to_page_size (*capacity);
334
335                 strcpy (file_name, MONO_ANON_FILE_TEMPLATE);
336                 fd = mkstemp (file_name);
337                 if (fd == -1) {
338                         *error = COULD_NOT_MAP_MEMORY;
339                         goto done;
340                 }
341
342                 unlink (file_name);
343                 ftruncate (fd, (off_t)*capacity);
344
345                 handle = g_new0 (MmapHandle, 1);
346                 handle->ref_count = 1;
347                 handle->capacity = *capacity;
348                 handle->fd = fd;
349                 handle->name = g_strdup (c_mapName);
350
351                 g_hash_table_insert (named_regions, handle->name, handle);
352
353         }
354
355 done:
356         named_regions_unlock ();
357
358         g_free (c_mapName);
359         return handle;
360 }
361
362
363 void *
364 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
365 {
366         g_assert (path || mapName);
367
368         if (!mapName)
369                 return open_file_map (path, -1, mode, capacity, access, options, error);
370
371         if (path) {
372                 MmapHandle *handle;
373                 char *c_mapName = mono_string_to_utf8 (mapName);
374
375                 named_regions_lock ();
376                 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
377                 if (handle) {
378                         *error = FILE_ALREADY_EXISTS;
379                         handle = NULL;
380                 } else {
381                         handle = open_file_map (path, -1, mode, capacity, access, options, error);
382                         if (handle) {
383                                 handle->name = g_strdup (c_mapName);
384                                 g_hash_table_insert (named_regions, handle->name, handle);
385                         }
386                 }
387                 named_regions_unlock ();
388
389                 g_free (c_mapName);
390                 return handle;
391         }
392
393         return open_memory_map (mapName, mode, capacity, access, options, error);
394 }
395
396 void *
397 mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
398 {
399         MmapHandle *handle;
400         char *c_mapName = mono_string_to_utf8 (mapName);
401
402         named_regions_lock ();
403         handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
404         if (handle) {
405                 *error = FILE_ALREADY_EXISTS;
406                 handle = NULL;
407         } else {
408                 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
409                 handle = open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, error);
410                 handle->name = g_strdup (c_mapName);
411                 g_hash_table_insert (named_regions, handle->name, handle);
412         }
413         named_regions_unlock ();
414
415         g_free (c_mapName);
416         return handle;
417 }
418
419 void
420 mono_mmap_close (void *mmap_handle)
421 {
422         MmapHandle *handle = mmap_handle;
423
424         named_regions_lock ();
425         --handle->ref_count;
426         if (handle->ref_count == 0) {
427                 if (handle->name)
428                         g_hash_table_remove (named_regions, handle->name);
429
430                 g_free (handle->name);
431                 close (handle->fd);
432                 g_free (handle);
433         }
434         named_regions_unlock ();
435 }
436
437 void
438 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
439 {
440         MmapHandle *h = mmap_handle;
441         int fd, flags;
442
443         fd = h->fd;
444         flags = fcntl (fd, F_GETFD, 0);
445         if (inheritability)
446                 flags &= ~FD_CLOEXEC;
447         else
448                 flags |= FD_CLOEXEC;
449         fcntl (fd, F_SETFD, flags);     
450 }
451
452 void
453 mono_mmap_flush (void *mmap_handle)
454 {
455         MmapInstance *h = mmap_handle;
456
457         if (h)
458                 msync (h->address, h->length, MS_SYNC);
459 }
460
461 int
462 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
463 {
464         gint64 mmap_offset = 0;
465         MmapHandle *fh = handle;
466         MmapInstance res = { 0 };
467         size_t eff_size = *size;
468         struct stat buf = { 0 };
469         fstat (fh->fd, &buf); //FIXME error handling
470
471         /**
472           * We use the file size if one of the following conditions is true:
473           *  -input size is zero
474           *  -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
475           */
476         if (eff_size == 0 || (eff_size > buf.st_size && !is_special_zero_size_file (&buf)))
477                 eff_size = buf.st_size;
478         *size = eff_size;
479
480         mmap_offset = align_down_to_page_size (offset);
481         eff_size += (offset - mmap_offset);
482         //FIXME translate some interesting errno values
483         res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
484         res.length = eff_size;
485
486         if (res.address) {
487                 *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
488                 *base_address = (char*)res.address + (offset - mmap_offset);
489                 return 0;
490         }
491
492         *mmap_handle = NULL;
493         *base_address = NULL;
494         return COULD_NOT_MAP_MEMORY;
495 }
496
497 gboolean
498 mono_mmap_unmap (void *mmap_handle)
499 {
500         int res = 0;
501         MmapInstance *h = mmap_handle;
502
503         res = mono_file_unmap (h->address, h->free_handle);
504
505         g_free (h);
506         return res == 0;
507 }
508
509 #endif