[runtime] Define DEFFILEMODE to 0666 if not defined.
[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 #ifndef DEFFILEMODE
101 #define DEFFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
102 #endif
103
104 static int mmap_init_state;
105 static mono_mutex_t named_regions_mutex;
106 static GHashTable *named_regions;
107
108
109 static gint64
110 align_up_to_page_size (gint64 size)
111 {
112         gint64 page_size = mono_pagesize ();
113         return (size + page_size - 1) & ~(page_size - 1);
114 }
115
116 static gint64
117 align_down_to_page_size (gint64 size)
118 {
119         gint64 page_size = mono_pagesize ();
120         return size & ~(page_size - 1);
121 }
122
123 static void
124 file_mmap_init (void)
125 {
126 retry:  
127         switch (mmap_init_state) {
128         case  0:
129                 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
130                         goto retry;
131                 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
132                 mono_mutex_init (&named_regions_mutex);
133
134                 mono_atomic_store_release (&mmap_init_state, 2);
135                 break;
136
137         case 1:
138                 do {
139                         g_usleep (1000); /* Been init'd by other threads, this is very rare. */
140                 } while (mmap_init_state != 2);
141                 break;
142         case 2:
143                 break;
144         default:
145                 g_error ("Invalid init state %d", mmap_init_state);
146         }
147 }
148
149 static void
150 named_regions_lock (void)
151 {
152         file_mmap_init ();
153         mono_mutex_lock (&named_regions_mutex);
154 }
155
156 static void
157 named_regions_unlock (void)
158 {
159         mono_mutex_unlock (&named_regions_mutex);       
160 }
161
162
163 static int
164 file_mode_to_unix (int mode)
165 {
166         switch (mode) {
167         case FILE_MODE_CREATE_NEW:
168         return O_CREAT | O_EXCL; 
169         case FILE_MODE_CREATE:
170         return O_CREAT | O_TRUNC;
171         case FILE_MODE_OPEN:
172                 return 0;
173         case FILE_MODE_OPEN_OR_CREATE:
174         return O_CREAT;
175         case FILE_MODE_TRUNCATE:
176         return O_TRUNC;
177         case FILE_MODE_APPEND:
178                 return O_APPEND;
179         default:
180                 g_error ("unknown FileMode %d", mode);
181         }
182 }
183
184 static int
185 access_mode_to_unix (int access)
186 {
187         switch (access) {
188         case MMAP_FILE_ACCESS_READ_WRITE:
189         case MMAP_FILE_ACCESS_COPY_ON_WRITE:
190         case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
191                 return O_RDWR;
192         case MMAP_FILE_ACCESS_READ:
193         case MMAP_FILE_ACCESS_READ_EXECUTE:
194                 return O_RDONLY;
195         case MMAP_FILE_ACCESS_WRITE:
196                 return O_WRONLY;
197         default:
198                 g_error ("unknown MemoryMappedFileAccess %d", access);
199         }
200 }
201
202 static int
203 acess_to_mmap_flags (int access)
204 {
205         switch (access) {
206         case MMAP_FILE_ACCESS_READ_WRITE:
207         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
208         
209         case MMAP_FILE_ACCESS_WRITE:
210         return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
211         
212         case MMAP_FILE_ACCESS_COPY_ON_WRITE:
213         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
214         
215         case MMAP_FILE_ACCESS_READ_EXECUTE:
216         return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
217         
218         case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
219         return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
220         
221         case MMAP_FILE_ACCESS_READ:
222         return MONO_MMAP_READ | MONO_MMAP_SHARED;
223         default:
224                 g_error ("unknown MemoryMappedFileAccess %d", access);
225         }
226 }
227
228 /*
229 XXX implement options
230 */
231 static void*
232 open_file_map (MonoString *path, int input_fd, int mode, gint64 *capacity, int access, int options, int *error)
233 {
234         struct stat buf;
235         char *c_path = path ? mono_string_to_utf8 (path) : NULL;
236         FileHandle *handle = NULL;
237         int result, fd;
238
239         if (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                         *error = FILE_NOT_FOUND;
247                         goto done;
248                 }
249         }
250
251         if (mode == FILE_MODE_CREATE_NEW && result == 0) {
252                 *error = 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 && (buf.st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) == 0) {
263                                 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
264                                 goto done;
265                         }
266                         *capacity = buf.st_size;
267                 } else if (*capacity < buf.st_size) {
268                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
269                         goto done;
270                 }
271         } else {
272                 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
273                         *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
274                         goto done;
275                 }
276         }
277
278         if (path) //FIXME use io portability?
279                 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFFILEMODE);
280         else
281                 fd = dup (input_fd);
282
283         if (fd == -1) { //XXX translate errno?
284                 *error = COULD_NOT_OPEN;
285                 goto done;
286         }
287
288         *capacity = align_up_to_page_size ((size_t)*capacity);
289
290         if (*capacity > buf.st_size) {
291                 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
292         }
293
294         handle = g_new0 (FileHandle, 1);
295         handle->handle.kind = MMAP_KIND_FILE;
296         handle->handle.ref_count = 1;
297         handle->handle.capacity = *capacity;
298         handle->fd = fd;
299
300 done:
301         g_free (c_path);
302         return (void*)handle;
303 }
304
305 static void*
306 open_memory_map (MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
307 {
308         char *c_mapName;
309         MemoryHandle *handle;
310         if (*capacity <= 1) {
311                 *error = CAPACITY_MUST_BE_POSITIVE;
312                 return NULL;
313         }
314
315         if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
316                 *error = INVALID_FILE_MODE;
317                 return NULL;
318         }
319
320         c_mapName = mono_string_to_utf8 (mapName);
321
322         named_regions_lock ();
323         handle = (MemoryHandle*)g_hash_table_lookup (named_regions, c_mapName);
324         if (handle) {
325                 if (mode == FILE_MODE_CREATE_NEW)
326                         *error = FILE_ALREADY_EXISTS;
327
328                 handle->handle.ref_count++;
329                 //XXX should we ftruncate if the file is smaller than capacity?
330         } else {
331                 if (mode == FILE_MODE_OPEN)
332                         *error = FILE_NOT_FOUND;
333
334                 *capacity = align_up_to_page_size (*capacity);
335
336                 handle = g_new0 (MemoryHandle, 1);
337                 handle->handle.kind = MMAP_KIND_MEMORY;
338                 handle->handle.ref_count = 1;
339                 handle->handle.capacity = *capacity;
340                 handle->handle.name = g_strdup (c_mapName);
341
342                 //FIXME compute RWX from access
343                 handle->address = mono_valloc (NULL, (size_t)*capacity, MONO_MMAP_READ | MONO_MMAP_WRITE | MONO_MMAP_PRIVATE | MONO_MMAP_ANON);
344                 handle->length = (size_t)*capacity;
345                 g_hash_table_insert (named_regions, handle->handle.name, handle);
346         }
347
348         named_regions_unlock ();
349
350         g_free (c_mapName);
351         return handle;
352 }
353
354
355 void *
356 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
357 {
358         g_assert (path || mapName);
359
360         if (!mapName)
361                 return open_file_map (path, -1, mode, capacity, access, options, error);
362
363         if (path) {
364                 FileHandle *file_handle;
365                 char *c_mapName = mono_string_to_utf8 (mapName);
366
367                 named_regions_lock ();
368                 file_handle = (FileHandle*)g_hash_table_lookup (named_regions, c_mapName);
369                 if (file_handle) {
370                         *error = FILE_ALREADY_EXISTS;
371                         file_handle = NULL;
372                 } else {
373                         file_handle = open_file_map (path, -1, mode, capacity, access, options, error);
374                         if (file_handle) {
375                                 file_handle->handle.name = g_strdup (c_mapName);
376                                 g_hash_table_insert (named_regions, file_handle->handle.name, file_handle);
377                         }
378                 }
379                 named_regions_unlock ();
380
381                 g_free (c_mapName);
382                 return file_handle;
383         }
384
385         return open_memory_map (mapName, mode, capacity, access, options, error);
386 }
387
388 void *
389 mono_mmap_open_handle (void *handle, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
390 {
391         FileHandle *file_handle;
392         char *c_mapName = mono_string_to_utf8 (mapName);
393
394         named_regions_lock ();
395         file_handle = (FileHandle*)g_hash_table_lookup (named_regions, c_mapName);
396         if (file_handle) {
397                 *error = FILE_ALREADY_EXISTS;
398                 file_handle = NULL;
399         } else {
400                 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
401                 file_handle = open_file_map (NULL, GPOINTER_TO_INT (handle), FILE_MODE_OPEN, capacity, access, options, error);
402                 file_handle->handle.name = g_strdup (c_mapName);
403                 g_hash_table_insert (named_regions, file_handle->handle.name, file_handle);
404         }
405         named_regions_unlock ();
406
407         g_free (c_mapName);
408         return file_handle;
409 }
410
411 void
412 mono_mmap_close (void *mmap_handle)
413 {
414         Handle *handle = mmap_handle;
415
416         named_regions_lock ();
417         --handle->ref_count;
418         if (handle->ref_count == 0) {
419                 if (handle->name)
420                         g_hash_table_remove (named_regions, handle->name);
421                 
422                 g_free (handle->name);
423                 if (handle->kind == MMAP_KIND_MEMORY)
424                         mono_vfree (((MemoryHandle*)handle)->address, ((MemoryHandle*)handle)->length);
425                 else
426                         close (((FileHandle*)handle)->fd);
427                 g_free (handle);
428         }
429         named_regions_unlock ();
430 }
431
432 void
433 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
434 {
435         FileHandle *h = mmap_handle;
436         int fd, flags;
437
438         if (h->handle.kind != MMAP_KIND_FILE)
439                 return;
440
441         fd = h->fd;
442         flags = fcntl (fd, F_GETFD, 0);
443         if (inheritability)
444                 flags &= ~FD_CLOEXEC;
445         else
446                 flags |= FD_CLOEXEC;
447         fcntl (fd, F_SETFD, flags);     
448 }
449
450 void
451 mono_mmap_flush (void *mmap_handle)
452 {
453         MmapHandle *h = mmap_handle;
454
455         if (h)
456                 msync (h->address, h->length, MS_SYNC);
457 }
458
459 int
460 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
461 {
462         gint64 mmap_offset = 0;
463         Handle *h = handle;
464         MmapHandle res = { 0 };
465         if (h->kind == MMAP_KIND_FILE) {
466                 FileHandle *fh = (FileHandle*)h;
467                 size_t eff_size = *size;
468                 struct stat buf = { 0 };
469                 fstat (fh->fd, &buf); //FIXME error handling
470
471                 if (eff_size == 0 || eff_size > buf.st_size)
472                         eff_size = buf.st_size;
473                 *size = eff_size;
474
475                 mmap_offset = align_down_to_page_size (offset);
476                 eff_size += (offset - mmap_offset);
477                 //FIXME translate some interesting errno values
478                 res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
479                 res.length = eff_size;
480                 res.kind = MMAP_KIND_FILE;
481
482         } else {
483                 MemoryHandle *mh = (MemoryHandle*)h;
484                 size_t eff_size = *size;
485
486                 if (!eff_size)
487                         eff_size = *size = mh->length;
488                 mmap_offset = (size_t)offset;
489                 res.address = (char*)mh->address + offset;
490                 res.length = (size_t)*size;
491                 res.kind = MMAP_KIND_MEMORY;
492         }
493
494         if (res.address) {
495                 *mmap_handle = g_memdup (&res, sizeof (MmapHandle));
496                 *base_address = (char*)res.address + (offset - mmap_offset);
497                 return 0;
498         }
499
500         *mmap_handle = NULL;
501         *base_address = NULL;
502         return COULD_NOT_MAP_MEMORY;
503 }
504
505 gboolean
506 mono_mmap_unmap (void *mmap_handle)
507 {
508         int res = 0;
509         MmapHandle *h = mmap_handle;
510
511         if (h->kind == MMAP_KIND_FILE)
512                 res = mono_file_unmap (h->address, h->free_handle);
513
514         g_free (h);
515         return res == 0;
516 }
517
518 #endif