[sre] Wrap mono_image_create_token with HANDLE_FUNCTION_{ENTER,RETURN}
[mono-project.git] / mono / metadata / file-mmap-posix.c
blob3c971a091b5f907ea864a239e9e118b5f121e162
1 /**
2 * \file
3 * File mmap internal calls
5 * Author:
6 * Rodrigo Kumpera
8 * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
9 * Licensed under the MIT license. See LICENSE file in the project root for full license information.
12 #include <config.h>
14 #ifndef HOST_WIN32
16 #include <glib.h>
17 #include <string.h>
18 #include <errno.h>
19 #ifdef HAVE_UNISTD_H
20 #include <unistd.h>
21 #endif
22 #ifdef HAVE_SYS_STAT_H
23 #include <sys/stat.h>
24 #endif
25 #ifdef HAVE_SYS_TYPES_H
26 #include <sys/types.h>
27 #endif
28 #if HAVE_SYS_MMAN_H
29 #include <sys/mman.h>
30 #endif
32 #include <fcntl.h>
35 #include <mono/metadata/object.h>
36 #include <mono/metadata/w32file.h>
37 #include <mono/metadata/file-mmap.h>
38 #include <mono/utils/atomic.h>
39 #include <mono/utils/mono-memory-model.h>
40 #include <mono/utils/mono-mmap.h>
41 #include <mono/utils/mono-coop-mutex.h>
42 #include <mono/utils/mono-threads.h>
44 typedef struct {
45 int kind;
46 int ref_count;
47 size_t capacity;
48 char *name;
49 int fd;
50 } MmapHandle;
52 typedef struct {
53 void *address;
54 void *free_handle;
55 size_t length;
56 } MmapInstance;
58 enum {
59 BAD_CAPACITY_FOR_FILE_BACKED = 1,
60 CAPACITY_SMALLER_THAN_FILE_SIZE,
61 FILE_NOT_FOUND,
62 FILE_ALREADY_EXISTS,
63 PATH_TOO_LONG,
64 COULD_NOT_OPEN,
65 CAPACITY_MUST_BE_POSITIVE,
66 INVALID_FILE_MODE,
67 COULD_NOT_MAP_MEMORY,
68 ACCESS_DENIED,
69 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
72 enum {
73 FILE_MODE_CREATE_NEW = 1,
74 FILE_MODE_CREATE = 2,
75 FILE_MODE_OPEN = 3,
76 FILE_MODE_OPEN_OR_CREATE = 4,
77 FILE_MODE_TRUNCATE = 5,
78 FILE_MODE_APPEND = 6,
81 enum {
82 MMAP_FILE_ACCESS_READ_WRITE = 0,
83 MMAP_FILE_ACCESS_READ = 1,
84 MMAP_FILE_ACCESS_WRITE = 2,
85 MMAP_FILE_ACCESS_COPY_ON_WRITE = 3,
86 MMAP_FILE_ACCESS_READ_EXECUTE = 4,
87 MMAP_FILE_ACCESS_READ_WRITE_EXECUTE = 5,
90 #ifdef DEFFILEMODE
91 #define DEFAULT_FILEMODE DEFFILEMODE
92 #else
93 #define DEFAULT_FILEMODE 0666
94 #endif
96 static int mmap_init_state;
97 static MonoCoopMutex named_regions_mutex;
98 static GHashTable *named_regions;
101 static gint64
102 align_up_to_page_size (gint64 size)
104 gint64 page_size = mono_pagesize ();
105 return (size + page_size - 1) & ~(page_size - 1);
108 static gint64
109 align_down_to_page_size (gint64 size)
111 gint64 page_size = mono_pagesize ();
112 return size & ~(page_size - 1);
115 static void
116 file_mmap_init (void)
118 retry:
119 switch (mmap_init_state) {
120 case 0:
121 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
122 goto retry;
123 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
124 mono_coop_mutex_init (&named_regions_mutex);
126 mono_atomic_store_release (&mmap_init_state, 2);
127 break;
129 case 1:
130 do {
131 mono_thread_info_sleep (1, NULL); /* Been init'd by other threads, this is very rare. */
132 } while (mmap_init_state != 2);
133 break;
134 case 2:
135 break;
136 default:
137 g_error ("Invalid init state %d", mmap_init_state);
141 static void
142 named_regions_lock (void)
144 file_mmap_init ();
145 mono_coop_mutex_lock (&named_regions_mutex);
148 static void
149 named_regions_unlock (void)
151 mono_coop_mutex_unlock (&named_regions_mutex);
155 static int
156 file_mode_to_unix (int mode)
158 switch (mode) {
159 case FILE_MODE_CREATE_NEW:
160 return O_CREAT | O_EXCL;
161 case FILE_MODE_CREATE:
162 return O_CREAT | O_TRUNC;
163 case FILE_MODE_OPEN:
164 return 0;
165 case FILE_MODE_OPEN_OR_CREATE:
166 return O_CREAT;
167 case FILE_MODE_TRUNCATE:
168 return O_TRUNC;
169 case FILE_MODE_APPEND:
170 return O_APPEND;
171 default:
172 g_error ("unknown FileMode %d", mode);
176 static int
177 access_mode_to_unix (int access)
179 switch (access) {
180 case MMAP_FILE_ACCESS_READ_WRITE:
181 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
182 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
183 return O_RDWR;
184 case MMAP_FILE_ACCESS_READ:
185 case MMAP_FILE_ACCESS_READ_EXECUTE:
186 return O_RDONLY;
187 case MMAP_FILE_ACCESS_WRITE:
188 return O_WRONLY;
189 default:
190 g_error ("unknown MemoryMappedFileAccess %d", access);
194 static int
195 acess_to_mmap_flags (int access)
197 switch (access) {
198 case MMAP_FILE_ACCESS_READ_WRITE:
199 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
201 case MMAP_FILE_ACCESS_WRITE:
202 return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
204 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
205 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
207 case MMAP_FILE_ACCESS_READ_EXECUTE:
208 return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
210 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
211 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
213 case MMAP_FILE_ACCESS_READ:
214 return MONO_MMAP_READ | MONO_MMAP_SHARED;
215 default:
216 g_error ("unknown MemoryMappedFileAccess %d", access);
221 This allow us to special case zero size files that can be arbitrarily mapped.
223 static gboolean
224 is_special_zero_size_file (struct stat *buf)
226 return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0;
230 XXX implement options
232 static void*
233 open_file_map (const char *c_path, int input_fd, int mode, gint64 *capacity, int access, int options, int *ioerror)
235 struct stat buf;
236 MmapHandle *handle = NULL;
237 int result, fd;
239 if (c_path)
240 result = stat (c_path, &buf);
241 else
242 result = fstat (input_fd, &buf);
244 if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
245 if (result == -1) { //XXX translate errno?
246 *ioerror = FILE_NOT_FOUND;
247 goto done;
251 if (mode == FILE_MODE_CREATE_NEW && result == 0) {
252 *ioerror = FILE_ALREADY_EXISTS;
253 goto done;
256 if (result == 0) {
257 if (*capacity == 0) {
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.
262 if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) {
263 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
264 goto done;
266 *capacity = buf.st_size;
267 } else if (*capacity < buf.st_size) {
268 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
269 goto done;
271 } else {
272 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
273 *ioerror = CAPACITY_SMALLER_THAN_FILE_SIZE;
274 goto done;
278 if (c_path) //FIXME use io portability?
279 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFAULT_FILEMODE);
280 else
281 fd = dup (input_fd);
283 if (fd == -1) { //XXX translate errno?
284 *ioerror = COULD_NOT_OPEN;
285 goto done;
288 if (result != 0 || *capacity > buf.st_size) {
289 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
292 handle = g_new0 (MmapHandle, 1);
293 handle->ref_count = 1;
294 handle->capacity = *capacity;
295 handle->fd = fd;
297 done:
298 return (void*)handle;
301 #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
302 static void*
303 open_memory_map (const char *c_mapName, int mode, gint64 *capacity, int access, int options, int *ioerror)
305 MmapHandle *handle;
306 if (*capacity <= 0 && mode != FILE_MODE_OPEN) {
307 *ioerror = CAPACITY_MUST_BE_POSITIVE;
308 return NULL;
310 #if SIZEOF_VOID_P == 4
311 if (*capacity > UINT32_MAX) {
312 *ioerror = CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE;
313 return NULL;
315 #endif
317 if (!(mode == FILE_MODE_CREATE_NEW || mode == FILE_MODE_OPEN_OR_CREATE || mode == FILE_MODE_OPEN)) {
318 *ioerror = INVALID_FILE_MODE;
319 return NULL;
322 named_regions_lock ();
323 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
324 if (handle) {
325 if (mode == FILE_MODE_CREATE_NEW) {
326 *ioerror = FILE_ALREADY_EXISTS;
327 goto done;
330 handle->ref_count++;
331 //XXX should we ftruncate if the file is smaller than capacity?
332 } else {
333 int fd;
334 char *file_name;
335 const char *tmp_dir;
336 int unused G_GNUC_UNUSED, alloc_size;
338 if (mode == FILE_MODE_OPEN) {
339 *ioerror = FILE_NOT_FOUND;
340 goto done;
342 *capacity = align_up_to_page_size (*capacity);
344 tmp_dir = g_get_tmp_dir ();
345 alloc_size = strlen (tmp_dir) + strlen (MONO_ANON_FILE_TEMPLATE) + 1;
346 if (alloc_size > 1024) {//rather fail that stack overflow
347 *ioerror = COULD_NOT_MAP_MEMORY;
348 goto done;
350 file_name = (char *)alloca (alloc_size);
351 strcpy (file_name, tmp_dir);
352 strcat (file_name, MONO_ANON_FILE_TEMPLATE);
354 fd = mkstemp (file_name);
355 if (fd == -1) {
356 *ioerror = COULD_NOT_MAP_MEMORY;
357 goto done;
360 unlink (file_name);
361 unused = ftruncate (fd, (off_t)*capacity);
363 handle = g_new0 (MmapHandle, 1);
364 handle->ref_count = 1;
365 handle->capacity = *capacity;
366 handle->fd = fd;
367 handle->name = g_strdup (c_mapName);
369 g_hash_table_insert (named_regions, handle->name, handle);
373 done:
374 named_regions_unlock ();
376 return handle;
380 /* This is an icall */
381 void *
382 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
384 MonoError error;
385 MmapHandle *handle = NULL;
386 g_assert (path || mapName);
388 if (!mapName) {
389 char * c_path = mono_string_to_utf8_checked (path, &error);
390 if (mono_error_set_pending_exception (&error))
391 return NULL;
392 handle = open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
393 g_free (c_path);
394 return handle;
397 char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
398 if (mono_error_set_pending_exception (&error))
399 return NULL;
401 if (path) {
402 named_regions_lock ();
403 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
404 if (handle) {
405 *ioerror = FILE_ALREADY_EXISTS;
406 handle = NULL;
407 } else {
408 char *c_path = mono_string_to_utf8_checked (path, &error);
409 if (is_ok (&error)) {
410 handle = (MmapHandle *)open_file_map (c_path, -1, mode, capacity, access, options, ioerror);
411 if (handle) {
412 handle->name = g_strdup (c_mapName);
413 g_hash_table_insert (named_regions, handle->name, handle);
415 } else {
416 handle = NULL;
418 g_free (c_path);
420 named_regions_unlock ();
421 } else
422 handle = open_memory_map (c_mapName, mode, capacity, access, options, ioerror);
424 g_free (c_mapName);
425 return handle;
428 /* this is an icall */
429 void *
430 mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *ioerror)
432 MonoError error;
433 MmapHandle *handle;
434 if (!mapName) {
435 handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror);
436 } else {
437 char *c_mapName = mono_string_to_utf8_checked (mapName, &error);
438 if (mono_error_set_pending_exception (&error))
439 return NULL;
441 named_regions_lock ();
442 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
443 if (handle) {
444 *ioerror = FILE_ALREADY_EXISTS;
445 handle = NULL;
446 } else {
447 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
448 handle = (MmapHandle *)open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, ioerror);
449 handle->name = g_strdup (c_mapName);
450 g_hash_table_insert (named_regions, handle->name, handle);
452 named_regions_unlock ();
454 g_free (c_mapName);
456 return handle;
459 void
460 mono_mmap_close (void *mmap_handle)
462 MmapHandle *handle = (MmapHandle *)mmap_handle;
464 named_regions_lock ();
465 --handle->ref_count;
466 if (handle->ref_count == 0) {
467 if (handle->name)
468 g_hash_table_remove (named_regions, handle->name);
470 g_free (handle->name);
471 close (handle->fd);
472 g_free (handle);
474 named_regions_unlock ();
477 void
478 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
480 MmapHandle *h = (MmapHandle *)mmap_handle;
481 int fd, flags;
483 fd = h->fd;
484 flags = fcntl (fd, F_GETFD, 0);
485 if (inheritability)
486 flags &= ~FD_CLOEXEC;
487 else
488 flags |= FD_CLOEXEC;
489 fcntl (fd, F_SETFD, flags);
492 void
493 mono_mmap_flush (void *mmap_handle)
495 MmapInstance *h = (MmapInstance *)mmap_handle;
497 if (h)
498 msync (h->address, h->length, MS_SYNC);
502 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
504 gint64 mmap_offset = 0;
505 MmapHandle *fh = (MmapHandle *)handle;
506 MmapInstance res = { 0 };
507 size_t eff_size = *size;
508 struct stat buf = { 0 };
509 fstat (fh->fd, &buf); //FIXME error handling
511 *mmap_handle = NULL;
512 *base_address = NULL;
514 if (offset > buf.st_size || ((eff_size + offset) > buf.st_size && !is_special_zero_size_file (&buf)))
515 return ACCESS_DENIED;
517 * We use the file size if one of the following conditions is true:
518 * -input size is zero
519 * -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
521 if (eff_size == 0)
522 eff_size = align_up_to_page_size (buf.st_size) - offset;
523 *size = eff_size;
525 mmap_offset = align_down_to_page_size (offset);
526 eff_size += (offset - mmap_offset);
527 //FIXME translate some interesting errno values
528 res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
529 res.length = eff_size;
531 if (res.address) {
532 *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
533 *base_address = (char*)res.address + (offset - mmap_offset);
534 return 0;
537 return COULD_NOT_MAP_MEMORY;
540 gboolean
541 mono_mmap_unmap (void *mmap_handle)
543 int res = 0;
544 MmapInstance *h = (MmapInstance *)mmap_handle;
546 res = mono_file_unmap (h->address, h->free_handle);
548 g_free (h);
549 return res == 0;
552 #endif