Merge pull request #2202 from mono/revert-2090-mono-4.2.0-branch-bug25480
[mono-project.git] / mono / metadata / file-mmap-posix.c
blob3de44d7a1db89342a2bd1282f14545a232ac12ed
1 /*
2 * file-mmap-posix.c: File mmap internal calls
4 * Author:
5 * Rodrigo Kumpera
7 * Copyright 2014 Xamarin Inc (http://www.xamarin.com)
8 */
10 #include <config.h>
12 #ifndef HOST_WIN32
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
30 #include <fcntl.h>
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>
40 typedef struct {
41 int kind;
42 int ref_count;
43 size_t capacity;
44 char *name;
45 int fd;
46 } MmapHandle;
48 typedef struct {
49 void *address;
50 void *free_handle;
51 size_t length;
52 } MmapInstance;
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
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,
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,
84 #ifdef DEFFILEMODE
85 #define DEFAULT_FILEMODE DEFFILEMODE
86 #else
87 #define DEFAULT_FILEMODE 0666
88 #endif
90 static int mmap_init_state;
91 static mono_mutex_t named_regions_mutex;
92 static GHashTable *named_regions;
95 static gint64
96 align_up_to_page_size (gint64 size)
98 gint64 page_size = mono_pagesize ();
99 return (size + page_size - 1) & ~(page_size - 1);
102 static gint64
103 align_down_to_page_size (gint64 size)
105 gint64 page_size = mono_pagesize ();
106 return size & ~(page_size - 1);
109 static void
110 file_mmap_init (void)
112 retry:
113 switch (mmap_init_state) {
114 case 0:
115 if (InterlockedCompareExchange (&mmap_init_state, 1, 0) != 0)
116 goto retry;
117 named_regions = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL);
118 mono_mutex_init (&named_regions_mutex);
120 mono_atomic_store_release (&mmap_init_state, 2);
121 break;
123 case 1:
124 do {
125 g_usleep (1000); /* Been init'd by other threads, this is very rare. */
126 } while (mmap_init_state != 2);
127 break;
128 case 2:
129 break;
130 default:
131 g_error ("Invalid init state %d", mmap_init_state);
135 static void
136 named_regions_lock (void)
138 file_mmap_init ();
139 mono_mutex_lock (&named_regions_mutex);
142 static void
143 named_regions_unlock (void)
145 mono_mutex_unlock (&named_regions_mutex);
149 static int
150 file_mode_to_unix (int mode)
152 switch (mode) {
153 case FILE_MODE_CREATE_NEW:
154 return O_CREAT | O_EXCL;
155 case FILE_MODE_CREATE:
156 return O_CREAT | O_TRUNC;
157 case FILE_MODE_OPEN:
158 return 0;
159 case FILE_MODE_OPEN_OR_CREATE:
160 return O_CREAT;
161 case FILE_MODE_TRUNCATE:
162 return O_TRUNC;
163 case FILE_MODE_APPEND:
164 return O_APPEND;
165 default:
166 g_error ("unknown FileMode %d", mode);
170 static int
171 access_mode_to_unix (int access)
173 switch (access) {
174 case MMAP_FILE_ACCESS_READ_WRITE:
175 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
176 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
177 return O_RDWR;
178 case MMAP_FILE_ACCESS_READ:
179 case MMAP_FILE_ACCESS_READ_EXECUTE:
180 return O_RDONLY;
181 case MMAP_FILE_ACCESS_WRITE:
182 return O_WRONLY;
183 default:
184 g_error ("unknown MemoryMappedFileAccess %d", access);
188 static int
189 acess_to_mmap_flags (int access)
191 switch (access) {
192 case MMAP_FILE_ACCESS_READ_WRITE:
193 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_SHARED;
195 case MMAP_FILE_ACCESS_WRITE:
196 return MONO_MMAP_WRITE | MONO_MMAP_SHARED;
198 case MMAP_FILE_ACCESS_COPY_ON_WRITE:
199 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_PRIVATE;
201 case MMAP_FILE_ACCESS_READ_EXECUTE:
202 return MONO_MMAP_EXEC | MONO_MMAP_PRIVATE | MONO_MMAP_SHARED;
204 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE:
205 return MONO_MMAP_WRITE | MONO_MMAP_READ | MONO_MMAP_EXEC | MONO_MMAP_SHARED;
207 case MMAP_FILE_ACCESS_READ:
208 return MONO_MMAP_READ | MONO_MMAP_SHARED;
209 default:
210 g_error ("unknown MemoryMappedFileAccess %d", access);
215 This allow us to special case zero size files that can be arbitrarily mapped.
217 static gboolean
218 is_special_zero_size_file (struct stat *buf)
220 return buf->st_size == 0 && (buf->st_mode & (S_IFCHR | S_IFBLK | S_IFIFO | S_IFSOCK)) != 0;
224 XXX implement options
226 static void*
227 open_file_map (MonoString *path, int input_fd, int mode, gint64 *capacity, int access, int options, int *error)
229 struct stat buf;
230 char *c_path = path ? mono_string_to_utf8 (path) : NULL;
231 MmapHandle *handle = NULL;
232 int result, fd;
234 if (path)
235 result = stat (c_path, &buf);
236 else
237 result = fstat (input_fd, &buf);
239 if (mode == FILE_MODE_TRUNCATE || mode == FILE_MODE_APPEND || mode == FILE_MODE_OPEN) {
240 if (result == -1) { //XXX translate errno?
241 *error = FILE_NOT_FOUND;
242 goto done;
246 if (mode == FILE_MODE_CREATE_NEW && result == 0) {
247 *error = FILE_ALREADY_EXISTS;
248 goto done;
251 if (result == 0) {
252 if (*capacity == 0) {
254 * Special files such as FIFOs, sockets, and devices can have a size of 0. Specifying a capacity for these
255 * also makes little sense, so don't do the check if th file is one of these.
257 if (buf.st_size == 0 && !is_special_zero_size_file (&buf)) {
258 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
259 goto done;
261 *capacity = buf.st_size;
262 } else if (*capacity < buf.st_size) {
263 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
264 goto done;
266 } else {
267 if (mode == FILE_MODE_CREATE_NEW && *capacity == 0) {
268 *error = CAPACITY_SMALLER_THAN_FILE_SIZE;
269 goto done;
273 if (path) //FIXME use io portability?
274 fd = open (c_path, file_mode_to_unix (mode) | access_mode_to_unix (access), DEFAULT_FILEMODE);
275 else
276 fd = dup (input_fd);
278 if (fd == -1) { //XXX translate errno?
279 *error = COULD_NOT_OPEN;
280 goto done;
283 if (result != 0 || *capacity > buf.st_size) {
284 int unused G_GNUC_UNUSED = ftruncate (fd, (off_t)*capacity);
287 handle = g_new0 (MmapHandle, 1);
288 handle->ref_count = 1;
289 handle->capacity = *capacity;
290 handle->fd = fd;
292 done:
293 g_free (c_path);
294 return (void*)handle;
297 #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
298 static void*
299 open_memory_map (MonoString *mapName, int mode, gint64 *capacity, int access, int options, int *error)
301 char *c_mapName;
302 MmapHandle *handle;
303 if (*capacity <= 1) {
304 *error = CAPACITY_MUST_BE_POSITIVE;
305 return NULL;
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;
313 c_mapName = mono_string_to_utf8 (mapName);
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;
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;
328 const char *tmp_dir;
329 int unused G_GNUC_UNUSED, alloc_size;
331 if (mode == FILE_MODE_OPEN) {
332 *error = FILE_NOT_FOUND;
333 goto done;
335 *capacity = align_up_to_page_size (*capacity);
337 tmp_dir = g_get_tmp_dir ();
338 alloc_size = strlen (tmp_dir) + strlen (MONO_ANON_FILE_TEMPLATE) + 1;
339 if (alloc_size > 1024) {//rather fail that stack overflow
340 *error = COULD_NOT_MAP_MEMORY;
341 goto done;
343 file_name = alloca (alloc_size);
344 strcpy (file_name, tmp_dir);
345 strcat (file_name, MONO_ANON_FILE_TEMPLATE);
347 fd = mkstemp (file_name);
348 if (fd == -1) {
349 *error = COULD_NOT_MAP_MEMORY;
350 goto done;
353 unlink (file_name);
354 unused = ftruncate (fd, (off_t)*capacity);
356 handle = g_new0 (MmapHandle, 1);
357 handle->ref_count = 1;
358 handle->capacity = *capacity;
359 handle->fd = fd;
360 handle->name = g_strdup (c_mapName);
362 g_hash_table_insert (named_regions, handle->name, handle);
366 done:
367 named_regions_unlock ();
369 g_free (c_mapName);
370 return handle;
374 void *
375 mono_mmap_open_file (MonoString *path, int mode, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
377 g_assert (path || mapName);
379 if (!mapName)
380 return open_file_map (path, -1, mode, capacity, access, options, error);
382 if (path) {
383 MmapHandle *handle;
384 char *c_mapName = mono_string_to_utf8 (mapName);
386 named_regions_lock ();
387 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
388 if (handle) {
389 *error = FILE_ALREADY_EXISTS;
390 handle = NULL;
391 } else {
392 handle = open_file_map (path, -1, mode, capacity, access, options, error);
393 if (handle) {
394 handle->name = g_strdup (c_mapName);
395 g_hash_table_insert (named_regions, handle->name, handle);
398 named_regions_unlock ();
400 g_free (c_mapName);
401 return handle;
404 return open_memory_map (mapName, mode, capacity, access, options, error);
407 void *
408 mono_mmap_open_handle (void *input_fd, MonoString *mapName, gint64 *capacity, int access, int options, int *error)
410 MmapHandle *handle;
411 if (!mapName) {
412 handle = open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, error);
413 } else {
414 char *c_mapName = mono_string_to_utf8 (mapName);
416 named_regions_lock ();
417 handle = (MmapHandle*)g_hash_table_lookup (named_regions, c_mapName);
418 if (handle) {
419 *error = FILE_ALREADY_EXISTS;
420 handle = NULL;
421 } else {
422 //XXX we're exploiting wapi HANDLE == FD equivalence. THIS IS FRAGILE, create a _wapi_handle_to_fd call
423 handle = open_file_map (NULL, GPOINTER_TO_INT (input_fd), FILE_MODE_OPEN, capacity, access, options, error);
424 handle->name = g_strdup (c_mapName);
425 g_hash_table_insert (named_regions, handle->name, handle);
427 named_regions_unlock ();
429 g_free (c_mapName);
431 return handle;
434 void
435 mono_mmap_close (void *mmap_handle)
437 MmapHandle *handle = mmap_handle;
439 named_regions_lock ();
440 --handle->ref_count;
441 if (handle->ref_count == 0) {
442 if (handle->name)
443 g_hash_table_remove (named_regions, handle->name);
445 g_free (handle->name);
446 close (handle->fd);
447 g_free (handle);
449 named_regions_unlock ();
452 void
453 mono_mmap_configure_inheritability (void *mmap_handle, gboolean inheritability)
455 MmapHandle *h = mmap_handle;
456 int fd, flags;
458 fd = h->fd;
459 flags = fcntl (fd, F_GETFD, 0);
460 if (inheritability)
461 flags &= ~FD_CLOEXEC;
462 else
463 flags |= FD_CLOEXEC;
464 fcntl (fd, F_SETFD, flags);
467 void
468 mono_mmap_flush (void *mmap_handle)
470 MmapInstance *h = mmap_handle;
472 if (h)
473 msync (h->address, h->length, MS_SYNC);
477 mono_mmap_map (void *handle, gint64 offset, gint64 *size, int access, void **mmap_handle, void **base_address)
479 gint64 mmap_offset = 0;
480 MmapHandle *fh = handle;
481 MmapInstance res = { 0 };
482 size_t eff_size = *size;
483 struct stat buf = { 0 };
484 fstat (fh->fd, &buf); //FIXME error handling
486 if (offset > buf.st_size || ((eff_size + offset) > buf.st_size && !is_special_zero_size_file (&buf)))
487 goto error;
489 * We use the file size if one of the following conditions is true:
490 * -input size is zero
491 * -input size is bigger than the file and the file is not a magical zero size file such as /dev/mem.
493 if (eff_size == 0)
494 eff_size = align_up_to_page_size (buf.st_size) - offset;
495 *size = eff_size;
497 mmap_offset = align_down_to_page_size (offset);
498 eff_size += (offset - mmap_offset);
499 //FIXME translate some interesting errno values
500 res.address = mono_file_map ((size_t)eff_size, acess_to_mmap_flags (access), fh->fd, mmap_offset, &res.free_handle);
501 res.length = eff_size;
503 if (res.address) {
504 *mmap_handle = g_memdup (&res, sizeof (MmapInstance));
505 *base_address = (char*)res.address + (offset - mmap_offset);
506 return 0;
509 error:
510 *mmap_handle = NULL;
511 *base_address = NULL;
512 return COULD_NOT_MAP_MEMORY;
515 gboolean
516 mono_mmap_unmap (void *mmap_handle)
518 int res = 0;
519 MmapInstance *h = mmap_handle;
521 res = mono_file_unmap (h->address, h->free_handle);
523 g_free (h);
524 return res == 0;
527 #endif