3 * File mmap internal calls
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.
22 #ifdef HAVE_SYS_STAT_H
25 #ifdef HAVE_SYS_TYPES_H
26 #include <sys/types.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>
59 BAD_CAPACITY_FOR_FILE_BACKED
= 1,
60 CAPACITY_SMALLER_THAN_FILE_SIZE
,
65 CAPACITY_MUST_BE_POSITIVE
,
69 CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
73 FILE_MODE_CREATE_NEW
= 1,
76 FILE_MODE_OPEN_OR_CREATE
= 4,
77 FILE_MODE_TRUNCATE
= 5,
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,
91 #define DEFAULT_FILEMODE DEFFILEMODE
93 #define DEFAULT_FILEMODE 0666
96 static int mmap_init_state
;
97 static MonoCoopMutex named_regions_mutex
;
98 static GHashTable
*named_regions
;
102 align_up_to_page_size (gint64 size
)
104 gint64 page_size
= mono_pagesize ();
105 return (size
+ page_size
- 1) & ~(page_size
- 1);
109 align_down_to_page_size (gint64 size
)
111 gint64 page_size
= mono_pagesize ();
112 return size
& ~(page_size
- 1);
116 file_mmap_init (void)
119 switch (mmap_init_state
) {
121 if (InterlockedCompareExchange (&mmap_init_state
, 1, 0) != 0)
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);
131 mono_thread_info_sleep (1, NULL
); /* Been init'd by other threads, this is very rare. */
132 } while (mmap_init_state
!= 2);
137 g_error ("Invalid init state %d", mmap_init_state
);
142 named_regions_lock (void)
145 mono_coop_mutex_lock (&named_regions_mutex
);
149 named_regions_unlock (void)
151 mono_coop_mutex_unlock (&named_regions_mutex
);
156 file_mode_to_unix (int mode
)
159 case FILE_MODE_CREATE_NEW
:
160 return O_CREAT
| O_EXCL
;
161 case FILE_MODE_CREATE
:
162 return O_CREAT
| O_TRUNC
;
165 case FILE_MODE_OPEN_OR_CREATE
:
167 case FILE_MODE_TRUNCATE
:
169 case FILE_MODE_APPEND
:
172 g_error ("unknown FileMode %d", mode
);
177 access_mode_to_unix (int access
)
180 case MMAP_FILE_ACCESS_READ_WRITE
:
181 case MMAP_FILE_ACCESS_COPY_ON_WRITE
:
182 case MMAP_FILE_ACCESS_READ_WRITE_EXECUTE
:
184 case MMAP_FILE_ACCESS_READ
:
185 case MMAP_FILE_ACCESS_READ_EXECUTE
:
187 case MMAP_FILE_ACCESS_WRITE
:
190 g_error ("unknown MemoryMappedFileAccess %d", access
);
195 acess_to_mmap_flags (int 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
;
216 g_error ("unknown MemoryMappedFileAccess %d", access
);
221 This allow us to special case zero size files that can be arbitrarily mapped.
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
233 open_file_map (const char *c_path
, int input_fd
, int mode
, gint64
*capacity
, int access
, int options
, int *ioerror
)
236 MmapHandle
*handle
= NULL
;
240 result
= stat (c_path
, &buf
);
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
;
251 if (mode
== FILE_MODE_CREATE_NEW
&& result
== 0) {
252 *ioerror
= FILE_ALREADY_EXISTS
;
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
;
266 *capacity
= buf
.st_size
;
267 } else if (*capacity
< buf
.st_size
) {
268 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
272 if (mode
== FILE_MODE_CREATE_NEW
&& *capacity
== 0) {
273 *ioerror
= CAPACITY_SMALLER_THAN_FILE_SIZE
;
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
);
283 if (fd
== -1) { //XXX translate errno?
284 *ioerror
= COULD_NOT_OPEN
;
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
;
298 return (void*)handle
;
301 #define MONO_ANON_FILE_TEMPLATE "/mono.anonmap.XXXXXXXXX"
303 open_memory_map (const char *c_mapName
, int mode
, gint64
*capacity
, int access
, int options
, int *ioerror
)
306 if (*capacity
<= 0 && mode
!= FILE_MODE_OPEN
) {
307 *ioerror
= CAPACITY_MUST_BE_POSITIVE
;
310 #if SIZEOF_VOID_P == 4
311 if (*capacity
> UINT32_MAX
) {
312 *ioerror
= CAPACITY_LARGER_THAN_LOGICAL_ADDRESS_SPACE
;
317 if (!(mode
== FILE_MODE_CREATE_NEW
|| mode
== FILE_MODE_OPEN_OR_CREATE
|| mode
== FILE_MODE_OPEN
)) {
318 *ioerror
= INVALID_FILE_MODE
;
322 named_regions_lock ();
323 handle
= (MmapHandle
*)g_hash_table_lookup (named_regions
, c_mapName
);
325 if (mode
== FILE_MODE_CREATE_NEW
) {
326 *ioerror
= FILE_ALREADY_EXISTS
;
331 //XXX should we ftruncate if the file is smaller than capacity?
336 int unused G_GNUC_UNUSED
, alloc_size
;
338 if (mode
== FILE_MODE_OPEN
) {
339 *ioerror
= FILE_NOT_FOUND
;
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
;
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
);
356 *ioerror
= COULD_NOT_MAP_MEMORY
;
361 unused
= ftruncate (fd
, (off_t
)*capacity
);
363 handle
= g_new0 (MmapHandle
, 1);
364 handle
->ref_count
= 1;
365 handle
->capacity
= *capacity
;
367 handle
->name
= g_strdup (c_mapName
);
369 g_hash_table_insert (named_regions
, handle
->name
, handle
);
374 named_regions_unlock ();
380 /* This is an icall */
382 mono_mmap_open_file (MonoString
*path
, int mode
, MonoString
*mapName
, gint64
*capacity
, int access
, int options
, int *ioerror
)
385 MmapHandle
*handle
= NULL
;
386 g_assert (path
|| mapName
);
389 char * c_path
= mono_string_to_utf8_checked (path
, &error
);
390 if (mono_error_set_pending_exception (&error
))
392 handle
= open_file_map (c_path
, -1, mode
, capacity
, access
, options
, ioerror
);
397 char *c_mapName
= mono_string_to_utf8_checked (mapName
, &error
);
398 if (mono_error_set_pending_exception (&error
))
402 named_regions_lock ();
403 handle
= (MmapHandle
*)g_hash_table_lookup (named_regions
, c_mapName
);
405 *ioerror
= FILE_ALREADY_EXISTS
;
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
);
412 handle
->name
= g_strdup (c_mapName
);
413 g_hash_table_insert (named_regions
, handle
->name
, handle
);
420 named_regions_unlock ();
422 handle
= open_memory_map (c_mapName
, mode
, capacity
, access
, options
, ioerror
);
428 /* this is an icall */
430 mono_mmap_open_handle (void *input_fd
, MonoString
*mapName
, gint64
*capacity
, int access
, int options
, int *ioerror
)
435 handle
= (MmapHandle
*)open_file_map (NULL
, GPOINTER_TO_INT (input_fd
), FILE_MODE_OPEN
, capacity
, access
, options
, ioerror
);
437 char *c_mapName
= mono_string_to_utf8_checked (mapName
, &error
);
438 if (mono_error_set_pending_exception (&error
))
441 named_regions_lock ();
442 handle
= (MmapHandle
*)g_hash_table_lookup (named_regions
, c_mapName
);
444 *ioerror
= FILE_ALREADY_EXISTS
;
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 ();
460 mono_mmap_close (void *mmap_handle
)
462 MmapHandle
*handle
= (MmapHandle
*)mmap_handle
;
464 named_regions_lock ();
466 if (handle
->ref_count
== 0) {
468 g_hash_table_remove (named_regions
, handle
->name
);
470 g_free (handle
->name
);
474 named_regions_unlock ();
478 mono_mmap_configure_inheritability (void *mmap_handle
, gboolean inheritability
)
480 MmapHandle
*h
= (MmapHandle
*)mmap_handle
;
484 flags
= fcntl (fd
, F_GETFD
, 0);
486 flags
&= ~FD_CLOEXEC
;
489 fcntl (fd
, F_SETFD
, flags
);
493 mono_mmap_flush (void *mmap_handle
)
495 MmapInstance
*h
= (MmapInstance
*)mmap_handle
;
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
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.
522 eff_size
= align_up_to_page_size (buf
.st_size
) - offset
;
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
;
532 *mmap_handle
= g_memdup (&res
, sizeof (MmapInstance
));
533 *base_address
= (char*)res
.address
+ (offset
- mmap_offset
);
537 return COULD_NOT_MAP_MEMORY
;
541 mono_mmap_unmap (void *mmap_handle
)
544 MmapInstance
*h
= (MmapInstance
*)mmap_handle
;
546 res
= mono_file_unmap (h
->address
, h
->free_handle
);