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